TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

576

11/13(土)にオンラインで開催されたGo Conference 2021 Autumnにシルバースポンサーとして協賛し、 1名のメンバーが登壇、オフィスアワーにも参加しました。 今回は登壇した東口( @hgsgtk )とオフィスアワー枠で参加した永野( @glassmonekey ) の両名による参加レポートをお届けします。 Go Conferenceとは Go Conference 2021 Autumn | Home Go Conferenceは半年に1回行われるプログラミング言語Goに関するカンファレンスです。 前回 に引き続き、オンライン開催でした。 今回、弊社は前回のブロンズからランクアップして、シルバースポンサーとして協賛しました。 ※ 本記事のGopherアイコンのライセンスは以下の通りです。 github.com The Go gopher was designed by Renee French. ( http://reneefrench.blogspot.com ) The gopher stickers was made by Takuya Ueda ( https://twitter.com/tenntenn ). Licensed under the Creative Commons 3.0 Attributions license. 登壇内容について こんにちは。BASE BANK 株式会社 Dev Division にて、 Engineering Manager をしている東口( @hgsgtk )です。 Create Go WebDriver Client from Scratch というタイトルで発表いたしました。 Webアプリ開発をしている現場ではUIレベルのブラウザ操作を自動化することによって実現する自動テストの作成は馴染みが深いものです。 ブラウザ操作の自動化にあたり出てくる WebDriver の Client はその仕様上 net/http パッケージ等の標準パッケージを介して自作することができます。 本トークでは、W3C勧告の標準化仕様WebDriver Wire Protocol等の詳細に軽く触れた上で、net/httpパッケージ等標準パッケージを組み合わせることでWebDriverとコミュニケーションをしブラウザを動かす実装方法を解説します。 登壇資料はこちらになります。 当日登壇中のトークの動画は以下の公式ページから見れます。概要レベルを喋りで抑えていただいて、詳細をスライドで見てもらえると、読み終わった瞬間から WebDriver を「完全理解」して好きなプログラミング言語で client を自作できるかとおもいます。 gocon.jp 何週間前から事前にリハーサルで配信方法についてすり合わせていただいたり、運営スタッフのみなさまありがとうございました。こぼれ話ですが、 GopherCon 2019 で San Diego でお会いした @hatajoe さんと久しぶりにお会いしたのが年月も感じてエモい気持ちになりましたw。 #gocon スポンサーリハーサル、GopherCon 2019のSan Diego以来の @hatajoe さんとの再会があった — Kazuki Higashiguchi (@hgsgtk) October 26, 2021 当日オフィスブースにいながらスピーカーの皆様のプレゼンテーションを聞いておりました。どれも知的好奇心がくすぐられるとても刺激なものばかりでした。登壇者の皆様、運営スタッフの皆様および参加者の皆様にこの場を借りて御礼申し上げます。とても刺激的な時間をありがとうございました! オフィス・アワーについて こんにちは。BASE BANK 株式会社 Dev Divisionにて、 Software Developerをしている永野 ( @glassmonekey ) です。 私からは当日のオフィスアワーで何をしたのか?どうだったのかをお伝えします。 connpass には以下で紹介を出させていただきました。 BASE BANKではフルサイクルエンジニアをエンジニア像として掲げて様々な技術を活用していますがバックエンド開発はGo言語がメインになります。そんなBASE BANKチームから2名のエンジニアがオフィスアワーに参戦します! 1. 現場のGoエンジニアの副音声セッション、スピーカーの皆さまのセッションを一緒に見ましょう! 2. BASE BANKのプロダクトとGoで何を開発しているのか、Goに限らず様々なシステム運用のざっくばらんな意見交換! 定期イベントとして開催している Gophers code reading party のご案内などホワイトボードに耳寄り情報をたくさん乗っけています。ぜひ立ち寄ってください♪ Go Conference 2021 Autumn (Online) - connpass 来場者の方にはホワイトボードを使った会社紹介や、隔週で行っているGoのコードリーディングパーティの宣伝などさせていただきました。 それに加えて、同じようにオフィス・アワーを行っている他社様のブースにお邪魔したりと、 オフラインでしか味わえなかったカンファレンスの廊下のような交流ができて大変楽しかったです。 特に採用まわりの赤裸々なトークができたりと、今しかできない話などできたのはいい体験でした。 様々な良い発表を聞けて、ますますGoが好きになりました。色々変更が入る1.18が今にも待ち遠しいですね。 今回は私自身は登壇するネタや時間を用意できなかったので、次回のSpringでは GenericsやFuzzingでの業務ネタで登壇できたらなと妄想中です。 登壇者の皆様、運営スタッフの皆様および参加者の皆様、貴重な時間を時間を割いて準備していただいてありがとうございました。最高の1日でした。 宣伝 GoのOSSを読む Go Code Reading Party なる会を定期開催しております。 BASEBANKブースに着ていただいた方にはお伝えしたのですが、週1回OSSのコードなどを読む会とかをしてるのでもし良かったらご参加ください。(宣伝) https://t.co/qQtMG4NRIE #goconRemo — エターナル・フィールド (@glassmonekey) 2021年11月13日 直近は11/25の15:00に開催予定です。 参加方法に関しては現在以下の2種類を用意しております。気軽にご参加ください。 github issue で参加表明をしていただく github.com gophers.slack.comの専用チャンネル(#basebank-code-reading-ja)に参加いただく 宣伝その2 BASE BANKチームでは Go, Python, PHPを中心に、フロントからインフラまでを一気通貫で開発しています。 また開発だけでなく、機能をグロース・分析・サポートまで担当します。 そんな開発スタイルに興味あるぞって方は永野( @glassmonekey )にDMを送っていただくか、 下記のリンクから気軽にご連絡ください。 open.talentio.com
アバター
はじめましての人ははじめまして、こんにちは!フロントエンドエンジニアのがっちゃん( @gatchan0807 )です。 今回は社内勉強会 Frontend Weekly LT にて、WebOTP / OTPの概要と使い方について発表をしたので、その内容を皆さまにも共有できればと思って記事にしました。 (以前、同じように社内勉強会での発表内容が記事化された 「Frontend Weekly LT(社内勉強会)で「Vite」について LT しました」 の記事もぜひどうぞ) 元々、BASEのどこかに使えないかなぁと思って個人的に調べていた内容を社内共有用にまとめたものなので一部 web.dev の記事をなぞっただけの部分もあるのですが、その内容と共にBASEでのOTPの使い方やWebOTPの利用状況についてのシェアしていければと思います。 イントロ Chrome 93から新機能が追加され、Android + Chromeユーザーであればさらに便利にOTPを利用できるようになりました。 今回の発表ではそれも含めて皆さまにWebOTPの機能について知ってもらい、世のOTPを使った電話番号認証がもっと便利になると嬉しいなと思っています! Chrome 93でのWebOTPの変更点の詳細は公式で出ている 「WebOTP APIを使ったスムーズなPCでの電話番号認証」についてのChrome Developerの紹介記事 をどうぞ。 ざっくりまとめると、 Androidスマホで受け取ったSMSからブラウザ経由でPCでもOTPを自動入力できるようになった! という変更が入りました。 今回はそれらの変更にも触れつつ、OTP / WebOTPの基礎知識から紹介していこうと思います。 目次 はじめに… そもそもOTPは何なのか? WebOTP APIって何? どうやって使う? どこでつかえる? はじめに… OTP、WebOTPについての一般論や使い方に関しては主に こちらのweb.devの記事 を参考に、一部自分の知見や社内での利用箇所に関しての情報を整理してまとめています。 そもそもOTPとは何なのか? OTP(one-time password / ワンタイムパスワード)とは、 Norton先生の記事 によると ワンタイムパスワードを直訳すると「一度きりのパスワード」です。一定時間ごとに発行され、文字通り一度きりしか使えないパスワード、およびそれを採用した認証の仕組みのことです。 各種サービスへのログインだけでなく、口座からの送金など慎重さが求められる操作する際に、第三者によるアクセスを防ぐ手法として主に金融機関などで普及が進んでいます。 となっており、一度だけ利用するパスワードとそれを使った認証の仕組みの総称です。 ひとくちにOTPといってもいくつか種類があり、今回言及するのはOTPの中でも 指定の電話番号にSMSを使ってOTPを送信するSMS OTPについて で、銀行アプリやGoogle Authenticatorなどの専用のアプリがOTPを発行する仕組みなどは対象外です。 このSMS OTPは電話番号と紐付けた形でユーザーの認証や確認のために利用されていて、主だったところで言うと メールアドレスの代わりのユーザー識別子として電話番号を1ユーザー ≒ 1アカウントとして認識する材料として利用する。 通常のパスワードと別にサインイン時に要求し、二段階認証を行うことでセキュリティをより高める方法として利用する。 大きい金額が移動する決済時に、本当に購入してよいか?を確認する方法として利用する。 のようなユースケースがあります。 BASEのなかでは、ショップに紐づく電話番号が「実際に存在」して、「ショップ運営者が管理している」電話番号であるかを検証する方法 としてSMS OTPが使われています。 (それが確認できた場合、ショップの特商法ページに電話番号が認証済みであることを表示し、より安心できるショップであることを表す方法として利用されています) PC版でのショップ電話番号認証のUI SP版でのショップ電話番号認証のUI PC版でのショップ電話番号認証のUI SP版でのショップ電話番号認証のUI WebOTP APIって何? 利用ユースケースがいくつかありBASEを含む様々なWebサービスで使われているSMS OTPですが、下記のような課題があるため、それらを改善する方法が以前より求められてきていました。 利用者からするとアプリやデバイスを横断して入力するのでめんどくさい フィッシング詐欺対策として効かない場合がある 解約した電話番号を再配布することもあるため、別のユーザーに対してSMS OTPが届いてしまうことがありうる 1. 利用者からするとアプリやデバイスを横断して入力するのでめんどくさい 利用者側からすると、ブラウザとSMSを受け取るアプリを移動してSMSで届いたパスワードを一時的に記憶した上で手で入力する必要 があり、安全のためとはいえ煩わしい作業となってしまっています。 さらにPCでOTPが必要な作業を行う場合、デバイスまでもを横断して作業を進める必要があるので、非常に煩わしい作業になりがちでした。 2. フィッシング詐欺対策として効かない場合がある セキュリティ保護のために利用されていることもあるOTPですが、ID/PassだけでなくOTPも同時に盗み取る手法を取られてしまうと セキュリティ強化のためのOTPが意味をなさなくなってしまう ため、問題になることもありました。 参照: IPA オンライン本人認証方式の実態調査報告書 3. 解約した電話番号を再配布することもあるため、別のユーザーに対してSMS OTPが届いてしまうことがありうる ユーザーが解約した電話番号を一定期間経過後に別のユーザーに対してキャリアから提供されることもあるため、 誤って別のユーザーのOTPが届いてしまうリスク をSMS OTPを利用する場合、どうしても孕んでしまっています。 (※これに関しては、SMS OTPを利用している以上取り除くことが難しい問題のため、専用アプリを使ったOTPや別の手法を取ることが推奨されています) これらの課題の解決方法の一つとして、WebOTP APIが提供されるようになりました。 現時点では、 Android Chromeと一部のモバイル用ブラウザでのみ 提供されているWebOTP APIという機能で、ブラウザがSMSアプリとのやり取りを自動的にブリッジし、 任意のプログラムからSMSに届いたOTPを取得してユーザーの手をわずらわせることなく、自動的に入力までを完了する ことができるようにして、より便利なWebアプリケーションのUXを実現できるようにしています。 iOS / Safari でも実装方法は違いますが、同じような自動入力機構が提供されています。 どうやって使う? 対象のブラウザでWebOTP APIを利用する方法としては3つの対応を行うだけで利用でき、比較的シンプルに利用可能なAPIとなっています。 <input> タグに autocomplete 属性を付与する(WebOTP APIの利用に必須ではないが推奨) JavaScriptで navigator 配下にある credentials オブジェクトから otp オプション付きでデータを取得する サービスから送信されるSMSに特定のフォーマットで記述したOTPを追記する 1. <input> タグに autocomplete 属性を付与する(WebOTP APIの利用に必須ではないが推奨) iOS Safari(ver14以上)の場合は <input> タグに対して autocomplete="one-time-code" を指定することで、自動でそのフォームがOTPの受け取り口であることを認識し、SMSで受け取ったOTPを自動ペーストする作業をレコメンドしてくれる機構があるため、まずその対応をしておきましょう。 web.dev でも、iOS / Android問わず類似のUXを提供し、互換性のために強く推奨する実装として紹介されています。 < form > < input autocomplete= "one-time-code" required/> < input type = "submit" > </ form > 2. JavaScriptで navigator 配下にある credentials オブジェクトから otp オプション付きでデータを取得する コードとしてはシンプルで、① OTPCredential が使えるかの確認と ② navigator.credentials.get に otp オプションを指定して取得するだけです。 ① OTPCredential が使えるかの確認 PWAやProject Fugu系のAPIを使う場合にはおなじみの if ('XXX' in window) でチェックをし、存在しない場合はWebOTP関連のコードを実行しないようにします。 if ( 'OTPCredential' in window ) { // 以下にWebOTPを使うコードを書く } ② navigator.credentials.get に otp オプションを指定して取得する WebOTPでは、実験的機能ではあるもののIE以外の多くのブラウザで実装されている CredentialsContainer というAPIが拡張されて機能が実現されており、それを使ってSMSからOTPを取得します。 CredentialsContainer自体は認証に関連するメソッド群をPromiseベースのAPIとして提供するインターフェイスで、 Credential Management API の一部です。 このAPIは基本的にブラウザのパスワード管理システムと対話するために利用しますが、今回のテーマからは外れるので割愛します。 実装自体は下記のように navigator.credentials.get に otp: { transport: ['sms'] } オプションを指定して取得し、Promiseが解決した時に受け取るオブジェクト内から code を取得して利用する形です。 より詳しいOTP取得までのプロセスとPromiseで受け取れるオブジェクトの中身などは こちらの解説 をご確認ください。 navigator.credentials.get( { otp: { transport: [ 'sms' ] } , signal: ac.signal } ).then(otp => { input.value = otp.code; if (form) form.submit(); } ). catch (err => { console.log(err); } ); web.dev より引用したコードの全体 if ( 'OTPCredential' in window ) { window .addEventListener( 'DOMContentLoaded' , e => { const input = document .querySelector( 'input[autocomplete="one-time-code"]' ); if (!input) return ; const ac = new AbortController(); const form = input.closest( 'form' ); if (form) { form.addEventListener( 'submit' , e => { ac.abort(); } ); } navigator.credentials.get( { otp: { transport: [ 'sms' ] } , signal: ac.signal } ).then(otp => { input.value = otp.code; if (form) form.submit(); } ). catch (err => { console.log(err); } ); } ); } 3. サービスから送信されるSMSに特定のフォーマットで記述したOTPを追記する HTML / JavaScript側の修正は以上で、最後にサービス側から送信しているSMSに手を加えることでWebOTPを使ったSMS OTPの自動入力を実現できます。 この対応をしていないSMSだと、SMSで送信されたテキストの中のどこにOTPのコードが記載されているのかが把握できず、また、どのサービス用のOTPなのかも判定ができないため、 SMS OTPの最終行に特定のフォーマットで紐付けるドメインとOTPを記入して送信する ことでプログラムからOTPの内容と対象サービスが判定できるようにフォーマットを定義しています。 そのフォーマットというのは以下のような形で、 あなたの認証コードは 123456 です。 こちらの認証コードをご入力ください。 @www.example.com #123456 SMSの最終行に @ で始まる形でドメインを指定し、半角スペースで区切った後に # から始まるOTPを記載する というものです。(フォーマットの細かいNG事項は こちらの一覧 をご確認ください) このフォーマットは W3Cで定義 されており、 origin-bound-one-time-code-message (直訳すると、オリジン(ドメイン)に紐付けられたワンタイムコード付きメッセージ)という名前でiOS / Android共にこのフォーマットを前提にSMS OTPの実装をしています。 どこで使える? WebOTP APIに対応しているブラウザは前述の通り、Android Chromeと一部のモバイルブラウザのみではあるのですが、iOS Safariでも 別の実装方法 ( input タグの autocomplete 属性として one-time-code を指定する方法)を取ることが可能です。 そして前述の通り、すでにWebOTPに対応しているサービスであれば、 ユーザー側の環境がPC側はChrome、SMSを受け取るスマホ側はAndroid Chromeで双方に同じアカウントでログインしている場合に受け取ったOTPをデバイスを越えて自動入力が行えるようになったアップデート が入り、これまで以上にSMS OTP入力の手間が減るようになっています。 まとめ これらのWebOTP / autocomplete="one-time-code" を使ったオーナーさんの手間を減らす細かい改善対応をこれから行っていければ良いなぁ〜と、調べながらに思いました。 また、今回Chrome 93で追加されたWebOTPのアップデートによってデバイスをも越えた連携ができるようになりましたし、(iOSが強い日本国内だと大手を振って使っていきにくい & 特定ベンダーに依存したコードを増やし過ぎてしまうのも問題の火種になりかねないという課題はあるものの、)Googleの世界で揃えたときのWebアプリケーションでできることが増えるのは見てて楽しいものがあるなぁ〜という感想も持ちました。 これからも、ちょくちょくブラウザ関連でWebでできることが広がるアップデートがあればシェアしていければなと思います! もし、「この部分間違ってるよ」や「この方が伝わりやすいよ」等あれば Twitter 等にご連絡いただけますと幸いです! 参照 Verify a phone number on desktop using WebOTP API Verify phone numbers on the web with the WebOTP API ワンタイムパスワードとは? – 使い方と安全性を簡単解説 オンライン本人認証方式の実態調査 報告書 About the Password AutoFill Workflow Enabling Password AutoFill on an HTML Input Element MDN / CredentialsContainer W3C / Origin-bound one-time codes delivered via SMS
アバター
まえがき こんにちは。Owners Experience Backend Group の杉浦です。主にサーバーサイドのアプリケーションの実装をしています。 エンジニアとして働いていると、当然、技術的なことには意識を向けるのですが、ROI(Return of Investment = 投資利益率)を意識することはあまりないと感じたので、この観点でエンジニアリングを考察しました。 想定する読者としては、下記を意識しています。 エンジニアの方で、ROIというビジネスの概念を知りたい方 エンジニアではない方で、ROIの観点でエンジニアリングの理解を深めたい方 このため、テックブログではあるものの、エンジニアではない方にも趣旨を理解いただけるように、技術に関しては少々噛み砕いて書いています。普段、コードには触れないビジネスパーソンの方にとっても、エンジニアリングを理解する一助になりましたら幸いです。 なお、筆者である私は、エンジニアに転生する前は、上場企業を対象とした投資会社で働いていました。普段からROICや競争優位性といったテーマに想いを巡らせてきたこともあり、本稿のバックグラウンドになっています。 リーディングガイド illustrated by @yusugiura このテックブログは3章からなります。 第1章では、ROIの再定義を試みています。エンジニアリングの世界では、そもそも金額効率を見るのがナンセンスであり、時間効率を意識すべきという、少々過激なスタンスをとっています。 第2章では、ROIを意識したエンジニアリングの実践例を書いています。「Facebookドメイン認証の自動化」というプロジェクトを例に、エンジニアが意識すべきポイントを考察しています。キーワードは「不確実性の可視化」です。 第3章では、ROIを意識したエンジニアリングがビジネスに競争優位をもたらす道筋を提示します。キーワードは「綺麗なコードは見えざる資産」です。 エンジニアではない方は、第1章と第3章をお読みいただければ論旨を理解いただけます。1分で結論を知りたい方は、第3章だけお読み下さい。 第1章 ROIの本質は何か? 伝統的なROIでは資金効率をみるが・・・ ROIはビジネスにおける金額効率を判定する指標です。 分母に投資額(Investment)、分子に利益額(Return)を設定して計算します。少ない投資額に対して、利益額が多ければ多いほど良い、というわけです。 ROIの分母である「投資」を広義の意味で捉えると「ヒト・モノ・カネ」といった希少資源に対する配分と定義できます。 たとえば、製造業の場合は「工場で使う工作機械や金型」、不動産デベロッパーの場合は「土地の仕入れ」、保険会社であれば「営業人員の人件費」、流通問屋であれば「在庫の確保」といった具合です。 企業は競争に勝つために、ビジネスで欠かせない要素に傾斜投資するため、ROIは本質的に「何に重点投資をするか?」という意思を問うものになります。 つまり「利益を生み出す効率を最大化するために、どの部分に、いくら投資すべきかを、よく吟味しましょう」というのが、ROIの存在意義になります。 ソフトウェアは「ヒト・モノ・カネ」で捉えられない では、ROIの分母である「投資」について、ソフトウェアのエンジニアリングの観点では、どのように捉えたらよいのでしょうか? 伝統的なROIを踏まえると「ヒト・モノ・カネ」が分母の値として適しているように見えますが、ソフトウェアの世界にこの考えを適用するのは少々危険です。 illustrated by @yusugiura 「ヒト」に関しては、エンジニアの頭数を増やせば良いという安易な考えは禁物です。場合によっては、開発現場に混乱をもたらして、逆に生産性が下がるかもしれないからです。 「モノ」に関しては、必要最低限でまかなうことができます。ソフトウェア企業における固定資産への投資はパソコンやサーバーに限られるからです。 「カネ」に関しては、投資金額を増やせば素晴らしいソフトウェアが完成するという、単純な関係は成立しません。 優れたソフトウェアの例では、エンジニアたちに愛されている様々なライブラリーは、OSS(オープンソースソフトウェア)によって、ほぼ無償で開発されています。 逆に、悪いソフトウェアの例では、何百億円、数千億円という莫大な金額をかけたプロジェクトが「稼働したら実は致命的なバグだらけだった」「全く稼働しなかった」というのは、この業界ではよく耳にする話です。 もちろん、お金は大切であり「Cash is King」はまさしくそうなのですが、「単純にお金をかければ良いのだ!」というわけではないところが、ソフトウェアの難しいところです。 ROIを意識したエンジニアリングでは時間効率をみる では、ROIにおける分母の「投資」は、エンジニアリングにおいて、どのように捉えたらよいのでしょうか? 私の結論は「投下時間」を設定することです。 伝統的なROIが「資金効率」を測定するものであったのに対して、エンジニアリングのROIでは「時間効率」をみるということです。 illustrated by @yusugiura あえて時間軸を見る理由は、ソフトウェア企業にとって「スピード」こそがサービスの成否を決める重要な要素だからです。 この業界では、厳しいグローバル競争が繰り広げられています。変化し続けるユーザーのニーズに応えるために、素早く機能をアップデートして、サービスの改善改良を継続しないと、長期的には存続できません。 ちなみに、BASEではMove Fastを行動指針の1つとして掲げていますが、同じような方針を掲げる企業は数多くあります。 これは「スピードを意識しないと、その先にはDieが待っているから」であり、明文化して常に意識しなければならないほど、激烈な競争が繰り広げられているからにほかなりません。 利益額を意識すると間違いを犯す 次に、ROIの分子である「リターン」は、エンジニアリングにおいて、どう捉えたら良いのでしょうか? 伝統的なROIのリターンには、利益額が設定されます。当然のことながら、生み出す利益額が多ければ、これに越したことはありません。 では、ROIを意識したエンジニアリングにも、リターンに利益額という指標を使用してよいのでしょうか? 答えはノーです。なぜかといえばソフトウェア企業には「時間軸のジレンマ」が存在するからです。 たとえば、新しい機能を実装してリリースしたとしても、短期的には「ユーザーの獲得」などに大きく貢献しない場合があります。リリースした機能がユーザーにとって使い勝手が悪かった場合、ユーザーが欲している機能であっても、その目的を達成できないからです。 ところが、その後、関連する別の機能がリリースされたり、UIが改善されたときに、かつてリリースした機能がベースとなって、ユーザーの獲得に大きく貢献することは珍しくありません。 illustrated by @yusugiura ソフトウェアは連続的かつネットワーク的に発展する技術であり、各パーツ(=機能)の統合によって、ユーザーに新しい便益を提供することができるという性質があります。 よって、ある機能をリリースした直後、つまり時間軸の初期段階では「利益」を期待することができません。 このため、利益額を前提とした意思決定はミスを犯すことになります。「短期的に利益が出ないなら、その機能の開発はストップしよう」という判断は、一見すると合理的に見えますが「時間軸によるネットワーク効果」を無視した行動になり得るからです。 エンジニアリングにおいて、短期利益を重視するスタンスは「長期利益を放棄する」ことを意味します。逆説的ですが「目先の利益を最大化するぞ!」という姿勢は「ぜんぜん貪欲じゃないなぁ・・・」とさえ思えます。 ユーザーが抱える問題の解決を意識する では、ROIを意識したエンジニアリングにおいて、分子の「リターン」には何を設定すべきでしょうか? その答えは「ユーザーが抱える問題の解決」です。 illustrated by @yusugiura ただし、問題解決の内容を具体的に定義することはできません。なぜかといえば、ユーザーが抱える問題のパターンは無限に存在しており、一つには決定できないからです。 たとえば、新しくリリースする機能が「ユーザー獲得につながる」のか、「ユーザーの維持につながる」のか、もしくは「コストダウンにつながる」のか、その狙いはプロジェクトの性質や、企業の成長フェーズによってさまざまです。 遂行するプロジェクトの性質によって、リターンに設定すべき値を考えなければならないと言えるでしょう。 第2章 ROIを意識したエンジニアリングの実践 「Facebookドメイン認証の自動化」プロジェクト それでは、実際に、ROIを意識して、どのようにエンジニアリングを実践していくのがよいのかを見ていきます。 今回取り上げるのは、2021年9月8日にリリースした「Facebookドメイン認証の自動化」というプロジェクトです。BASEとInstagramを連携するために、ユーザーがFacebookを通じてドメイン認証を行う必要があり、このフローを自動化するという狙いでスタートしました。 このプロジェクトは、実装者としては、私が1人で携わる形になり、試験的にROIを意識する良い機会だと思って(こっそりと)実践することにしました。 リリースにいたる背景 このプロジェクトが立ち上がった理由は、いままでのドメイン認証というフローが、Instagramを連携したいユーザーにとって、ボトルネックになっていると考えられたからです。 従来の機能では、ユーザーに「metaタグを設定する」というアクションを求めていました。 metaタグとは、HTMLで定義される要素の1つです。metaタグに認証用のコードを埋め込むことによって、外部のシステムがサイトの所有者を確認するために使用されます。 ですが、metaタグという概念は、エンジニアリングの専門的な知識であり、本来、ユーザーが知る必要はありません。 illustrated by @yusugiura したがって、metaタグの設定を含めたドメイン認証のフローを、すべて自動化することによってユーザーの不便を解消し、「ドメイン認証が完了したショップ数を増やす」というのが目指したゴールでした。 このような事情があったため、リリースは1日でも早いほうが良い、という状況でした。 そこで、ROIを高める観点で、4つの方針を立てました。 方針1 バックエンドとフロントエンドを1人で実装する 方針2 ユーザーに通知するエラーを限定する 方針3 リリース後の分析項目を限定する 方針4 不確実性を下げるためにコミュニケーションする 方針1:バックエンドとフロントエンドを1人で実装する 素早い改修を行うために、最初に決めた方針が、実装者である私自身がバックエンドに加え、フロントエンドの実装も同時並行で行うことでした。 この理由は、ドメイン認証にあたっては、外部システムから提供される複数のAPIを叩くため、不確実性が高いことが予想されたからです。 本来は、API仕様書に記載された内容を精査することはもちろん、実際にAPIを叩いて、想定されるすべてのパターンを設計に盛り込むべきです。 ですが、限られた時間の中で、API仕様書を抜け漏れなく確認することは非現実的でした。リリースが遅れてユーザーに不便を強いる期間が長引いてしまうことも、好ましくありません。 illustrated by @yusugiura そこで、今回の実装では「外部のAPIは不確実である」という前提を置き、実装の途中で想定外の仕様を発見した場合は、バックエンドとフロントエンドへの修正が必要になることを考慮しました。 したがって、予期しない修正によって生じる調整コストを最小化するという観点から、サーバーとフロントを1人で実装することが合理的であると判断したのです。 方針2:ユーザーに通知するエラーを限定する システムが取り扱うエラーのパターンが多い場合、その全てをユーザーに表示することは現実的ではなく、取捨選択が必要になります。 ドメイン認証を実行する際も、外部システムのAPIを複数叩く必要があり、それぞれのAPIに対して複数のエラーのパターンが存在します。このため、ユーザーにエラーを伝えるという観点において、対応範囲を絞ることが有効と判断しました。 今回の実装では、ユーザーにとって遭遇しやすいエラーはその理由を画面に表示する一方で、頻度の少ないエラーが発生した場合は、ヘルプページに取るべきアクションを記載する形をとりました。 方針3:リリース後の分析項目を限定する リリースをした後に必要なのが分析です。この内容が「次にどうアクションすべきか?」という意思決定の材料になります。エンジニアとしては「分析を楽にする」ことも視野に入れて、実装しなければなりません。 そこで今回は、分析で測定すべき項目を下記に限定しました。 ドメイン認証に成功したユーザー数 ドメイン認証に失敗したユーザー数 失敗した場合のエラーの原因 これらのデータが取得できれば「失敗率」と「失敗理由」が判明するため、仮に失敗率が高かった場合に、BASEとしてとるべきアクションが明確になります。 具体的な実装方法としては、APIの実行時にログを内部出力するコードを仕込み、Elastic社が提供するログ解析ツールのKibanaによって分析できるようにしました。実際に分析結果をまとめるところまで、エンジニアとして対応しています。 なお、今回の分析では、データベースのテーブルに分析用のカラムを追加することを、あえて避けています。 その理由は、外部システムとの連携を前提とするため「認証済みである・認証済みではない」というステータスを、BASEのシステムでは管理できないからです。ユーザーが自分自身でmetaタグを埋め込んで、BASEを介在させずに、外部システムから認証することも考えられるため、この方法で認証に成功した場合、BASEのシステムは感知できません。 一方で、分析用のカラムを使わないことは、代償を伴います。テーブルをjoinするといった複雑な分析の可能性を放棄していることや、ログの分析は実装したエンジニアが行わなければならないからです。 今回は、ログを活用して十分に検証できると判断しましたが、分析用のカラムを活用すべき局面もあります。このあたりの方向性は「何を分析すべきなのか?」「誰が分析するのか?」という観点から考える必要があるでしょう。 方針 4:不確実性を下げるためにコミュニケーションする illustrated by @yusugiura あらかじめ完璧に設計したという自信を持っていても、不測の事態は起こり得るものです。 今回のプロジェクトでは、万一の事態に備えるために、頻繁にコミュニケーションを取ることを意識しました。 プロジェクトの責任者の方に毎日進捗を報告し、APIの仕様に不明点があれば詳しい方に質問し、不確実性の共有と可視化に努めました。 社内で検討してもわからないAPIの仕様があったときには、外部の企業に直接問い合わせたため、実装時間と同じくらい、コミュニケーションにも時間を割く形となりました。 つくづく、エンジニアリングとは、不確実性との闘いだと思います。 コードの品質を妥協してはいけない ROIを意識したエンジニアリングを通じて、妥協してはいけないのがコードの品質維持です。 実装時間を短縮するために、綺麗ではないコードを容認してしまうと、次のエンジニアが実装に取り掛かる際に、ROIが大きく低下します。綺麗ではないコードとは「責務が適切に分離されず、リーダブルでなく、テストが十分ではない状態」と捉えています。 今回の「Facebookドメイン認証の自動化」の実装にあたっては、既存のコードから多くの恩恵を受けました。ビジネスロジックを扱う層が的確な粒度で分離されており、テストコードも過不足なく存在していたため、実装スピードの向上が可能になったからです。 品質の高いコードを将来に継承することも、ROIを高めるうえで大切な事だといえます。 第3章 競争優位をもたらすROIエンジニアリング 綺麗なコードは「見えざる資産」 ROIを意識したエンジニアリングが、ビジネスの競争優位に結びつく道筋は次の通りです。 インターネットを取り巻く技術は常に変化する それに伴ってユーザーが求めるニーズも日々変化する したがって、リリース頻度を高めることが競争優位につながる そこで、時間効率を意識したROIエンジニアリングが有効になる 競争優位の持続には「綺麗なコード」の資産化が欠かせない このなかで、エンジニアのご経験がない方にとって理解しにくいことが「エンジニアが綺麗なコードに執着する理由」かと思います。 コードにおける「綺麗」をわかりやすく言い換えると「エンジニアの誰もが理解しやすいように書かれており、将来の改修可能性を考慮した状態のこと」をいいます。 つまり「綺麗なコード」がシステムの内部に蓄積されていれば、将来にわたってエンジニアによる実装のスピードは向上するため、リリースの頻度が高まり、結果としてビジネスにおける競争優位をもたらします。 逆に、コードを「汚くても良い。動けば何でも良いじゃないか」と捉えた瞬間、短期的にはリリースのスピードを向上できたとしても、そのシステムは長期的に競争優位を失うことになります。 なぜならば、一度でも窓ガラスが割れてしまうと「綺麗なコード」を書く規律が失われてしまうからです。こうなると、システムにおける不確実性が高まり、エンジニアは疲弊します。そして、その噂は必ず広まり、新たにエンジニアを採用できないという、悪循環に陥ります。ここまでいくと、もはやなす術がありません。 コードの品質には「普通」という曖昧な概念は存在せず、「綺麗である」と「綺麗ではない」の2つしか存在しません。いわば真偽値(boolean)です。 そして「綺麗なコード」への規律を保つことは容易ではないからこそ、規律を維持できれば差別要素になります。 この意味で「綺麗なコード」は、競争優位性を持続させるためのカギであり、貸借対照表における「見えざる資産」なのです。 illustrated by @yusugiura ROIを意識したエンジニアリングの限界 ROIを意識したエンジニアリングには、ある限界が存在します。 それは、ROIを追求した先に「ビジネスにおける長期利益の最大化が達成されなければならない」ということです。 ROIを意識したエンジニアリングには、利益額を設定できないという限界が存在します。しかしながら、サービスの提供者が株式会社である以上、長期的に利益を生み出すシナリオを描けないと、いずれ財務危機に陥ります。 つまり、エンジニアリングのROIを最大化したとしても、ビジネスを通じて長期利益を獲得する「筋書き」が存在しないと、従業員・株主・経営者の苦労は報われないということです。 この点は、ROIを意識したエンジニアリング「だけ」を追求したときの限界として、胸に刻んでおく必要があります。いくらリリースの時間効率を高めて、綺麗なコードを書き続けたとしても、その事業が長期利益に結びつかなければ意味がありません。 エンジニアリングとは、目的を実現するための手段であり、目的そのものではないのです。 終わりに & 宣伝 エンジニアリングに終わりはない ここまでROIを意識したエンジニアリングについて考察をしてきましたが、あくまで、現時点の私見であり、未完成の考察になります。エンジニアリングで投資効率を考えるという、少々突拍子のない論考であり、しかもROIの定義を強引に変更しているため、完成された思索でもないからです。 そして、web業界の激しい変化の渦中にあっては、完成することを目指すべき論考でもない、と感じています。仮に完成されたとしたら、それはweb業界の発展がストップしたことを意味しますが、当分の間、このような状況が来ることはないでしょう。 あくまでも、ユーザの抱える問題を解決するために、最適なエンジニアリングの方法を模索し続ける姿勢が大事なんだなと、日々、感じています。 いつもの宣伝 BASEではサービスを発展させるうえで、エンジニアも募集しております! カジュアル面談も実施しておりますので、お気軽にお問い合わせください! https://open.talentio.com/r/1/c/binc/homes/4380 参考文献(書籍) エンジニアリングの視点 Bill Karwin『SQLアンチパターン』オライリージャパン、2013年 Dustin Boswell、Trevor Foucher『リーダブルコード:より良いコードを書くためのシンプルで実践的なテクニック』オライリージャパン、2012年 Thomas Kuhn『科学革命の構造』みすず書房、1971年 大野耐一『トヨタ生産方式:脱規模の経営をめざして』 ダイヤモンド社、1979年 成瀬允宣『ドメイン駆動設計入門:ボトムアップでわかる!ドメイン駆動設計の基本』翔泳社、2020年 広木大地『エンジニアリング組織論への招待:不確実性に向き合う思考と組織のリファクタリング』技術評論社、2018年 投資の視点 John Kenneth Galbraith『不確実性の時代』TBSブリタニカ、1978年 Nassim Nicholas Taleb『ブラック・スワン[上]:不確実性とリスクの本質』ダイヤモンド社、2009年 Nassim Nicholas Taleb『ブラック・スワン[下]:不確実性とリスクの本質』ダイヤモンド社、2009年 Scott Kupor(2019). Secrets of Sand Hill Road: Venture Capital―and How to Get It. : Virgin Books. 中神康議『三位一体の経営:経営者・従業員・株主がみなで豊かになる』ダイヤモンド社、2021年 経営の視点 大津広一『企業価値を創造する会計指標入門』ダイヤモンド社、2005年 楠木建『ストーリーとしての競争戦略』東洋経済新報社、2013年 三品和広『高収益事業の創り方(経営戦略の実戦(1))』東洋経済新報社、2015年 社会の視点 Daniel Bell『脱工業社会の到来:社会予測の一つの試み』ダイヤモンド社、1975年 今井賢一『情報ネットワーク社会』岩波書店、1984年
アバター
この3ヶ月で開催したBDIの内容を紹介します こんにちは、Design Group Manager の小山です。 7〜9月もデザイナー発信の勉強会「BDI」を実施したので、どんなことをやっているのか内容をまとめました。 社内勉強会のネタのタネにぜひご活用ください! BDIとは? 『BDI』は「BASE Design Inspiration」の略。 2018年の秋頃から活動している、デザイナーがやりたいことを持ち寄って、 デザインに関する幅広い知見をみんなで楽しく学ぶことを目的とした任意参加の社内勉強会です。 BASEのデザイナーであれば、デザイナーだけでなく誰でも参加することができます。 Inspirationの名の通り新たなひらめきにつながる新しいトピックを取り上げることも多くあります。 過去に開催したBDIのまとめはこちら devblog.thebase.in devblog.thebase.in 7月 実践編!デザインツール「Figma」をマスターしよう 6月に実施したFigma初心者向けの使い方解説に続いて、今回はFigmaのマニアックな使い方を学べる"応用編"を実施しました。 BASEでは2021年から本格的にデザイン業務でFigmaを使い始めました。 実務でFigmaを使う中で初めてわかる「使いづらい」と感じるポイントに対して、Figmaマスターのデザイナーが、他のデザイナーにTipsを紹介する形式でBDIを開催。 この他にもSmart Animateのマニアックな使い方や、現在ベータ版で提供されているInterective Componentsの使い方などを解説してもらい、みんなで実際にデータを触りながらFigmaの使い方を学びました。 完全に余談ですが、最近公開された Cocoda Gallery でBASEのFigma移行についてを詳しく紹介しているので、よければこちらも見ていただけると嬉しいです! cocoda.design 新人×ベテランデザイナー対談「デザインここがわからない!」 去年デザイン未経験のインターンからデザイン研修を通して入社したデザイナーと当時メンターをしていた先輩デザイナーの対談を開催しました。 初学者ならではの「そもそもこれって...」という質問をきっかけにいろいろな話をする会になりました。 slackの実況チャンネルを作成して、画面共有をしているスライドにコメントを流したことで、2人の対談だけにとどまらず、参加してくれていたデザイナーもそれぞれの考えを共有してくれて盛り上がりました。 終盤では、普段参考にしているおすすめのデザイナーやポートフォリオサイトを雑談形式で紹介し合う流れになり、リモート環境下でPJに関わらずデザイナー間でコミュニケーションを取るいい機会になったと思います。 去年の記事になりますが、新人デザイナーが入社4ヶ月目に書いたふりかえりブログはこちらから見ることができます。 devblog.thebase.in 8月 404エラーページを分析してみよう 普段は意識することがないけど、意図しないタイミングでユーザーの目に触れてしまうことがある404ページ。 「そもそも404ページにはどのような役割があるのか?」 「他サービスの404ページはどのような工夫がなされているのか?」 を分析し、他社の事例を比較する会を開催しました。 この回ではFigmaのアカウントを持っていない人でも参加しやすいようにMiroを活用しました。 まずは404ページが持つ役割ってどんなものがあるかを参加者でブレスト的に付箋に書き出し、「確かにそういう役割や体験もあるね」という話をしながら整理をしました。 後半はさまざまなサービスの404ページを整理した内容を踏まえて分析していきました。 サービスごとに遊び心を持たせたページになっていたり、ターゲットユーザーによって使われている文言が違っていたりと、404ページに訪れたことがネガティブな体験にならないような工夫がされていることがわかり面白かったです。 UXライティング入門!意外と間違えやすい日本語 デザインを作成する上で切っても切り離せないUXライティング。近年UIデザイナーの中でもその大切さが改めて注目されていますね。 BASE内で日頃UXライティング視点でテキストのチェックや作成を行ってくれているメンバーにファシリテーターをお願いし、間違えやすい日本語クイズと各種サービスのUXライティング比較のワークショップを開催しました。 ↑みなさんはこの答えわかりますか...? 正しく、わかりやすい日本語を使うことは、ユーザーを思いやること。 今回はスライド内にクイズを入れることで聞いてる人も参加できる工夫を行い、楽しく「正しい日本語を使うことの大切さ」を学びました。 後半ではmiroを使ってライティングの比較をするワークショップを実施しました。 トンマナの違う複数のサービスを比較し、どのようなトンマナだとどのような印象を受けるのかをみんなで考え、付箋に書き出して行きました。 最近は社内でもUXライティングに対する意識が徐々に上がってきていたり、テキストガイドラインの運用が始まったりしています。 UXライティングの活動については去年のAdvent Calendarでも書いているのでこちらもチェックしてみてください! devblog.thebase.in 9月 ショップデザインRTA〜1時間でショップデザインできるかな〜 この回では、BASEのショップデザイン機能リニューアルを担当したデザイナーにファシリテーターになってもらい、お題に合わせたショップデザインを1時間で作成しました。 あらかじめいくつかのショップのテーマ設定を用意してオンラインのあみだくじで誰がどのテーマでショップのデザインをするかをその場で決めました。 ワークショップが始まるまでどんなショップを作ることになるかわからないドキドキ感とよーいスタートで一斉に作り始めるゲーム感がとても楽しかったです。 ショップをデザインした後はMiroにキャプチャを並べて、それぞれどんなところにこだわったかをシェアしました。 サービスをあらためて使ってみて、できること/できないことの再確認やチームとしての共通認識ができたんじゃないかなと思います。 ちなみにショップデザイン機能についてはこちらの記事で詳しく紹介しているのでよければ覗いてみてください! baseu.jp OOUIを使って経費精算UIを整理しよう この回では、立替経費精算などで利用する「経費精算ツール」のUIをOOUIで作成し直すというテーマでBDIを開催。 最近PMチームが読書会でOOUI本を読んでいるという話を聞いたので、この機会にBDIでワークショップを行うことにしました。 前半はOOUIについてのLTを行い、後半は実践ワークショップとして、タスク指向になりがちなUIを、OOUIのフレームワークに沿って整理しました。 身近な経費精算で使っているツールを題材にしたので、イメージしやすいワークショップになりました。 まとめ 7〜9月のワークショップでは、社内で話題になっているトピックを意識的にテーマに取り入れて実施することができたかなと思います。 BASEのデザインチームでは他にもいくつかの勉強会や共有会を実施しており、それぞれアップデートを行っています。 BDIも10月から開催頻度や運用を見直しながら、社内の「きっかけづくり」をしていけたらと思います。 次回のまとめも楽しみにしていただけたら幸いです!
アバター
CTOの川口 ( id:dmnlk ) です。 プロダクト開発組織を運営していく中で地味ながら無視できないものとして、「支給PCの選定」というものがあります。 PCスペックによって大きく作業効率が変わるので、なるべくで希望通りのものを支給していくというのが基本方針ではありますが同時に社内資産としての価値やチームでの伝達効率といった点は重要です。 それらを考えるなかで、1年ほど前に発売されたAppleのM1チップ搭載のMacについて解禁するまでの過程を紹介します。 この文章はもともと社内向けに公開予定でしたが、使用PCの選定基準を最も知りたいのはこれから入社される方ということに気づいたため入社前にアクセスしやすい文書として公開ブログとすることにしました。 今までのエンジニア/デザイナー向け支給PCの基準について BASEでのエンジニア/デザイナーに支給しているPCは現在下記の基準になっています。 以前よりTypeScriptのbuildなども多くなり要求マシンスペックが高くなっているなと感じます。 それ以前よりbuildフェーズの比重が大きいネイティブアプリチームにはiMac Proを支給していたりもします。 Mac/Windowsの制限はかけていませんが、大体のメンバーがMacを選んでいることで何か問題が起きたときに対応がしやすいという点でMacを推奨しており一部検証用にWindowsを複数台共用で支給しています。 特にこだわりがない場合は下記スペックでキーボードをJP/USで選んでもらっている形です。 16インチMacBook Pro 2.6GHz 6コアIntel Core i7プロセッサ 32GB 2,666MHz DDR4メモリ 512GB SSDストレージ AMD Radeon Pro 5300M 13インチMacBook Pro 2.3GHzクアッドコアIntel Core i7プロセッサ 32GB 3,733MHz LPDDR4Xメモリ 512GB SSDストレージ Intel Iris Plus Graphics M1チップ搭載のMacの登場 2020年11月にAppleが発表した次世代のM1チップを搭載したMacシリーズが発表されました。 www.apple.com M1チップがデフォルトになるであろうことは予期できましたが、発売時点では各種アプリケーションが正常に動作しない報告も多くこのまま開発者に支給してしまうと本来やるべき開発が滞ってしまうため支給は一旦控えることにしました。 CTOである自分は全く開発しないことはないですが、メイン開発者としてプロジェクトに参画することはないのでまずサブ機として一台購入し試していくことにしました。 しばらく使用してM1 Mac自体は素晴らしい機体であることがわかりました。 13インチのMacBook Airの16GB RAMにカスタマイズしたものを買ったのですがPHP StormとChromeでしっかり開発出来ますし何よりバッテリーの持ちがいいためZoomで何時間もMTGしていても殆どバッテリーは消費されていませんでした。 支給までのボトルネック とはいえ全く問題がないわけではありませんでした。 特にローカル開発で最重要であるDockerがM1対応されることが必要条件です。 対応は12月に行われましたが、BASEのローカル環境はしばらく動くまでの対応はかかりました。 とりあえず自分だけの問題かはわかりませんが、Issueを立てておきました。 github.com Issueを立てると同じような問題を持っている人達が多くコメントを頂き根本原因としてはApple側の問題ということもわかりました。 つたない英語でもIssueを立てておくということは重要なので、臆せずIssueを立てましょう(事前にissueは検索するとして)。 この問題はKnown Issueとしてもdocにリンクされています。 docs.docker.com 開発が進み、docker-desktop-rc-3によってワークアラウンド的対策が入ったようで「Use the new Virtualization framework」のチェックを外すことでローカル環境が完動するようになりました。 これにより開発者視点での支給に対するボトルネックは解消されましたがもう一点重要な点が残っていました。 それはアンチウイルスソフトが対応されていないことでした。 これはM1対応というよりはMacOS Big Sur対応すら非常に遅れており、社内のIntel Macもアップデートが出来ないという状態になっていました。 ユーザーさんが触っている環境を日常的に触れていないということはサービス開発の視点でもリスクと考え、現在社内全体のアンチウイルスソフトのリプレイス計画を進めており年内目標で別のソフトウェアにリプレイスしていく予定です。 結果として、2021年6月にBig SurおよびM1対応が行われましたのでようやく支給の準備が出来ました。 ここでデザイナーチームにも1台支給し普段利用するソフトウェアなどが問題なく動くか、といった点でテストをしてもらいごく一部の動画編集ソフトを除き問題なく動いたようでした。デザイナーからもM1 Macは好評でした。 リリース当初はHomeBrewなど動かす場合にワークアラウンドが必要でしたが現在は特に考える必要はないでしょう。 社内で使われているフロントエンドビルド用のNode.jsがv12系のためネイティブ対応しているv16にアップデートしないとビルド時に速度が出ないという問題もあるのでここはフロントエンドチームに取り組んでもらおうと思っています。 どのMacを選ぶべきか これからBASEに入社されるエンジニア及びデザイナーがどちらのMacを選ぶべきか。 M1 Macは見かけのスペック以上に快適なマシンです。今自分が選ぶなら16インチがない状態でもこちらを選ぶでしょう。しかしながらフロントエンドのbuildには相当時間かかっている感じは否めないのでMacBook Pro16インチ相当が出て欲しいとも思います。 反面、ネイティブ対応されていないソフトウェアなどもまだ多くその場合に自分で解決できるような対応力は求められるでしょう。 社内にもまだ殆ど支給されていないので助けてくれるメンバーも多くはないので、僕も手伝えますがある程度は頑張って欲しいところです。 まだ絶対にこれすべきとは言えませんが、これからの流れとしてM1 Macが主流になっていくと思いますし動かないソフトウェアがあってもIssueが上がっていくことで対応されていく速度が上がっていきますので是非ともM1を選んで欲しいなとも思っています。
アバター
こんにちは!すっかり秋らしくなってきましたね。さて、この度は、10/2(土)~10/3(日)にオンラインで開催された PHP カンファレンス 2021 にプラチナスポンサーとして協賛し、5名のメンバーが登壇しました。 今回は、登壇者 5 名からコメントと、オンラインで開催したスポンサーツアーの様子をお届けします! PHP カンファレンス 2021 とは 2021/10/2(土) ~ 2021/10/3(日) の 2 日間にわたって PHP カンファレンス 2021 がオンライン開催されました。BASE はこれまでにも開催されている PHP カンファレンスへの登壇並びにスポンサードをコミュニティ貢献活動として行って参りました。今回はプラチナスポンサーとして当カンファレンスに協賛しています。 プラチナスポンサー一覧に並ぶBASE 登壇者のコメント 粟村 ( @tawamura1224 ) BASE株式会社 Product Dev Division / Data Strategy Section で Group Manager をしている粟村( @tawamura1224 ) です。 スポンサーセッションにて、お問い合わせ対応業務の改善をした話について発表をさせていただきました。 以前 プロダクトチームブログで公開した内容 でして、本記事の実装は Python にはなるのですが、 PHP を主として扱っている会社での改善の取り組みとしてトークさせていただきました。同様の問題における課題解決への参考になれば幸いです。 大津 ( @cocoeyes02 ) BASE 株式会社にて、 Product Dev Division / Service Dev Section に所属している02( @cocoeyes02 )こと大津です。 PHP カンファレンス 2021 では、本編で Composer 2.0 にまつわるトークを、懇親会で登壇にまつわるトークをさせて頂きました。 今回はコアスタッフとスピーカーを兼務していたので、やり切ることができるか不安な気持ちもありましたが、無事終えることができてよかったです! また、過去 PHP カンファレンスで BASE から登壇者した人数が最多だったり、スポンサーツアーという形でも関わったりと色々記念すべきことがあった他、お祭りのような感じもあって楽しかったです! 東口 ( @hgsgtk ) こんにちは。BASE BANK 株式会社 Dev Division にて、 Engineering Manager をしている東口( @hgsgtk )です。 PHP で Web Driver Client を自作する〜己の手でブラウザ操作自動化を完全理解する方法〜 というタイトルで発表いたしました。 LT スピーカーの先頭バッターとして、副将・大将のプレゼンテーションの前説として張り切って参りました。副将の永野さん・大将のがっちゃんさんのトーク流石でした! 古賀 ( @gatchan0807 ) はじめましての人ははじめまして、こんにちは!がっちゃん( @gatchan0807 )です! Product Dev Division / Service Dev Section に所属しているフロントエンドエンジニアです! 自分は アプリケーションのデプロイを高速化するために node_modules に手を出した話 というタイトルで、デプロイの高速化に向けて行った活動とそのハマりどころをシェアしてきました! BASE からは 5 人のメンバーが登壇者として参加していて、発表順がたまたま最後で「大将」の座を任せていただいて、せっかくなのでラスボスっぽく「よくぞここまでたどり着いたな…」って言わせてもらいました 4 分に喋りたい内容をなんとか詰め込んだのでめっちゃ早口で発表しましたが、ギリ発表しきれてよかったです…! 今回 PHP Conference Japan 初参加でしたが、めちゃ楽しかったので次回もぜひ参加できればなって思います! 永野 ( @glassmonekey ) BASE BANK 株式会社 Dev Division にて Software Developer をしている永野( @glassmonekey )です。 リリース頻度を向上させるテクニックであるフィーチャートグルの紹介をしました。 がっちゃんさんのLTと合わせて、昨今の BASE においてはリリース頻度が何かと重要になってきたなという感想に改めてなりました。 実は発表時に PC の充電が尽きるというハプニングがあり運営の方々とコミュニケーションが取れない事態になってしまったのですが、何とかいい感じに着地できて良かったです。運営の皆様聞いてくださった皆様ありがとうございました。 スポンサーツアーも想像よりも多数の方にきていただいて、ありがたい気持ちになりました。カジュアル面談お待ちしてます!! スポンサーツアーの様子 司会(炭田( @tanden ))より BASE 株式会社にて、 Product Dev Division / Service Dev Section でエンジニアリングマネージャー をしている炭田( @tac_tanden )です。 スポンサーツアーで、「 BASE のお問い合わせ対応の裏側!」と題しまして、日頃 BASE でどのようにユーザからのお問い合わせに対応しているのかについて、実際に日々向き合っている永野さん、がっちゃんさん、そして CS 大臣(お問い合わせ対応のリーダー)の植田さんとの座談会形式でお伝えいたしました。 BASE の開発チームでどのようにユーザからの質問に応対しているのか、また社内でどうやってユーザからの声を取り入れてサービス作りに活かしているのか、BASE のサービス開発の雰囲気を少しでもお伝えできていれば幸いです! 加えて、スポンサーツアーの中でお知らせした、会社説明会・カジュアル面談もお待ちしております! 読み込んでいます… 謝辞 協賛・社員のスピーカー参加を通して PHP コミュニティの盛り上がりに貢献することができ、弊社としても大変有意義な時間となりました。 スタッフの方々には業務でお忙しいにも関わらず、多くの時間をカンファレンス準備へ割いていただいたかと思います。この場を借りて御礼申し上げます。 最後に 来年の PHP カンファレンスは 9 月 24 日・25 日に開催される予定だそうです。 来年のPHPカンファレンス2022は9/24,25に開催予定です! (ただし、今後の時勢により不透明なところがあるので開催形態については後ほど告知します。) 来年もよろしくお願いします! https://t.co/BzDgBPitgz #phpcon #phpcon2021 #phpcon2022 — PHPカンファレンス2021 (@phpcon) October 3, 2021 それでは、また来年の PHP カンファレンスでお会いしましょう!
アバター
こんにちは。BASE BANK 株式会社にて、 Engineering Manager をしている東口( @hgsgtk )です。この度 スクラムフェス三河2021 にて 振り返りを積み上げて自分たちのプラクティスとして昇華•体得していくための仕組みと考え方 というテーマで登壇しました。 スクラムフェス三河とは スクラムフェス三河 はスクラムの学びの場として愛知県の三河で開催されたスクラムに関するカンファレンスイベントです。 スクラムフェス三河はスクラムの学びの場です。 スクラムの初心者からエキスパートまで現場を少しでも良くしようと考えている様々な人々が集まります。 この2日間にわたる魅力的なセッションからはスクラムやアジャイル開発に関する多くのヒントを見つけることができ、現場の力をより一層高めることにつながります。 弊チームでは自分たちのチームが目的・理想とするプロダクト開発の姿を目指すため、スクラムのフレームワーク並びにプラクティスを土台としつつ、日々試行錯誤を重ねています。今回は振り返り(レトロスペクティブ)に関して、その試行錯誤の結果をスクラムコミュニティにシェア・意見交換できれば良いなと思いプロポーザルを提出いたしました。 そして、ありがたいことに採択いただきまして今回の登壇の場をいただきました。 提出したプロポーザル 提出したプロポーザルは 「振り返りを積み上げて自分たちのプラクティスとして昇華•体得していくための仕組みと考え方」 という内容です。 https://confengine.com/conferences/scrum-fest-mikawa-2021/proposal/15589 具体的にはブログ下に公開しておりますスライドの冒頭に転載したとおり、 振り返りで得られたチームの気づき・知見がフロー情報で流れていってしまう ことに対する課題感です。 スライド内で言及した課題意識 スクラムを開発スタイルとして取り入れているチームはスクラムのフレームワークを土台として日々のプロダクトづくりをするわけですが、あくまでそれは汎用的なものなので自分達に合わせて最適にチューニングする必要があります。別のプラクティスを採用したり新規に自分達のプラクティスを生み出すこともあるでしょう。 そんな組織ローカルなプラクティスの連なり・一連をいかにストック情報として積み上げることが出来るか、弊チームが実践した内容を発表させていただきました。 発表資料 当日発表した資料はこちらです。 この発表資料は今年の年始に公開したブログ『 振り返りで積み上げた開発プラクティス(2020年総まとめ) 』のその後の姿ともいえます。 https://devblog.thebase.in/entry/bank-practices-2020 当日のセッション中に聴講いただいた方々との Q and A もスライド内に掲載させていただいています。 Q and A(1) Q and A (2) 振り返りを起点としたチームの成長を考える方々の参考になれば幸いです。 おわりに 今回発表の機会を頂いて、Discord上でリアルタイムに聴講いただいている方々とコミュニケーションができ、「たしかにそういう視点もあったな」など気づきが得られてとても有意義な時間でした。 オンラインと現地会場のハイブリッドシステムでしたが、不自由なくオンライン上で参加できました。運営の皆様誠にありがとうございました。
アバター
こんにちは! Process Engineeringグループのセキネです。 入社してからQAという言葉が社内で頻繁に使われることが多いのですが、どうも違和感を感じて今回の内容を社内で共有したところ結構反響があったので紹介したいと思います。 そもそもQAって言葉どういう意味で使っていますか? 実は結構前から社内では人によってQAという言葉の認識が異なっているように思います。 QAという便利な言葉によって認識の齟齬が起きているような話をちょくちょく聞くようになりました。 誤った認識や、認識の齟齬が起こらないようにしていく必要があります! QA(Quality Assurance) 日本語訳的には品質保証 QA ≠ テストです!!! (テストも含んではいますが、イコールではないです) もし、 QA=テストとして使っている人がいるのであれば少し認識を改めていただいた方がよいかもしれません。 品質保証の内容は以下の多岐にわたります。品質をよくするための活動全般をさします。 設計段階での機能仕様の確認・評価 テストケースの設計・テスト実行 (←QAってここだけじゃないよ テスト結果と不具合の分析 テスト自動化の検討 開発プロセスの見直し・改善 などなど、品質向上に繋がる活動 コード書いた人に自信を持ってリリースしてもらうための支援という表現が近い気がします。 QAのイメージとしてはこんな感じ ※以下、解説資料( JSTQBシラバス 1.2.2から抜粋) 品質保証(または単に QA)という用語がテストを意味するために使われることがある。しかし、 品質保証とテストは、関連してはいるが同じではない。 品質保証は、一般的に、品質が適切なレベルに確実に到達するよう、適切なプロセスを遵守することに重点を置く。 プロセスを適切に遂行すると、これらのプロセスで作成される作業成果物は一般的に高い品質を持つ。 これにより、欠陥の混入を防止できる。 さらに、根本原因分析を使用して欠陥の原因の検出と除去を行い、プロセスを改善するために振り返りミーティングでの発見事を適切に利用することが、品質保証の有効性を高めるために重要である。 品質コントロールには、テスト活動を含め、適切なレベルの品質を達成するために役立つさまざまな活動が含まれる。 テスト活動は、ソフトウェア開発またはメンテナンスプロセス全体の一部を構成する。 品質保証では、テストを含むプロセス全体を適切に実行することが必要となるため、適切なテストを支援する。 じゃあなんて言ったらいいの?? QAだと広義なので XXXテスト と伝えましょう テスト内でもフェーズを分けて 単体テスト(Unit Test、Component Test) コードを書いた人がモジュールレベルでテストする 結合テスト(Integration Test)←BASE社内でQAと呼ばれてテストしている大部分な気がしていました 単体テストが完了したプログラムモジュールや外部モジュールを本番環境同様に組み合わせ、正しく動作するか検証をおこなうテストです システムテスト(System Test) 開発側の最終検証工程であり、本番と同じ環境ですべての不具合が解消されていることが理想とされます 受入テスト(Accept Test)  一般的には発注者側がおこなう最終確認テストであり、このテストに合格することで納品・検収が完了します その他上記と合わせて、有用なテストタイプ(上手に組み合わせましょう) 探索的テスト (ad hocテストなどと呼ばれたりもします) ←社内ではあまり意識せずにやっている人が多いかも テスト項目は書かずに実施する 実施者の能力、経験に依存する 開発完了前でもスコープをや目的を絞ると有用 工数削減(事前に大きなバグの洗い出し)  結合テスト、回帰テストで拾えないバグの洗い出し 結合テスト後でも有識者にやってもらうと、想定外の不具合を見つけられるかも? 回帰テスト (リグレッションテスト、デグレチェック) 開発機能とは関係なく、サービス全体が正しく動作しているか確認するテスト セキュリティテスト 負荷テスト まとめ QAという言葉は便利ですがこれらを全て含んだ広義な言葉であり、人によって捉え方や認識が異なることが多いので、何を目的としたテストを実施するのか明確に関係者や、テスト実施をしてもらう方に伝えるようにしましょう。 また機能テスト、シナリオテストってよくやるけど、ここに書かれてないのでは。。。?と思われる方もいるかもです。 これらはテストタイプと呼ばれているもので、どのテストレベルでもでき、QAと近しいぐらいフワッとした表現になります。 フェーズと目的を明確にしましょう! QAエンジニア: 採用情報 / カジュアル面談
アバター
こんにちはProduct Dev Division所属の @cureseven です。8月にリリースしました「 再入荷自動通知 App 」のプロジェクトでは、ペアプロ/モブプロで開発を進めていました。 今回はその体験談を書いてみたいと思います。 なぜオンラインモブプロをやろうと思ったか 「再入荷自動通知 App」プロジェクトでは、バックエンドエンジニアの過半数がフルリモート下で入社して社歴半年以内、初めてのプロジェクト開発参加という状況でした。 また、「再入荷自動通知 App」プロジェクトでは、フレームワーク依存を脱却するため、DDDの実装パターンの導入を試みました。チームで輪読会を行い、DDDについて勉強しながらではあったものの、DDDの実装パターンの導入を実践するのが初めてのメンバーが多く、DDDの観点のすり合わせの意味も込めてペアプロ/モブプロを行いました。 オンラインモブプロの様子 BASEでは新型コロナウイルスの感染拡大を鑑み、現在はWFH(Work From Home)を推奨しており、オンラインでペアプロ/モブプロを実施することになります。 まず、メンバー全員がPhpStormを利用しているため、Code With Meという機能を使ってペアプロ/モブプロを試みました。2021年3月時点ではベータ版であり、コピーペーストができない、反映にラグがあるなどの問題にぶつかったため、zoomで画面共有をしながら実施する方法に落ち着きました。 ただCode With Meでは手元にドライバーの変更を即時反映しつつ別のファイルがどうなっているかなどを確認しながらナビゲータをすることができる、交代が楽などの利点がありました。zoomで実施した際には、細かくcommitしドライバーを交代しました。 Code With Me以外にも、VScodeのLive Shareという拡張機能を用いるなどの方法があります。チームのエディタ勢力を鑑みてツール選定していくと良さそうです。 ペアプロとモブプロの違いはナビゲータが1人か複数人かです。ドメインモデリングとドメインモデルの実装は認識を揃えるためにモブプロで行いました。 実装が進んでいったタイミングで、認識を合わせたい部分の実装はモブプロ、分担して行った方がいいものはペアプロと、使い分けるようになっていきました。 モブプロを通して学んだことや感じたこと プロジェクトを終えて、ペアプロ/モブプロの開発手法がどうだったかを振り返り、以下の内容を話し合いました。 PRレビュー時間の削減とコードの品質の向上 一人でコーディングしている時よりも、考慮漏れが少なくなりました。また、色々な仕様や認識を都度合わせて進めるので、結果的にレビューコストが少なくなりました。プルリクエストを出した段階で既に複数人のチェックが終わっています。プルリクエストでは、細かい指摘が減り、より本質的な議論ができたように感じます。 調査時間の減少 既存機能の仕様や、再現方法、PHPの挙動など、ドライバー自身が知らないことで調査が必要なシーンでは、それぞれが持っている知識や観点を集約し、素早く問題を解決できました。 入社したばかりの新人がモブプロに参加して進めるのは適している 開発着手当時入社して2ヶ月しかたっていなかった私ですが、ペアプロ/モブプロを実施することで助けられた場面がとても多かったです。 エディタの便利機能やデバッグ方法など、実装内容以外の部分でもアドバイスをいただき、今後の開発効率を上げることができました。 また、入社してすぐはわからないことも多く手詰まりになりがちですが、タスクも進捗し、かつ毎日学びを得て満足感の高い日々を過ごしました。 ドライバーはドライバーに専念する 1週間ごとに振り返りの時間を設けて、開発の進め方の改良を重ねていきました。そこで出た議論もご紹介します。 ドライバーは基本的にはナビゲータの言う通りに作業をする人になります。ドライバーに意思が芽生えてしまうと、先々進んでしまいナビゲータが置いていかれそうになります。ドライバーがどうしても意見したい場合は、ドライバーを交代し、ナビゲータとして発言するようにすることを意識するようにしました。 モブプロするべきタスクとしないタスクは分けるべき タスクの難易度に応じてモブプロが適しているタスクとモブプロじゃなくてもよいタスクは必ず存在すると思います。例えば、文言修正や前回のモブプロ/ペアプロで共通認識が取れて、あとは直すだけ!といったような内容のものはソロ(一人)で十分です。私たちは一概にモブプロしていこう!と決めずに、細かくタスクを分割し、以下の時に絞って複数人で作業するようにしました。 ドメインモデルの実装 一旦ソロで作業したが他の観点が欲しくなった時 モブプロ・ペアプロのメリットは多々ありますが、どうしても複数名の時間を奪うことになります。「流れで」「ついでに」モブプロ/ペアプロをするのではなく、「ここからは自分でやれそうなのでやっておきます」のようなコミュニケーションができると、効率よく開発が進められました。 また、モブプロ、ペアプロでタスクに着手する前に、参加者全員で「今日はこれをここまでやる!」という認識を共有しておくことも有効でした。ゴールからそれた内容は別の時間で集まるか、ソロでやってしまうかを判断します。ゴールに集中することで、疲れが見えにくいオンラインでのコミュニケーションを不必要に長引かせないように心がけました。 全員のスケジュール合わせるのが難しい ドメインモデリングや実装方針のすり合わせは、なるべく全員で行いたいものです。特に今回はメンバーが慣れていないDDDの実装パターンを適応したため、参加していないメンバーの解釈が違った場合、手戻りが起こることがありました。 極力作業に集中できるように普段のミーティングは精査しているものの、6名のエンジニアのスケジュールを合わせるのは難しかったです。毎週末の振り返りのタイミングで、来週はここでモブプロしよう、と仮でスケジュールを押さえておくことで、集まりやすくなりました。 1回あたりの作業は連続でやる場合も50分作業+10分休憩を意識してスケジューリングしました。 今後のペアプロ/モブプロとの付き合い方 チームメンバーとの交流の時間にも使えますし、オンボーディングの時や、新しいことに挑戦する時の認識合わせに有効な開発手法だなと感じました。 通話をずっと繋ぎっぱなしは辛い人もいるかと思うので、人に合わせて実施検討することが大切です。また、気軽に相談できる人・環境があることも大切です。今回の場合、プロジェクトの開発に直接入っていないマネージャとの1on1の時間が毎週あるので、客観的な意見を聞くこともできました。モブプロ/ペアプロはあまりない取り組みでしたが、挑戦したことが評価される文化も安心して取り組める要因になりました。 開発に詰まった時の一手段としていつでも実施できるように、普段からSpeak Openly, Be Hopefulでいたいです。
アバター
はじめまして!フロントエンドエンジニアのがっちゃん( @gatchan0807 )です。 9月7日にBASE主催で「 BASE Engineer 座談会 〜BASEの若手エンジニアがそれぞれの今と未来を語る!〜 」というイベントを実施したのですが、その中のLTパートで発表した「 Changelogを読んで自分のエンジニアキャリアを作る 」でお話しきれなかった部分があったので、そこも含めてお話した内容を記事にまとめなおしました! 当日ご参加いただいてLTを聞かれた方も、そうでない方も、ぜひご覧ください! 「Changelog」と「エンジニアキャリア」がどう繋がるのか 早速この記事でお伝えしたいことから書いてしまうのですが、 Changelogは最新機能だけでなく、未来のビジョンも含めたプロダクトの情報の宝庫で、それらをうまく使えばエンジニアキャリアづくりに活かせる! ということを記事を読んでいただいた皆さまには持って帰っていただければなと思っております。 ここからは、私がどういう経緯でChangelogを読むようになったか、Changelogを読むことでどんなメリットがあるのかについて、具体的に紹介していきます。 なぜChangelogを読み始めたのか ことの始まりはdependabotくんの対応から BASEにはショップオーナー向け管理画面で利用する社内用コンポーネントライブラリ「BBQ」があります。 今年2月頭頃、そのリポジトリに dependabot という、依存しているライブラリのバージョンアップ情報を調べ、アップデートするPull Requestを自動的に作成してくれるシステムを導入したところから話は始まります。 私は今年1月に入社したのですが、オンボーディング研修やたくさんのオリエンテーション、BASE内のコードリーディングを行っていた1ヶ月がちょうど過ぎた頃に、上記のような話題が上がり、実際に導入されることになりました。 ちょうどBBQのコードを読むきっかけにもなりそうと思って、空いた時間で参加させてもらうことになりました。 実際の dependabot の対応自体は難しいものではなく、自動的にOpenされるPull Requestを確認し、必要に応じて動作確認を行うだけです。 dependabot が設定した頻度で package.json / yarn.lock (Node.js関係のライブラリの依存関係情報が載っているファイル)をチェックする アップデートが公開されているライブラリがあった場合に、そのライブラリのバージョンを変更した( $ npm upgrade が実行された)Pull Requestが自動的にOpenされる 「マージ待ってるよ~」状態になるので、それをチェックして問題なければマージする 実際にOpenされるPull Requestの例 上記対応の3つ目の「マージして問題ないかのチェック」の中で、BBQのコードリーディングついでに 該当ライブラリの利用箇所の確認 と 該当ライブラリのアップデート内容が書かれたChangelog確認 を行っていたのですが、BBQのコードに詳しくなれるのはもちろんのこと、副次的に BBQ以外にも知見を得れていること に気が付きました。 dependabot で見つかるもの以外にも世には色んなChangelogがある 少し話は変わって、それまではプロダクトのアップデート情報やChangelogを見ても、流し見するだけで真剣には読んでいなかったのですが、 dependabot の対応でライブラリのChangelogを読むようになってから自然と世の中のプロダクトのアップデート情報が気になるようになりました。 いくつか読んだ中で印象的だったものとしては下記2つで、これらは社内ドキュメントツールにざっくり翻訳してまとめた記事を上げてみたりもしました。 https://hasura.io/blog/announcing-hasura-graphql-engine-2-0/ DBにかぶせたらGraphQLのエンドポイント生やせる君のHasuraに大型アップデートが入るアナウンス記事 複数DB接続できたり、使えるデータソースがPostgreSQL以外も対応するようになった https://developer.chrome.com/blog/new-in-devtools-90/ Chrome 90でのバージョンアップ内容がまとまった記事 flexboxがDevTools内でデバッグしやすくなったり、CWVが見れる機能がついた やってみてわかったChangelogを読むメリット LTの中では下記3つでまとめていましたが、時間の都合上1つ目の「変更点から、プロダクト・ライブラリがどう変化しようとしているのか気づくきっかけができる」についてしか触れることができませんでした。 しかし、個人的には2つ目、3つ目のメリットも非常に大きいと感じているため、こちらの記事でご紹介できればなと思います。 Changelogをフックにプロダクトを深ぼる リリースされているフレームワークやライブラリ、アプリケーションなどはすでに一定プロダクトとして成立する形で出来上がってる構造になるので、どこから見ていいのかわからなくなることも多いです。 しかしながら、Changelogには多くの場合、アップデートされた内容とともにアップデートされた理由と関連するユースケースの情報(こういう使い方を想定して機能を追加しましたみたいなこと)が書かれているので、アップデート対象そのものの知識をつけるのに効率が良いというメリットがあります。 これが こうなる これは2つ目の「アップデートからその対象の全体的な構造・関連性について知るきっかけができる」というメリットに大きく関わる部分で、Changelogからアプリケーションやライブラリの構造を深ぼっていく糸口になりますし、どういった機能が関連しあっているのかに気づくきっかけにもなり得ます。 また同時に、Changelogに書かれている想定ユースケースから「そんな機能があったのか!」と気づく3つ目のメリットにも繋がります。 実際に、ChromeのChangelogを読んだ際にDevToolsの知らなかった機能をいくつも発見して驚いた記憶があります。(意外と深い階層に便利機能があって気づいてないことが多々ある) 技術力向上にはアウトプットが重要と言うけども また、Changelogを読むだけでなく、(Changelogが英語記事の場合に)ざっくり翻訳して記事にまとめるという活動は自然とアクティブラーニングになって良い勉強になることを実感したので、合わせて紹介しておきます。 私は、学生時代に「知識の定着のためにはインプットだけではなく、アウトプットまで行うことが大事」と聞き、技術力の向上のために下記のような形で学習を行ってきていました。 とにかく勉強会に参加して知らない単語を知る(とりあえず情報に体当たりして、出てくるキーワードのインデックスだけ貼る) ↓ そこで知った単語をあとでググって調べてみたり、ちょこっとデモ環境を作って肌感覚を得る ↓ ☆(機会があれば)実践投入して更に自分の血肉にしたり、一定ラインを超えてきた辺りで手を動かして得たしくじりや知識を登壇・発表側に回る このやり方は☆の発表まで持っていくところが肝要で、 登壇・発表側に回ることによって 、自分の知識が整理されるし、理解があやふやなところを再度調べ直して知識の深さが高まったりするので 得られるものが沢山ありました 。 しかしながら、 常に身の回りに登壇・発表する場があるわけでもない のと、 発表する場の期待値のコントロールであったり表現方法を考えたりしないといけなかったりで意外と1回のカロリーが高い という問題をずっと感じていました。 アウトプットの方法としては、登壇・発表まで行かずとも経験した内容をブログ記事にする学習方法なども選択肢にありますが、上述したとおり Changelog記事自体が構造的にとっつきやすいようになっているため、初級者を脱してさらに技術力に磨きをかけたい!という人にとって効率よく知識を吸収できる という点で効率的だと感じたので、個人的におすすめできるなと思いました! 最後に… ここまでお読みいただきありがとうございました! 私がChangelogを読むようになった経緯と、実際にやってみて感じたメリットをつらつらと書かせていただきました。 読んでいただいた中で、もし少しでもここは良さそうだなと思うポイントがありましたら、ぜひ一度実践してみていただけると嬉しいです! これからも価値あるプロダクトを作り続けられるようにするためにも、自分の技術力アップのためにも、色々なChangelogを読んで行ければなと思います! それに、みんなでChangelogを読んでみよう会みたいなのを開催できたらなぁ…とか妄想したりもしています…! 最後に、イベントでお話したときの資料( https://speakerdeck.com/gatchan0807/changelogwodu-ntezi-fen-falseensiniakiyariawozuo-ru )と当日のアーカイブ動画( https://www.youtube.com/watch?v=mslmwMd2_rM )のURLを記載しておきます! イベントでは座談会という形で社内の雰囲気なども感じていただけるかと思いますので、よければご覧ください! 参照 世界のJavaScriptを読もう @ 2014 What's New In DevTools (Chrome 90) Announcing Hasura GraphQL Engine 2.0!
アバター
BASE BANKでエンジニアをしている @budougumi0617 です。 先日行われたNew Relic User Group Vol.0でGoでNew Relic APMを活用するためのOSSを紹介するLT発表をさせていただきました。 New Relic User Group Vol.0 New Relic User Group(NRUG)はNew Relicを活用するユーザーの集いです。NRUGは ヌルグ と読むとのことです。 コロナ禍後初開催となるVol.0は2021年9月15日にオンライン形式で次のコンテンツが行われました。 New Relic One 最新機能紹介 Nerd Life Talk (LT) ネットワーキング 関連ハッシュタグは #NRUG です。 https://twitter.com/hashtag/NRUG BASE BANKを含めBASEグループでも2020年9月よりNew Relic Oneを利用した開発・運用体制を敷いており、今回LT発表の機会をいただくことができました。 発表資料 私の当日の発表資料は次のスライドになります。 私の発表では、BASE BANKのGoアプリケーションでNew Relic Oneを活用するために私が開発し、OSSとして公開している3つのツールについて紹介しました。 当日紹介したOSS スライド中で紹介したOSSは次の3点です。 https://github.com/budougumi0617/nrseg https://github.com/marketplace/actions/action-newrelic-segment-lint https://github.com/budougumi0617/nrzap github.com/budougumi0617/nrseg nrseg コマンドはGoアプリケーションの関数・メソッドにNew Relic APMでSpanを生成するためのNew Relicライブラリの呼び出しを自動挿入するツールです。 GoアプリケーションへNew Relic APMを導入する際、Goでは実行時間を計測したい関数・メソッド全てで次のようなライブラリの呼び出しをする必要があります。 defer txn.StartSegment( "mySegmentName" ).End() nrseg コマンドは指定ディレクトリ配下の全てのGoのコードに含まれる関数やメソッド 1 にスパンを生成するために必要なコードを自動挿入します。 import ( "context" "fmt" "net/http" + + "github.com/newrelic/go-agent/v3/newrelic" ) func (f *FooBar) SampleMethod(ctx context.Context) { + defer newrelic.FromContext(ctx).StartSegment("foo_bar_sample_method").End() fmt.Println("end function") } func SampleFunc(ctx context.Context) { + defer newrelic.FromContext(ctx).StartSegment("sample_func").End() fmt.Println("end function") } func SampleHandler(w http.ResponseWriter, req *http.Request) { + defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() fmt.Fprintf(w, "Hello, %q", req.URL.Path) } // nrseg:ignore ignoreコメントをしておけば無視します。 func IgnoreHandler(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Hello, %q", req.URL.Path) } 既存アプリケーションの関数やメソッド全てに上記のような呼び出しを手動で挿入するのは手間なのでこちらを利用しております。 内部実装的にはASTから関数名を取得したり関数の位置を読み取って先頭行にコードを挿入したりと楽しい静的解析になっております。 action-newrelic-segment-lint actions action-newrelic-segment-lint はNew RelicのSpan対応漏れがある関数・メソッドがPull Request(PR)に含まれていたときに警告コメントをするGitHub Actionsです。 既存コードに対しては nrseg コマンドでNew Relicの対応ができます。 しかし、我々のGoアプリケーションは機能追加が活発に行われており、毎日関数やメソッドが追加されています。 新しく追加された関数やメソッドがNew Relicの対応をしていなかった時警告コメントをしてくれます。 PR上でNew Relic対応漏れの関数にコメント github.com/budougumi0617/nrzap nrzap はNew Relicの Trace ID や Span ID を uber-go/zap のログに埋め込むためのヘルパーです。 New Relicには分散トレースとログを紐付けるLogs in contextという機能があります。 しかし、New Relicの公式Goライブラリは logrus というロガーパッケージしか現在はLogs in contextに対応していません。 弊社のGoアプリケーションで利用しているロガーパッケージは uber-go/zap です。 Logs in contextに必要なNew Relicの Trace ID や Span ID を uber-go/zap のログに埋め込むためのヘルパーが nrzap です。 GetNrMetadataFields 関数を使うと context.Context オブジェクトにNew Relic関連情報が含まれていれば uber-go/zap のログに適切なJSONキーでNew Relicの Trace ID などの情報を含めることができます。 func ExampleHandler(w http.ResponseWriter, r *http.Request) { logger, _ := zap.NewProduction() defer logger.Sync() // nrgorillaなどでcontextから*newrelic.Transactionが取れる前提 + nrfs := nrzap.GetNrMetadataFields(r.Context()) logger.Info("failed to fetch URL", nrfs...) } 会に参加して 会ではNew Relicのコンサルタントの皆様よりPixieなどのNew Relic Oneの最新機能を紹介していただきました。 また、他社の皆様には開発組織へのNew Relic導入活動のコツを教えていただいたり、本番ダッシュボードを実際に拝見させていただけました。 懇親会では紹介したOSSを実際に利用してくださっている方と話しかけていただき、とてもモチベーションが高まりました。 どのOSSもまだまだバギーなところがあるので時間を見つけて改善を重ねておこうと思います。 終わりに NRUGの次回開催は12月を予定されているようです。今回とても有意義な時間だったのでぜひまた参加する予定です。 New Relicの最新機能、現場の生の声を聞ける貴重な会なので現在活用中の方、導入を検討されている方とも12月にお会いできればと思います。 正確には引数に context.Context オブジェクトまたは *http.Request オブジェクトを持つ関数/メソッドです。 ↩
アバター
BASE株式会社 Owners Experience Frontend チームのパンダ( @Panda_Program )です。 BASE では社内用コンポーネントライブラリ「BBQ」を使ってフロントエンドの開発をしています。 BBQ は Vue2 + Storybook v5 で作成されていましたが、 TypeScript Compiler API と社内のフロントエンドエンジニアの有志たちのおかげで Storybook のバージョンを最新の v6.3 にする対応が完了しました。 以前執筆した 「Vue2 + Storybook v5 のコンポーネントを v6 向けに書き換える」 という記事で、Storybook v5 から v6 の書き方である Component Story Format(CSF) への変更手順を確認しました。 この記事では、TypeScript Compiler API を使って前回の記事で紹介した書き換えをスクリプトで自動実行した話を紹介します。 前置き 率直な意見を書くと、TypeScript Compiler API(以下、Compiler API)はとっつきにくいです。なぜなら、初めて出会う単語や概念がたくさんあるからです。さらに、それをコードで操作するので慣れるまでは書きにくいし読みにくいです。 記事の途中で出てくる単語の意味が分からなくても、途中でコードの input と output の例を多めに掲載するので文章は読み飛ばしてもらっても大丈夫です。 TypeScript Compiler APIとは何か TypeScript Compiler API(以下、Compiler API) とは、TypeScript の Compiler を操作するための API です。 つまり、TS/JS のコードを解析したり、TS を JS に変換したり、コードの文法エラーを検出するといったコンパイラの機能を利用するための API です。 Compiler API の機能は多岐に渡りますが、今回利用したのは大きく分けて AST(Abstract Syntax Tree。抽象構文木) の走査(AST という木構造のデータを深さ優先探索で順番に見て回ること)、ファクトリ関数によるコード生成、visitor によるコードの部分的書き換えの3点です。 Compiler API で実現できること 百聞は一見に如かずです。CSF への書き換えに用いたスクリプト(以下、「書き換えスクリプト」または「置換スクリプト」)を使って、 「Vue2 + Storybook v5 のコンポーネントを v6 向けに書き換える」 で紹介した storiesOf で記述しているコンポーネントを置換してみましょう。 こちらが変換元のコンポーネントです。 // bbq/stories/v5/elements/button-group.stories.js import { action } from '@storybook/addon-actions' import { number, select, text, withKnobs } from '@storybook/addon-knobs' import { storiesOf } from '@storybook/vue' import { withInfo } from 'storybook-addon-vue-info' import { ButtonGroup } from '../../elements/buttonGroup/button-group.vue' import README from '../../elements/buttonGroup/README.md' import { devices } from '../../values/Devices' // Storybook コンポーネント名 const buttonStories = storiesOf( 'Elements/ButtonGroup' , module) buttonStories // addon-knob .addDecorator(withKnobs) // addon-info .addDecorator(withInfo) .add( 'ButtonGroup' , () => { return { components: { ButtonGroup } , // Vue Template template: ` <div : class = "'theme-'+device" > <p> <h2>デフォルト</h2> <div>アイコン+ラベル</div> <bbq-button-group :tag= "tag" :items= "iconsAndLabels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>アイコン</div> <bbq-button-group :tag= "tag" :items= "icons" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>ラベル</div> <bbq-button-group :tag= "tag" :items= "labels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> </p> <p> <h2>カスタムUI</h2> <bbq-button-group :tag= "tag" :items= "['a','b', 'c', 'd']" @change= "({index}) => change(index)" :selected= "selected" :width= "width" > <template v-slot= "{items, change}" > <button v- for = "(item, index) in items" @click= "change(index)" > {{ item }} : {{ index }} </button> </template> </bbq-button-group> </p> </div> `, // Data data() { return { device: select(`device`, devices, 'pc' ), selected: number( 'selected' , 0), width: select( 'width' , [ '' , 'full' ] ), } } , // Props props: { tag: { default : text( 'tag' , 'ul' ) } , } , // Computed computed: { icons() { return [{ icon: 'list' } , { icon: 'grid' }] } , labels() { return [{ label: 'ドラッグで並び替え' } , { label: '数値で並び替え' }] } , iconsAndLabels() { return [ { icon: 'list' , label: 'リストで並び替え' } , { icon: 'grid' , label: 'グリッドで並び替え' } , { icon: 'attentionCircle' , label: '念で並び替え' } , ] } , } , // methods methods: { change: action( 'change' ), } , } } , // Parameters { notes: README, } ) 置換スクリプトを実行することで、このコンポーネントが以下のように書き変わります。 // bbq/stories/v6/elements/button-group.stories.js import { action } from '@storybook/addon-actions' import { number, select, withKnobs } from '@storybook/addon-knobs' import { ButtonGroup } from '../../elements/buttonGroup/button-group.vue' import { devices } from '../../values/Devices' import README from './README.md' export default { title: "V6/Elements/ButtonGroup" , component: ButtonGroup, parameters: { notes: { README } , docs: { extractComponentDescription: ((_, { notes } ) => notes?.README) } } , argTypes: { device: { options: devices, control: { type: "select" } } , width: { options: [ "" , "full" ] , control: { type: "select" } } } } ; const Template = (args, { argTypes } ) => ( { components: { ButtonGroup } , props: Object .keys(argTypes), template: ` <div : class = "'theme-'+device" > <p> <h2>デフォルト</h2> <div>アイコン+ラベル</div> <bbq-button-group :tag= "tag" :items= "iconsAndLabels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>アイコン</div> <bbq-button-group :tag= "tag" :items= "icons" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>ラベル</div> <bbq-button-group :tag= "tag" :items= "labels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> </p> <p> <h2>カスタムUI</h2> <bbq-button-group :tag= "tag" :items= "['a','b', 'c', 'd']" @change= "({index}) => change(index)" :selected= "selected" :width= "width" > <template v-slot= "{items, change}" > <button v- for = "(item, index) in items" @click= "change(index)" > {{ item }} : {{ index }} </button> </template> </bbq-button-group> </p> </div> `, methods: { change: action( "change" ), } } ); export const Default = Template.bind( {} ) Default.args = { device: select(`device`, devices, "pc" ), selected: 0, width: select( "width" , [ "" , "full" ] ), selected: 0, tag: "ul" } ; プロパティの重複や改行の不足もありますが、あえてありのまま記述しています。 Compiler API を使ったスクリプトを実行することでコードを劇的に書き換えられます。 これでCompiler API の威力を実感していただけたかと思います。 Storybook の書き換えに Compiler API を使った動機 さて、書き換えスクリプトの詳細に入る前に Compiler API で書き換え対応した理由を説明します。 想定読者はエンジニアなので早めに技術の話に入りたいのですが、Compiler API での書き換え実行に至った背景は普遍的なはずなので共有する価値があると考えています。 以下、Storybook の書き換えに Compiler API を使った理由を箇条書きで記述します。大きな目的は「Storybook の v5 から v6 へのバージョンアップを楽に実行すること」ですが、Compiler API を採用したのは以下のような背景があります。 書き換え作業にまとまった時間と人が確保できないこと( 人海戦術ができない ) BBQ というコンポーネントライブラリの保守・メンテは数人の有志者(以下、メンテナー)がスキマ時間に行なっている 体制として、例えば20%ルールなどの負債解消の時間を設けていない Storybook のバージョンアップは喫緊の課題でもないのでプロジェクト化されない ファイル数が多いこと BBQ の Storybook のコンポーネントは60ファイル BBQ の他にも Storybook を利用しているレポジトリがあるため、置換スクリプトを書くと再利用できる 簡潔に書くと、手作業による書き換え作業コストはファイル数に応じて比例しますが、スクリプトを適用できるのであればコストのほとんどはスクリプト作成だけで済むため、トータルコストが抑えられると判断して Compiler API での書き換えを採用しました。 またメンテナーに書き換えタスクを割り振って手作業で対応する場合は、新しい書き方を決定したり、書き換え方がわからないケースの調査結果の共有やコードレビューといったコミュニケーションコストが発生します。さらにそれらは往々にして同期的なものです。 このようなコミュニケーションコストを最小限にするために、書き換え方や気をつけるポイントを自分でドキュメント化して他の方に共有しました。これによりコミュニケーションを非同期化することができ、各自のタスクに集中できます。 書き換えの作業コストのイメージを可視化すると以下のようになります。 一人がスクリプトを書くほうが全体的にコストが少ない 書き換えスクリプトを使うと、最も簡単なケースでは、あるファイルを置換した後 Storybook で動作確認を完了するまで1分もかかりませんでした。 置換スクリプトで除外したケース ただし、スクリプトで細かいケースまで全部対応するのは実装コストが高くなるだけです。先程紹介した重複プロパティを削除する、などちょっとしたところであればあえて自動化しないという局所的な判断もしています。 また、1つのファイル内で forEach を記述して複数の storiesOf を実行しているようなパターンもスクリプトでの対象外としています。 [ 'blue' , 'red' , 'green' ] .forEach((color) => { const component = storiesOf(...) } ) このケースは、コードを書くより人の手で書き換えた方が早いと判断してメンテナーに割り振って対応してもらいました。 結果、60ファイル中置換で対応できたのが40ファイル、人の手で書き換えたのが15件ほどでした (15件のうち半分は TSX で書かれており、また残り5件は元から CSF でした)。 書き換えのスケジュール スケジュールは以下のように進みました。 1,2週間目: スキマ時間でCompiler API、Storybook v6 対応調査、書き換えスクリプト作成と調整 3週間目: スクリプトを実行して一括置換し、自分を含むメンテナに微調整タスクを割り振り 4週間目: PRレビュー依頼、コメント対応 5週目: 動作確認、Storybook v6 化リリース 最初の2週間は自分一人で動き、3週間目からはメンテナーの有志たちの力を借りました。量としては一人でも対応できるものだったのですが、v6 化にあたって知識の俗人化を避けるためにレビューも含めみんなで対応しました。 以上、置換スクリプトで「何ができるか」「なぜ Compiler API を使ったのか」「どのような背景があったのか」はお伝えできたかと思います。 Compiler API を使ったファイル書き換えの流れ 早速置換スクリプトの中身に入っていきたいところですが、 まずは Compiler API を使った処理の大まかな流れをお伝えします。 Compiler API でファイルを書き換える処理自体は簡単です。大まかな流れは以下のようなものです。 ファイルをメモリに保持する AST を解析する 特定の箇所を書き換える 書き換えた値をメモリからファイルに出力する これをコードで表現するためにミニプログラムを作成します。 ここでは説明のために「読み込んだファイルの import 文 だけ抜き出す」というシンプルな処理だけをしています。 import * as ts from "typescript" import * as fs from 'fs' // 1. ファイルを入力してメモリに保持する const code = fs.readFileSync ( './input.js' , 'utf8' ) const outputFilename = './output.js' const sourceFile = ts.createSourceFile ( outputFilename , code , ts.ScriptTarget.Latest ) const imports: string [] = [] // 2. AST を解析する function printRecursive ( node: ts. Node , sourceFile: ts.SourceFile ) { const text = node.getText ( sourceFile ) // node の解析結果を出力する(後述) const syntaxKind = ts.SyntaxKind [ node.kind ] const textWithSyntaxKind = ` ${ syntaxKind } : ${ text } ` console .log ( textWithSyntaxKind ) // 3. 特定の箇所を書き換える if ( ts.isImportDeclaration ( node )) { imports.push ( text ) } node.forEachChild ( child => { printRecursive ( child , sourceFile ) } ) } printRecursive ( sourceFile , sourceFile ) // 4. 書き換えた値をメモリからファイルに出力する fs.writeFileSync ( outputFilename , imports.join ( '\n' )) このコード例では文字列を抜き出しているため、Compiler API をご存知の方は少し変だと感じるかもしれません。なぜなら、eslint の plugin などでは「ASTを解析し、特定のパターンのASTを書き換えた後、printer.printNode で文字列に変換する」処理が一般的だからです。 ただ、storiesOf の書き方と CSF では構造が大いに異なるため、置換スクリプトではコードの場所に応じて使い分けています。 例えば、import 文では上記のように配列と文字列で扱い、 export default { title: 'SomeComponent' } のようなオブジェクトの箇所では AST を操作する形にしています。 Compiler API を使ったことがない方が大半だと思うので、今回は2つのうち簡単な方をコード例に挙げました。 ミニプログラムを実行する さて、このミニプログラムを実行してみましょう。入力と実行結果は以下のようになります。 入力 // input.js import * as path from "path" import * as fs from 'fs' const exists = fs.existsSync(path.resolve() + '/input.js' ) console.log(exists); 実行結果 // output.js import * as path from "path" import * as fs from 'fs' また、プログラムの途中に仕込んだ console.log(textWithSyntaxKind) の出力は以下のようになります。 このままでは読みにくいと思うので、今回抜き出しの対象とした import 文があるところにコメントを追加します。 # より右は出力結果はではなく追加したコメントです。 SourceFile: import * as path from "path" # ソースファイル全体 import * as fs from 'fs' const exists = fs.existsSync(path.resolve() + '/input.js') console.log(exists); ImportDeclaration: import * as path from "path" # import 文 ImportClause: * as path NamespaceImport: * as path Identifier: path StringLiteral: "path" ImportDeclaration: import * as fs from 'fs' # import 文 ImportClause: * as fs NamespaceImport: * as fs Identifier: fs StringLiteral: 'fs' FirstStatement: const exists = fs.existsSync(path.resolve() + '/input.js') VariableDeclarationList: const exists = fs.existsSync(path.resolve() + '/input.js') VariableDeclaration: exists = fs.existsSync(path.resolve() + '/input.js') Identifier: exists CallExpression: fs.existsSync(path.resolve() + '/input.js') PropertyAccessExpression: fs.existsSync Identifier: fs Identifier: existsSync BinaryExpression: path.resolve() + '/input.js' CallExpression: path.resolve() PropertyAccessExpression: path.resolve Identifier: path Identifier: resolve PlusToken: + StringLiteral: '/input.js' ExpressionStatement: console.log(exists); CallExpression: console.log(exists) PropertyAccessExpression: console.log Identifier: console Identifier: log Identifier: exists EndOfFileToken: 深さ優先探索で AST の node を一つずつ辿っている様子が分かります。 例えば、 console.log(exists) という箇所は以下のように解析されています。 CallExpression: console.log(exists) PropertyAccessExpression: console.log Identifier: console Identifier: log Identifier: exists 深さ優先探索でツリーの探索順の説明 また、この出力結果を見ると、なぜ ts.isImportDeclaration(node) というメソッドで import 文を判定できるかわかると思います。 それは、 import * as path from "path" が ImportDeclaration であると判定されるからですね。 これで大まかな動きはイメージしてもらえるかなと思います。 Compiler API を使った置換スクリプトの中身を紹介します では、実際に置換スクリプトの中身を紹介しようと思います。 ただし、書き捨てのコードであるため書きやすさ重視でコード量が多く、極力整理はしたもののあまり洗練されていないコードになっています。 そこで、置換スクリプトと Compiler API のエッセンスが伝わるような箇所を2つ抜き出して紹介しようと思います。 import 文の書き換え 1つ目は、元ファイルから import 関連の文字列を抜き出して少し書き換える処理です。 まずは変換前と変換後を紹介します。 // 変換前 import { action } from '@storybook/addon-actions' import { number , select , text , withKnobs } from '@storybook/addon-knobs' import { storiesOf } from '@storybook/vue' import { withInfo } from 'storybook-addon-vue-info' import { ButtonGroup } from '../../elements/buttonGroup/button-group.vue' import README from '../../elements/buttonGroup/README.md' import { devices } from '../../values/Devices' // 変換後 import { action } from '@storybook/addon-actions' import { number , select , withKnobs } from '@storybook/addon-knobs' import { ButtonGroup } from '../../elements/buttonGroup/button-group.vue' import { devices } from '../../values/Devices' import README from './README.md' 変換ポイントは以下の通りです。 @storybook/addon-knobs から import している text を削除 storiesOf 、 withInfo の import を削除 README の import パスを書き換える これを実現するためのコードは以下の通りです。説明のために処理を先程のミニプログラムに組み込みます。 import * as ts from "typescript" import * as fs from 'fs' const code = fs.readFileSync ( './input.js' , 'utf8' ) const outputFilename = './output.js' const sourceFile = ts.createSourceFile ( outputFilename , code , ts.ScriptTarget.Latest ) const imports: string [] = [] let hasReadme = false function printRecursive ( node: ts. Node , sourceFile: ts.SourceFile ) { const text = node.getText ( sourceFile ) if ( ts.isImportDeclaration ( node )) { imports.push ( text ) if ( text.includes ( 'README' )) { hasReadme = true } } node.forEachChild ( child => { printRecursive ( child , sourceFile ) } ) } printRecursive ( sourceFile , sourceFile ) // import 文を書き換える function getFilteredImport ( imports: string [] , hasReadme: boolean ) : string { // filter で不要な import を削除し、 // concat で必要な import を追加する let filtered: string [] = imports .filter ( item => ! item.includes ( 'storiesOf' )) .filter ( item => ! item.includes ( 'storybook-addon-vue-info' )) // README が存在したらパスを書き換える if ( hasReadme ) { filtered = filtered .filter ( item => ! item.includes ( 'README' )) .concat ( `import README from './README.md'` ) } return filtered.join ( "\n" ) .replace ( 'withKnobs,' , '' ) .replace ( "import { withKnobs } from '@storybook/addon-knobs'\n" , '' ) // knob は `preview.js`で読み込むので不要 .replace ( 'text,' , '' ) // knob の text を削除 } const filteredImport = getFilteredImport ( imports , hasReadme ) // ファイルとして出力する const newFile = ` ${ filteredImport } ` fs.writeFileSync ( outputFilename , newFile ) 変数 imports は string の配列なので、 concat と filter を使って追加と削除をしています。 この処理は、元ファイルの import 文をほぼ流用できるのでこのような愚直な実装にしています。次は、元ファイルの情報を使い回せない場合の処理方法を紹介します。 export default の追加 次に、AST を生成する方法を紹介します。CSF ではコンポーネントのメタ情報をオブジェクトとして default export します。これは storiesOf の書き方とは全く似ていません。 // storiesOf const buttonStories = storiesOf( 'Elements/ButtonGroup' , module) // v6 の書き方 export default { title: "V6/Elements/ButtonGroup" , component: ButtonGroup , } 何か流用できるものはないかと探しながら前後を見比べると、ストーリー名だけは利用できることがわかります。 変更前のファイルからストーリー名を抜き出してV6という接頭辞をつけてストーリー名を差別化し、コンポーネント名はストーリー名から抜き出せれば OK です。 これをコードで実現していきます。前出のコードに追記していきます。 import * as ts from "typescript" import * as fs from 'fs' const code = fs.readFileSync ( './input.js' , 'utf8' ) const outputFilename = './output.js' const sourceFile = ts.createSourceFile ( outputFilename , code , ts.ScriptTarget.Latest ) const imports: string [] = [] let hasReadme = false // 以下を追加 let prevText = "" let title = '' let component = '' // SB コンポーネント名 const isPrevStoriesOf = ( text: string ) => text === 'Identifier: storiesOf' const includesStorybook = ( text: string ) => text.includes ( 'storybook' ) function printRecursive ( node: ts. Node , sourceFile: ts.SourceFile ) { const text = node.getText ( sourceFile ) const syntaxKind = ts.SyntaxKind [ node.kind ] const textWithSyntaxKind = ` ${ syntaxKind } : ${ text } ` // import 文 if ( ts.isImportDeclaration ( node )) { ... } // Storybook コンポーネント名を抜き出す(後述) if ( isPrevStoriesOf ( prevText ) && ! includesStorybook ( text )) { // クォーテーションを削除 title = text.substring ( 1 , text.length - 1 ) } // 一つ前の node を文字列でメモする prevText = textWithSyntaxKind node.forEachChild ( child => { printRecursive ( child , sourceFile ) } ) } printRecursive ( sourceFile , sourceFile ) // import 文を書き換える function getFilteredImport ( imports: string [] , hasReadme: boolean ) : string { ... } const filteredImport = getFilteredImport ( imports , hasReadme ) // コンポーネント名を抜き出す component = title.split ( '/' ) .pop () || '' if ( ! component ) { throw new Error ( 'Component 名を取得できません。' ) } // 以下の2つの関数を組み合わせて export default の AST を作成する function getExportAssignmentProperties ( title: string , component: string ) { const properties: ts.ObjectLiteralElementLike [] = [ ts.factory.createPropertyAssignment ( ts.factory.createIdentifier ( "title" ), ts.factory.createStringLiteral ( title ) ) ] properties.push ( ts.factory.createPropertyAssignment ( ts.factory.createIdentifier ( "component" ), ts.factory.createIdentifier ( component ) ) ) return properties } function createExportAssignmentAst ( node: ts.ObjectLiteralElementLike [] ) { return ts.factory.createExportAssignment ( undefined , undefined , undefined , ts.factory.createObjectLiteralExpression ( node , true ) ) } // 元の Storybook コンポーネント名と区別するために V6 でディレクトリを分ける const storybookComponentTitle = `V6/ ${ title } ` const properties = getExportAssignmentProperties ( storybookComponentTitle , component ) const exportAssignmentAst = createExportAssignmentAst ( properties ) // printer const printer = ts.createPrinter () const printNode = ( node: ts. Node ) => printer.printNode ( ts.EmitHint.Unspecified , node , ts.createSourceFile ( '' , '' , ts.ScriptTarget.Latest ), ) const exportAssignment = printNode ( exportAssignmentAst ) // ファイルとして出力する const newFile = ` ${ filteredImport } ${ exportAssignment.normalize( 'NFC' ) } ` fs.writeFileSync ( outputFilename , newFile ) import 文の処理より複雑なので、ポイントを解説していきます。 直前に出てきた要素を prevText にメモしておく コンポーネント名だけを抜き出すために、 printRecursive という再帰関数の中で prevText という変数に直前の node の名前を保存するようにしました。 以下のコードを解析するとその下のような結果になるため、直前の node の情報が必要なのです(関係のある箇所だけ抜き出しています)。 const buttonStories = storiesOf( 'Elements/ButtonGroup' , module) CallExpression: storiesOf('Elements/ButtonGroup', module) Identifier: storiesOf StringLiteral: 'Elements/ButtonGroup' もしストレートに実装するなら「 storiesOf の第一引数を抜き出す」という方法で良いのですが、少し試してもうまく取得できなかったので直前の要素を参照する形にしています。 今回抜き出したいのはコンポーネント名なので StringLiteral: 'Elements/ButtonGroup' だけ使えれば良いです。 ただし、StringLiteral はただの文字列であるため import 文の from '@storybook/vue' のように他の場所でも出てくるので、 isStringLiteral だけでは判定条件が不足してしまいます。 このため、 storiesOf 関数の第一引数は必ずストーリー名の string であり、AST の node を辿る順番は深さ優先探索であることを利用しようと考え、試行錯誤した結果以下のような記述方法になりました。 const isPrevStoriesOf = ( text: string ) => text === 'Identifier: storiesOf' const includesStorybook = ( text: string ) => text.includes ( 'storybook' ) if ( isPrevStoriesOf ( prevText ) && ! includesStorybook ( text )) { // クォーテーションを削除 title = text.substring ( 1 , text.length - 1 ) } isPrevStoriesOf は関数名の通り、直前が storiesOf であるか判定します。 includesStorybook はその node が以下のような import 文ではないことを判定しています。 import { storiesOf } from '@storybook/vue' 以上が、 storiesOf('Elements/ButtonGroup', module) からコンポーネント名を抜き出す方法です。 factory.createExportAssignment でオブジェクトを default export する AST を作成する 次は default export するオブジェクトの作成です。 文字列操作でオブジェクトを作成するのはとても複雑です。このため、Compiler API の factory 関数を使いますが、factory 関数はたくさんあるため使い方を一つずつ知るのは大変です。 ここで裏技を使います。作成したいオブジェクトは以下のようなものだとわかっていましたね。 export default { title: "V6/Elements/ButtonGroup" , component: ButtonGroup , } このコードから factory 関数を生成する手段があるのです。 TypeScript AST Viewer を使えば関数の自動生成を実現できます。 サイトにアクセスし、左上の入力欄に作成したいコードを書き込めば左下に factory 関数が生成されます。 TypeScript AST Viewer の画面 今回は以下のようなコードが出力されています。 [ factory.createExportAssignment ( undefined , undefined , undefined , factory.createObjectLiteralExpression ( [ factory.createPropertyAssignment ( factory.createIdentifier ( "title" ), factory.createStringLiteral ( "V6/Elements/ButtonGroup" ) ), factory.createPropertyAssignment ( factory.createIdentifier ( "component" ), factory.createIdentifier ( "ButtonGroup" ) ) ] , true ) ) ] ; このコードを一度見てしまえば、変更すべき箇所は明白ですね。 プロパティはそのままでいいので、ストーリー名 V6/Elements/ButtonGroup とコンポーネント名 ButtonGroup を変数化すればいいと分かります。 先述のコードで読みにくい関数が出てきたと思いますが、これはストーリー名とコンポーネント名を変数にする関数を作り、factory 関数をラップしているだけです。 function getExportAssignmentProperties ( title: string , component: string ) { const properties: ts.ObjectLiteralElementLike [] = [ ts.factory.createPropertyAssignment ( ts.factory.createIdentifier ( "title" ), ts.factory.createStringLiteral ( title ) ) ] properties.push ( ts.factory.createPropertyAssignment ( ts.factory.createIdentifier ( "component" ), ts.factory.createIdentifier ( component ) ) ) return properties } function createExportAssignmentAst ( node: ts.ObjectLiteralElementLike [] ) { return ts.factory.createExportAssignment ( undefined , undefined , undefined , ts.factory.createObjectLiteralExpression ( node , true ) ) } createExportAssignmentAst の返り値である AST を printer で string に変換すれば、オブジェクトの default export が出力できます。 // printer。AST を string に変換する const printer = ts.createPrinter () const printNode = ( node: ts. Node ) => printer.printNode ( ts.EmitHint.Unspecified , node , ts.createSourceFile ( '' , '' , ts.ScriptTarget.Latest ), ) const exportAssignment = printNode ( exportAssignmentAst ) 処理をまとめて書くと、以下のような単純なものだと理解できます。 const exportDefault = printNode ( createExportAssignmentAst ( getExportAssignmentProperties ( 'V6/Elements/ButtonGroup' , 'ButtonGroup' ) ) ) 実際は argTypes や parameters をオブジェクトに追加するのでもう少し複雑になっていますが、基本的な考え方は以上の通りです。 なお、以下のようにオブジェクトをネストさせる書き方は今回は解説しません。詳しく知りたい方は 実際のコードをご覧ください。 export default { // ... argTypes: { width: { options: [ "" , "full" ] , control: { type : "select" } } } } ; 記述量は多いものの結局はプロパティと値の組み合わせなので、組み合わせ方さえわかってしまえば作成自体はそれほど難しくはないです。 終わりに ここまで読んでくださってありがとうございました。コードを追うだけでも大変だったと思います。 実際に使った変換スクリプトは GitHub で公開しています。 コードコメントもそのまま残しているので生々しいと思います。 このスクリプトの目的は「BASE 社内の Storybook + Vue のコンポーネントを CSF に書き換える」ものなので、あまり一般化していません。 そのため、そのままスクリプトを流用しようとすると齟齬が出るかと思いますが、処理の流れを追ったり実装で詰まったところの対処法の参考にはしていただけるかなと思います。 TypeScript Compiler API の記事はほとんどなく、あったとしても記事内容が古かったり書き換えの全体像が見えにくかったり、再帰関数の中で部分的に書き換えるようなものが多かったです。 このため、公開しているコードは手探りの中で実装したコードであり、より良い書き方があるだろうということは書き添えておきます。ただ、拡張できるように綺麗に完璧に書くことは当初の目的から除外していたこと、全部 CSF に書き換わったので目的を達成して役目を終えたので、このようなコードもありかなと思います。 この書き換えスクリプトは私が入社して1~2ヶ月目の時で、アサインされたタスクをこなしつつ空いた時間で作成したものでした。試しに小さいスクリプトを書いて、これを応用すれば労力をかけずに Storybook のコンポーネントを置換できるとわかったタイミングでマネージャーに相談して、そのまま Go サインを出してもらったため完遂できたことだと思います。 転職してまだ日が浅かった上に、自分は慣れていない少しレベルの高い手段で、解決できるかわからない課題にチャレンジさせて貰えた環境とマネージャーの決断に感謝しています。 BBQの週次定例でメンテナーのみんなに置換スクリプトを披露し、「便利ですね」といってもらったことで方向が間違っていないことを確認でき、しっかりやり切ろうというモチベーションに繋がりました。 TypeScript Compiler API はうまく使うと人海戦術を避けられる強力な武器になるため、興味のある方は課題解決の手段としてぜひ試してみてください。 参考 TypeScript AST Viewer AST Viewer です。コードを貼り付ければ AST やそのコードを生成するためのコードが表示されます Compiler API (TypeScript) TypeScript Compiler API の使い方がコードとともにわかりやすく紹介されているページです。 TypeScriptのcompiler APIをいじる TypeScript CompilerAPI - 創出の落書帳 - TypeScript Compiler API を解説した書籍 TypeScript Compiler APIを使って型を中心に実装を自動生成する
アバター
Customer Product Dev Groupの北川です。 直近では主にショッピングアプリ「BASE」のiOSアプリの開発をしています。 私たちモバイルアプリエンジニアの所属するNative Application Teamでは、 『チームで育てるAndroidアプリ設計』 の社内読書会を行いました。 『チームで育てるAndroidアプリ設計』について peaks.cc この書籍は2021年3月30日にPEAKSより出版されたもので、公式のHPによると対象読者として以下を挙げています。 アプリ開発をこれから始める方 チーム開発をよりよくしたい方、技術的なチームビルディングに興味がある方 アーキテクチャの選択、設計の維持に難しさを感じている方 新規開発/継続開発などチーム開発に関わる方 引用: https://peaks.cc/books/architecture_with_team ショッピングアプリ「BASE」はiOSアプリは2013年、Androidアプリは2015年にリリース以降、機能を追加してきました。アプリ開発を取り巻く技術トレンドも変化していく中で、設計パターンも数世代が混在しているという現状でした。 チームメンバーも増えていく中で、こうした秩序の綻びはアプリ開発のスピードの障害になると感じており、アーキテクチャの選択や改善をチームでどう行取り組んでいくか、という課題解決のヒントとして本書をチームで読むことにしました。 読書会の進め方 チームでTandemというコミュニケーションツールを導入しており 、こちらを使ってリモートで開催しました。各章ずつ読み進め、参加者は事前に読んでおいて、当日は読んでいて気になった部分について議論する形式で進めました。 読書会の記録は1つのGoogleドキュメントで管理し、話したいトピックを事前にコメントしたり、議論で出た観点、実際に取り入れられそうなアクションを記録したりできるようにしました。 読書会によって得られたもの 本書には既存のプロジェクトであっても取り組みやすい実践的なプラクティスが多く、読書会での議論をきっかけに実際に取り入れた改善がいくつかありました(前半が新規プロジェクト向け、後半が大規模プロジェクト向けという構成になっています)。 採用しているアーキテクチャを明文化する 序盤の3章を読み、最初に取り組んだのはアーキテクチャの明文化です。 どのような書き方や設計が推奨されるのかを改めて整理しました。指針が明文化されていることで、新しく参加するメンバー、レビューをする既存のメンバー双方の負担を下げることができます。 例えば、プロジェクトで採用しているライブラリについての説明として、RxJavaはKotlin Coroutineに移行中のため新たに利用するのは非推奨であることなどを記載しています。そのような異なる書き方が混在している状態と対処については、第3章「アーキテクチャの浸透と改善」で詳しく解説されています。 GitHub Issueでアーキテクチャについて検討する また、取り入れたい設計パターンや、技術課題についてGitHub Issueで議論するようにしました。 これまではSlackなどで議論が完結することが多々ありましたが、技術的な意思決定の経緯をGitHub Issueの形で残しておくと、その時にいないメンバー含めて後から意思決定を追いやすくなり、チームの齟齬を減らすことができます。 読書会を進めている期間中にもKotlin Coroutineへの移行、Jetpack Composeの導入、マルチモジュールの導入などいくつかの技術課題について検討を進めていたのですが、GitHub Issueに議論を集約することができました。 終わりに このように平易なものではありますが、読書会をきっかけにチームの開発フローの改善することができました。 『チームで育てるAndroidアプリ設計』は、チームでアプリ開発に取り組む際に最初に検討すべきことや設計を取り入れる具体的な進め方などがまとまっています。 実際にプロジェクトに取り入れた改善はもちろん、普段のミーティングの場では見えづらい、チームの課題や改善点について議論できたことが社内読書会を開催して最もよかったことの一つだと感じました。 今後もプロダクトを継続して成長させられる、よりよいチーム開発を目指してさまざまな取り組みにチャレンジしたいと思います。 また、BASEではiOSアプリエンジニアを絶賛募集中です。 少しでも興味を持っていただけましたら、お気軽にご連絡いただけると嬉しいです。 iOS: 採用情報 / カジュアル面談
アバター
8/21(土)に開催された ISUCON 11 予選に BASE から4チームが参加しました。 参加者の感想をお届けします! チーム「牡蠣に当たる時の効果音→カキーン」 BASEバックエンドチームの @cureseven です。このチームはエンジニアコミュニティのメンバーから募ってできたチームであり、BASEからは私のみ参加しています。 ISUCON11では31位 92336点でした。ISUCONに向けて 16回の練習 を重ねてきましたが、予選敗退という結果に終わり非常に悔しいです。 やったことの詳細は 個人ブログ をご覧ください。 BASEはPHPのプロダクトですが、 弊チームはGoで出場し、ISUCON練習のためにGoを書いてきました。BASE BANKではGoを利用しており、2週間に1度 Go Code Reading Party を開催しているので、参加してISUCON練習で困ったことを聞いたりしました。 去年も同じメンバーで出て本戦出場したのですが、今年は当時よりもずいぶん早く、去年よりもいろんな施策を打てるようになりました。ISUCON練習を通して成長を感じられたので有意義な大会でした。 またISUCONを通してDB周りの知識が身についたので、業務でも実行計画はどうか?より軽い実装はどのようなものか?有効なindexが貼れているか?などを意識して仕様検討・実装できるようになりました。 来年こそ本戦出場したいです。また、3年連続アプリケーション担当として参加しているので、今度はインフラ担当もやってみたいかもと思っています。 ISUCON12の準備は来月あたりから始めます。 個人ブログ @cureseven: 「ISUCON11に参戦しギリ敗退しました!!!!!!!!!悔しい!!!!!!!!!!!!!!!!!!!!!」 チーム「example.com」 川口 CTOです。 去年から前職の先輩である横山さんと、前職の超優秀だけど現在無職のエンジニアと出場しています。 案の定他メンバーのほうがスコアが良くて悲しい限りです、CTOたる威厳がない。 雑にdeployの仕組みを作ったりtrendのキャッシュとかを組み込んだりとかをしていました。 limit 1とか入れてたけど別修正とかも合わせて入れていて整合性チェックでfailして諦めたりしたので、もっと細かく手数を作れるようにならないとなと反省しきりです。 次こそは本戦、という気持ちで1年いますが来年もまた無準備になりそうな気がしています。 運営の皆様お疲れさまです。 横山 BASE所属の横山です。去年に引き続きCTOとニートエンジニアと3人で参戦しました。 初速だけはよく開始1時間ぐらいまでは10位内に張り付いていたのですがそこからの伸びがなく最終スコアは43,684点で90位でした。 自分は主にMySQLまわりを触り、1台をレプリカにして参照クエリをレプリカに投げる、みたいなことをやっていました。 レプリカ化とかはじめてやってみましたができてよかったです。 スコア的な伸びはなかったんですが・・・。 朝から仕様を頭に叩き込むのは辛かったですが、終わってみればあっという間でした。 また参戦します。 チーム「Speed of Sound」 松雪(@applepine1125) BASE BANK 所属の budougumi0617 と applepine1125 で「Speed of Sound」というチームを組みエントリーしました。 2人とも今回が初参加だったので、競技終了時に0ptにならない&100位以内という目標を掲げ、当日は無事? 最終スコア47,995点で79位をマークしました。 スロークエリを発見してindexを貼ったりbulk insertを活用できれば初参加でも100位以内に食い込めるんだなと思いつつ、それ以上を目指そうと思ったときにはもっといろいろな武器を持っていないと上位入賞は遠いな・・・と悔しい限りでした。 来年も是非参加したいと思います。運営の方々、準備から開催までありがとうございました!本戦出場される方も頑張ってください! 清水(@budougumi0617) 今回初の個人スポンサー、初の参加をしました。 以前から「いつか出るぞ出るぞ…」と思っていたので予選参加600チームに潜り込めてよかったです。 事前準備として環境構築スクリプトなどを作成して臨みました。最初の各種計測の設定は準備のおかけでスムーズにできてよかったです。 しかし、その後の実際のパフォーマンス改善はなかなか難しかったです。 「計測結果が悪いけど直し方がわからない」「たぶん良くなるからいじってみる」というような振る舞いが多くなってしまい、計測データに基づいた原因分析、修正対応ができませんでした。 分析力・対応力をもっと身につけて次回は本戦に出場したいと思います。 環境構築はCloudFormationで一瞬終わりましたし、ベンチマークは気軽に何回も実行できて快適な競技環境でした。ありがとうございました。 詳細な当日の様子は、それぞれの個人ブログの記事に記載しています。ぜひそちらもご覧ください! 個人ブログ @applepine1125: 「ISUCON11予選に参加した(47995点で予選落ち)」 @budougumi0617: 「ISUCON11予選に参加した(最終スコア47,995点 79位) #isucon」 チーム「賢者のつどい」 BASE のフロントエンドチームの @Panda_Program です。 大学時代の友人である 0daryo が声をかけてくれたので「賢者のつどい」という2人チームでエントリーしました。 私は BASE でフロントエンドエンジニアをしているため、普段の業務ではサーバーのコードを書いたりインフラを触ることがありません。 前職では業務で PHP も書いていたものの、これまで特にパフォーマンス向上の業務はしたことがなかったのでいい機会だと思ったのがエントリーの理由です。 ISUCON は初参加で右も左もわからなかったところ、幸い社内の Slack に ISUCON に挑戦する人たちのチャンネルがあったので参加してみました。 エントリーの期限や参加後に対応が必要なことのアナウンスやリマインド、また他の方もそれぞれ予選に向けて事前準備をしているという発言を見ることで ISUCON 参加の実感が湧いてきたり、自分も準備をしなければというやる気をもらいました。 予選当日の様子は 個人ブログ に記載しているのでそちらをご覧いただければと思います。 最終スコアは16680(598組中249位)と BASE の他のチームの戦績に比べて結果は芳しくなかったです。 ただ、事前準備では普段業務で触らないところが勉強できたこと、パフォーマンス向上の鉄則である「推測より計測」を実際に体験できたため参加した甲斐がありました。 昼休憩で PC から離れた時に肩があまりにも凝っていたことから、集中と緊張のあまり体が強張っていたのだなと後から気づき、なんだか大会らしいなと感じたのはいい思い出です。 ISUCON の結果は事前準備でほとんど決まるのだなと思いました。来年はしっかり準備して予選に臨む所存です。 個人ブログ @Panda_Program: 「ISUCON11に参加しました」 最後に ISUCON 運営の方々、とても有意義な大会を開いていただきありがとうございました。 また来年も参加したいと思います!
アバター
BASE開発担当役員の藤川です。 BASEのYoutube動画で、EC系SIerからBASEに転職してきてくれて4年半を経過したタイミングでの宮村さんにBASEでの働き方についてインタビューをしました。今回は前編と後編の2本立てになります。 前編動画:スタートアップの成長過程の風景について www.youtube.com 2017年に転職してきて、今では技術責任者と呼ばれる開発プロジェクトの設計や技術責任を牽引する役割を担ってもらっていて、SIerからスタートアップに転職したリアルな話を聞いてみました。 動画の中で思い出のプロジェクトとして携帯キャリア決済の実装についてお話いただきました。当時のBASEはクレジットカード決済、後払い、銀行振込等は実装されていて、そこに携帯キャリア決済が実装されました。 キャリア決済を実装したことがある人ならわかると思いますが、相応に決済フローが複雑で情報もわかりにくい上に3キャリア毎に挙動の癖があります。実際リリースした後に、想定しないタイムアウトエラーが発生したり、フロー制御にも気をつけなくてはいけなかったりしました。しかし、先方の実装が見えるわけでもないし、相手は超大企業ということもあり、開発ドキュメントの理解力が問われます。 また、SIerや受託出身の方は開発ドキュメントの作成や管理に慣れているので、当時、まだまだスタートアップ初期の内輪な状態から人数も増え、いわゆる組織として変化させていく中で、ドキュメント整備を通じて開発ナレッジの整理を自然としてくれていたというのが印象的でした。 よくスタートアップとSIerを比較した時にドキュメントが整備されていなかったり、いろんなことが曖昧であることが転職するメリット・デメリットとして挙げられることがあります。 とりわけ初期のスタートアップでは、明日をも知らぬフェーズで仕事をしていく必要があるわけで、実装面、ドキュメント面共々、5年後10年後を見据えた活動をしているよりは、今を生き抜くことが優先されます。 しかし、ある程度サービスが成功し安定成長を開始してからは、人材採用は未来を作るための最重要課題となり、常に実装力、ドメイン知識に関する初心者の方が毎月入社してくる流れにシフトします。それ故に、先輩の方々からドメイン知識や実装に関する情報が適切に共有されていく流れは重要です。 今回はそういうところでの心構えなども聞いてみました。 大手企業に新卒で入った人たちは、各社のドメイン知識を吸収し成長をお膳立てされるプロセスが当たり前に存在しています。入社後数年は昇給が横並びの大企業も少なくないですが、その間は教育期間として考えられていることが理由だと思います。 しかし実際に企業が急成長するタイミングを目の当たりにすると、そういうことが当たり前に存在するのではなく意図して作っていくものだということを再認識されられます。それがスタートアップが大きくなっていく時に必要とされるスキルです。 後編動画:リモートだからこそ重視されるアジャイル設計 後半の動画では、BASEの現状の開発状況についての話を聞きました。 www.youtube.com その中でも昨今のリモートワーク下での新入社員の適切なオンボーディングは非常に重要な課題です。まだ心理的安全性も適切に構築できてない状態で、オンラインコミュニケーションを強制されるコロナ禍において、入社いただく方に如何に馴染んでもらって活躍いただくかを常に注視しています。その中で設計や開発プロセスに業界標準のノウハウを取り入れているという話をしました。 開発はGitHubフローを活用していますが、設計面でもドメイン駆動設計、アジャイル開発などと言ったノウハウを我々も導入しています。組織としては、まだまだ試行錯誤中ですがプロジェクト毎にチャレンジをしている状況です。 このような勉強会も随時行っています。 devblog.thebase.in スモールチームだったショップが成長し続けることにしっかりついていく 群雄割拠のEC支援事業の市場でBASE社が存在し続ける理由としては、個人やスモールチームにフォーカスした機能を提供することでビジネスが成立するんだよという、それまで言語化されていなかった独特な領域に市場を見出したことだと思います。そういった初心者にも使いやすいサービスはこれまでいくらでもあったにも関わらず、そこに特化した意思決定を続けてきたことがBASEが成長した鍵だと考えます。 一方で、BASEで成長したショップさんがBASEの機能では物足りなくなって別のサービスに移動されてしまうということは避けなくてはなりません。EC市場の中で、「お店を作ってみたい」という入り口を押さえているからこそ、成長したショップさんがずっとBASEを使い続けていただくというのは最重要課題として、常に存在し続けています。 BASEのプロダクトデザインとしては初心者の方がすぐにお店が作れますというシンプル性を担保しつつ、成長したショップさんが使い続けることができる機能やスケーラビリティを提供し続けるという部分で、ある種の二律背反的な要素も存在します。宮村さんがインタビューで話しているように、実は以前の方が限られたリソースの中で割り切ったデザインをしていたのに対して、最近は、大きくなったショップさんの組織構造等も踏まえて、しっかり全体を考えていくことが求められており、それらを担保し続けられる人材が不可欠です。 もちろん、それは当社の開発経験の中で培っていけばよいので、これまでのBASEがどのようにサービスを成長させてきたかをしっかりトランスファーしながらになりますが、そのようなドメイン知識を通じてBASEの作り手として活躍する人をもっと増やしていかないといけません。 エンジニアの働き方はBASEの成長を生み出すことと連動する 今回、改めて宮村さんと話したことを反芻してみると、中途採用である個々人がこのチームに投入されるノウハウというのが、BASEというサービスの成長の中でうまく溶け込んでいるということを再認識させられました。 我々はまだまだ発展途上のチームであり、一人ひとりの経験が新しい視界を生み出すことに繋がります。開発のプロセスにおけるアジャイルの導入も決してCTOのビジョンで先導したのではなく、それの先導者がいて、フォロワーとしてついていく人たち、それぞれの前向きな姿勢があってこそできていることです。CTOがやったのは、そういう人達の考え方を聞き、開発の進め方としてオーソライズしたということです。 一人ひとりが有名な人とか、そういう人たちを優先する採用はしていなくて、このチームを全体で良くしていくんだという期待が持てる人しか採用していないので、是非、一緒に成長していきたいという人がいたら、是非、お話させてほしいです。
アバター
はじめに こんにちは! BASE 株式会社 Customer Product Dev で Android エンジニアをしている小林です。 ショッピングアプリ「BASE」のAndroid版アプリの開発を担当しています。 最近、フォロー中タブ追加というアプリのトップ画面を大きく変えるリリースを行いました。 その際、RecyclerViewの実装でPagingライブラリの3.0.0を導入してページング処理を実装してみたのでその話をしていきたいと思います。 Pagingライブラリとは? データの追加読み込みやメモリに保持するデータ量の管理をやりやすくしてくれるライブラリです。 2021年9月時点ではPagingライブラリの最新バージョンは3.0.1になっています。 https://developer.android.com/topic/libraries/architecture/paging/v3-overview 2021年5月にメジャーバージョンが上がり、v2の時とは色々と実装方法が変わっています。 Paging 3ライブラリを使うと、以下の機能がサポートされます RecyclerView Adapterと組み合わせることで、自動で次のデータを取得してくれるページング機能 Kotlin コルーチンやFlowを使ってのデータの取得や反映 リフレッシュやローディング中、エラー時のUIの切り替え 今のアプリ内のPaging処理の現状と問題点 今のアプリはScrollListenerを使って独自にページングを実装しています。 スクロール時に何件まで商品を表示したかをチェックして、ある程度読み込んだら次のページを読み込み始める形です。 この実装はJavaで書かれていてKotlin コルーチンとの相性が悪く、今のアプリの構成に合わない実装になっていました。 Pagingを使おうとした理由 上記の問題点を解消するために、現状のページング処理のKotlin化&アーキテクチャ適応も検討しました。 しかし、以下のことを考慮した結果、今回実装する新規画面にPaging 3ライブラリを導入することにしました。 機能的にPaging 3ライブラリが優れていること 特に、次のデータを自動で取得してくれるページング機能はコード量を大きく減らせると期待できる 実装 公式ドキュメントを参考に、ViewModelにFlowを定義して、それをFragmentで購読する形で実装していきました。 https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data?hl=ja#pagingdata-stream ViewModel MVVMにおけるViewModelです。AACのViewModelを継承しています。 Pagerクラスを生成して、remoteMediatorを設定します。 class ItemsViewModel( //... ) :ViewModel() { // 商品情報を取得するRepository @Inject lateinit var itemsRepository: ItemsRepository // お気に入り情報を取得するRepository @Inject lateinit var favoriteItemsAllRepository: FavoriteItemsRepository // APIで取得したリストの情報をキャッシュするDatabase @Inject lateinit var itemDatabase: ItemDatabase // 1画面に表示される商品数 // pagingの先読み商品数の設定のために定義している private val viewIngItemNum = 15 @OptIn (ExperimentalPagingApi :: class ) val flow = Pager( config = PagingConfig(pageSize = viewIngItemNum, initialLoadSize = viewIngItemNum), remoteMediator = ItemRemoteMediator( itemDatabase, itemsRepository, favoriteItemsRepository ) ) { ItemDatabase.ItemDao().pagingSource() }.flow.map { pagingData -> pagingData.map { it.convert() } }.cachedIn(viewModelScope) //... } RemoteMediator Repository経由でAPIから商品情報を取得しています。 後述するお気に入り情報の更新のために、ローカルキャッシュとしてRoomのテーブルクラスに格納しています。 色々書いていますが、やっていることは以下のとおりです APIから読み込むページ数を決める リフレッシュならRoomのテーブルを削除する APIから取得する 取得した情報をRoomのテーブルに格納する Roomのテーブルに情報が更新されたことをViewModelのFlowが検知して、Adapterに反映されます。 @OptIn (ExperimentalPagingApi :: class ) class ItemRemoteMediator( private val database: ItemDatabase, private val itemsRepository: ItemsRepository, private val favoriteItemsRepository: FavoriteItemsRepository ) : RemoteMediator< Int , ItemEntity>() { override suspend fun load(loadType: LoadType, state: PagingState< Int ,ItemEntity>): MediatorResult { val page: Int = // 略 リフレッシュ時には1,追加読み込み時はnextKeys等の読み込むページを指定している try { // itemRepository.itemsはRxJavaのSingleで返ってくるので、await()を使ってKotlin コルーチンに変換しています。 val result = itemsRepository.items(page).subscribeOn(Schedulers.io()).await().items // APIからの値をローカルDBに格納できるクラスへ変換する val entities = result.convertToEntity() entities.forEach { // 取得した商品がお気に入りしているかどうかを取得して設定する it.isFav = favoriteItemsRepository.isFavorite(it.itemId) } val endOfPaginationReached = result.isEmpty() database.withTransaction { if (loadType === LoadType.REFRESH) { // PullToRefresh等で、最初から読み込む場合はRoomのテーブルの情報を消す database.remoteKeysDao().clearRemoteKeys() database.itemDao().clearAll() } val prevKey = if (page == 1 ) null else page - 1 val nextKey = if (endOfPaginationReached) null else page + 1 val keys = entities.map { entity -> ItemRemoteKeys(repoId = entity.itemId, prevKey = prevKey, nextKey = nextKey) } // Roomのテーブルに取得した商品情報とページ数を格納する database.remoteKeysDao().insertAll(keys) database.itemDao().insertAll(entities) } return MediatorResult.Success(endOfPaginationReached) } catch (e: RetrofitException) { return MediatorResult. Error (e) } } } Fragment ViewModelで設定したflowで、変更された値をAdapterへ通知します。 class ItemsFragment : Fragment(){ lifecycleScope.launch { binding.viewModel?.flow?.collectLatest { pagingData -> //... // PagingDataAdapterのメソッド adapter.submitData(pagingData) } } } Adapter PagingDataAdapterを継承していること以外は通常のRecyclerView.Adapterと実装方法は変わらないです。 ViewModel class ItemsAdapter( //... diffCallback: DiffUtil.ItemCallback<ItemViewData> ) : PagingDataAdapter<ItemViewData, ItemsAdapter.ViewHolder>(diffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int ): ViewHolder { return ViewHolder(ViewItemBinding.inflate(LayoutInflater.from(parent.context)), listener) } override fun onBindViewHolder(holder: ViewHolder, position: Int ) { val item = getItem(position) holder.setData(item, position) } class ViewHolder( //... ) : RecyclerView.ViewHolder(binding.root) { fun setData(data: ItemViewData?, index: Int ) { //... } } } // Paging 3ライブラリが、Adapter内の商品のチェックを行うための処理 object ItemComposer : DiffUtil.ItemCallback<ItemViewData>() { override fun areItemsTheSame(oldItem: ItemViewData, newItem: ItemViewData): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: ItemViewData, newItem: ItemViewData): Boolean { return oldItem == newItem } } 実装してみての詰まったところ アプリでは、ユーザが商品をお気に入りする機能があります。 他の画面でお気に入りが変更された場合、フォロー中画面の商品リストに反映する必要があります。 しかし、Paging 3ライブラリでFlowを使って商品リストの反映を行っていたため、すでに取得/反映している商品の情報をAdapterやViewModelでは更新することができませんでした。 これはPaging 3ライブラリの仕様となっています。 一度リストの情報をPagingDataとして取得した後は、個々の情報の中身を取得したり変更することができません。 現在更新を可能にするissueが上がっている状態です。 https://issuetracker.google.com/issues/160232968 解決策 Roomを使ったローカルキャッシュを実装して解決をしました。 Paging 3ライブラリは中間にRoomのテーブルを挟んで実装した場合、Roomのテーブルを唯一の情報源として動作します https://developer.android.com/topic/libraries/architecture/paging/v3-network-db?hl=ja#basic-usage RemoteMediator 実装は、ページング データをネットワークからデータベースに読み込む際には有用ですが、データを直接 UI に読み込むためには使用できません。代わりに、アプリはデータベースを信頼できる唯一の情報源として使用します。つまり、アプリはデータベース内にキャッシュされたデータのみを表示します。PagingSource 実装(Room によって生成されたものなど)は、データベースから UI へのキャッシュ データの読み込みを処理します。 これにより、お気に入りが変更された場合に、API通信を行うこと無く情報の反映ができるようになりました。 実装では、RxBusなどで他画面からのお気に入りが変更されたことの通知を受け、changeFavを実行してテーブルの更新を行っています。 class ItemsViewModel{ // APIで取得したリストの情報をキャッシュするDatabase @Inject lateinit var itemDatabase: ItemDatabase val flow = // 略 // map = お気に入り状態を変更した商品ID=Stringと、変更後状態=Booleanが格納されている private fun changeFav(map: Map < String , Boolean >) { viewModelScope.launch { itemDatabase.withTransaction { val table = itemDatabase.itemDao() // 商品IDでキャッシュの情報を取得する table.selectByItemId(map.keys.toList()) .forEach { val beforeFav = it.isFav it.isFav = map[it.itemId] ?: false if (it.isFav != beforeFav) { // リスト表示用のキャッシュに、お気に入り変更を更新している table.update(it) } } } } } まとめ Paging 3ライブラリでのリスト表示処理の実装と、他画面からの通知を受け取ったお気に入り変更処理の実装について書いてみました。 Paging 3ライブラリはリスト表示についてどんなシチュエーションでもカバーできるようになっていて、一見難しいです。 でも、使いこなせるようになれば自分で実装するよりも手間もコード量も少なく済むのでとても便利なライブラリだと思います。 おわりに 最後になりますが、BASEではAndroid/iOSのエンジニアを募集しています。 カジュアル面談もありますので、興味のある方は気軽にご連絡ください! Android: 採用情報 / カジュアル面談 iOS: 採用情報 / カジュアル面談
アバター
こんにちは!New Owners Dev GroupにてEngineering Managerをしている植田です。 皆様の会社・組織ではSlackを導入・活用されていますか? 私は2021年4月にBASEへ入社したのですが、色々と驚いた点のうちの一つに 「Slackを全社をあげて活用しまくってる!」 というものがあります。 BASEではただのチャットツールのみならず、あらゆる場面でSlackを活用し業務を効率化していることに大きな驚きを覚えました。エンジニアは当然のことながら、非エンジニアもSlackをフル活用しているので今回はそれを大公開してしまおうと思います! ※実際社内で利用しているSlackのキャプチャを掲載しているため、画像にはマスクを施しています BASEでのSlack活用例 今回、記事を書くにあたり社内の関係者に声をかけ、どんな場面でSlackを活用しているかヒアリングをかけたところ、ありとあらゆる場面でSlackを活用していることがわかり、図にまとめてみました。 ご覧の通り、各組織でSlackを活用していることがおわかりいただけると思います。では、それぞれの代表的な活用事例を細かくみていきましょう。 ①コラボレーションツールとの連携 BASEでは、社内Wikiツール、タスク管理ツール、カレンダーツール等、いわゆるコラボレーションツールとSlackとの連携がなされています。特に、WikiツールであるKibelaとの連携は強力で、新しい記事が作成されたり、記事にコメントが追加されると通知されるチャンネルがあるため、記事の見逃しがなくコラボレーションを後押ししています。 Kibela通知の例 また、Googleカレンダーに登録している「機能リリースカレンダー」とも連携しているため、リリース情報を見逃すこともありません。 リリース通知の例 その他の活用事例 「Asana」との連携で、自身に割り当てられたタスクやステータスの変わったタスクが通知されます(Asana=タスク管理ツール)。 「Redmine」との連携で、SQLレビューチケットの通知がされます。※BASEではSQLのレビューをRedmineのチケットで管理しています。 ②エンジニアリングの中でのSlack活用例 現状Slackなくして、エンジニアリングは回らないといっても過言ではないほどエンジニアリングとSlackは密接に紐付いています。 GitHubと連携することで、プルリク通知やIssue通知をSlackで受け取っています。 プルリク通知の例 また、ChatOpsを利用したデプロイといった連携もしておりとても便利です。 デプロイ連携の例 Looker を利用したデータモニタリング(データ通知)も仕組み化されています。SlackとLookerを連携し、デイリーで主要KPI実績をSlackチャンネルに流しています。こうすることで全社員が数値、KPIに対する意識を高めています。KPI情報ということでキャプチャを載せられないのが残念です。 その他の活用事例 「Mackerel」や「Sentry」との連携で、メトリクス情報を通知 「Geekbot」を利用し日報の登録 CRONの正常稼働を通知 ③総務・人事・労務におけるSlack活用例 BASEでは総務・人事・労務といったバックオフィスチームもSlackを活用していますよ! 例えば、健康診断の日程調整といったフローも、Slackのワークフローで完結されています。入社して地味にすごいなと思った一つです。 健康診断ワークフローの例 また、労務関連のお問い合わせはやはりワークフロー化されています 労務ワークフローの例 こちら の記事で紹介されている通り、BASEでは自社サービス利用サポートの福利厚生があります。「BASE」で購入した商品を申請し、共有用の回答欄に商品URLを入力すると、該当のチャンネルでどんな商品を購入したかが通知されるようになっています。他の社員が購入した商品やショップが分かり、素敵なショップを知るきっかけになります。 利用補助の投稿例 その他のSlack活用事例(一部) 慶弔・特別休暇の申請をワークフローで受け付け、労務メンバーのみのチャンネルに通知 人事発令情報を必要関係者へ通知 Twitterなどで都やメディアからのコロナ関連のニュースがあったら該当のチャンネルに通知 Twitterなどで気象・地震情報が発信された場合に該当のチャンネルに通知、震度5以上の地震発生で必要関係者へ通知 コロナ関連や、天災情報をSlackに通知するというところに総務、労務らしさが出ていますよね。 ④採用におけるSlack活用例 採用チームでは各種採用サービスを利用していますので、Slackと連携し採用サービス側での通知をSlackへ連携し、スピーディな採用活動を実現しています。 採用サービス通知の例 その他の活用事例 ワークフローにて、お知り合い紹介フォームを構築(リファラル採用フォーム) Slackのワークフローにて、採用会食申請フォームを構築 BASEテックブログ のTweetをSlackチャンネルへ通知 ⑤法務におけるSlack活用例 続いては法務でのSlack活用です。 いわゆる法務チェックはワークフロー化されています。 法務チェック申請フォーム 法務では クラウドサイン を利用しており、締結が完了するとSlackへ通知されるようになっています クラウドサイン通知連携 こちらは後述する「リアク字チャンネラーアプリ」を使った活用例です。 Slackチャンネル上のメッセージに対して「法務check」リアクションをすることで、任意のチャンネルへメッセージを連携することができます。 法務チェックのリアク字チャンネラーアプリ使用例 ⑥CSにおけるSlack活用例 Zendeskと連携し、購入者さんからのご意見をSlackへ通知し、プロダクトの改善に繋げています。 Zendeskとの連携例 また、ショップオーナーさんからのご意見もSlackへ通知し、こちらもプロダクトの改善に繋げています。 オーナーさんからのフィードバックの例 BASEでは こちらの記事 の通りCS対応を当番制で運用しています。CSチームからエンジニア向けに調査依頼が投稿されると、自動的に過去の似たようなお問い合わせを拾ってきて、調査のヒント度案内してくれるbotも構築されています。 CS_q_bot ⑦情シスにおけるSlack活用例 社員からのヘルプデスクをワークフローを使って受け付けています。 ヘルプデスクの例 その他の活用事例 情報セキュリティ事象・事項の報告をワークフローにて受付 翌営業日に物理出社するかのアンケート(リモートワーク期間中の運用) ⑧その他の活用例、Slackの便利機能 Slackには無数のアプリや便利機能がありますが、ここではBASEが利用している便利機能の一部をご紹介します。 Unipos連携・・・BASEではピアボーナスである Unipos を導入しているのでSlack上で感謝の気持ちを送り合うことができます。 Colla連携・・・ Collaアプリ を利用すると、毎日botがメンバーにインタビューをし、指定したチャンネルでメンバー紹介をしてくれます。お互いのことを知ることができコミュニケーションの円滑化に役立ちます。 新規チャンネルを通知・・・Zapierというアプリを利用すると、スペース内で新しいチャンネルが作成されたら通知することができます。新しいチャンネルの存在を見過ごすことも減りますね。 新規絵文字を通知・・・同じくZapierを利用すると、新しく絵文字が追加されたら通知することができます。 話題のツイートをSlackへ通知・・・例えば新しい機能をリリースした際に、Twitterで反応があったらSlackへ通知するようにしています。こちらは3月に Amazon Pay 対応をした時の反応ツイートの通知の例です。 リアクションで任意のチャンネルにメッセージをコピー・・・「 リアク字チャンネラーアプリ 」を使うと、パブリックチャンネルに投稿されたメッセージにリアクションすることで、別のチャンネルにメッセージをコピーすることができます。例えば「これはあのチャンネルの話題だ」と思った時にリアクションすることで、話題の集約といったことができます。 おわりに いかがでしたでしょうか。おそらくご存知のものもあれば、「こんな使い方があったのか!」という発見もあったのではないでしょうか。手作業でやっているルーチン業務などもSlackの機能をうまく使うことで自動化できたり業務改善ができるかもしれません。ぜひチャレンジしてみてください! お知らせ BASE 株式会社では、「BASE」の開発を一緒に盛り上げてくれる仲間を募集しています。 一緒にかっこいい開発チームを作りましょう! ぜひよろしくお願いします! open.talentio.com
アバター
こんにちは!! BASE株式会社 SRE Group Managerの富塚( @tomy103rider )です。 以前、以下のSREの求人票についての記事を公開してから多くの方とカジュアル面談でお話をさせていただく機会が増え、カジュアル面談の中でも「求人票ブログの記事見ました!」という声をいただき嬉しい限りです。ありがとうございます。 devblog.thebase.in そんなカジュアル面談の中でよくいただく質問があり、今回はその中から最近のSREチームについて、 チームで大事にしていることは? チームの働き方は? チームの業務は? この3つについて紹介したいと思います。 チームで大事にしていることは? 現在SREチームでは、 「信頼性=ユーザの期待値を超え続けること」としてこれを維持し続ける BASEの機能等々の価値を高めるための時間を多く作っていけるようにする が大事であると考えています。 この2つの言葉は、以前チームのミーティングの際に、 「そもそもBASEのSREチームとして今の時点ではどういうチームを目指しているの?」 「BASEのSREは何を大事にしているのか?」 という話題になり、「自分たちが考えるBASEのSRE像とは?」をチーム内で持ち寄って話し合った結果を言語化したものです。ちなみにBASEのSREチームから見たユーザとしては、前者は「オーナーズ(ショップオーナー)とカスタマー(購入者)」の方々、後者は社内でサービスに関わる(SRE含む)人たちを指していて、BASEに関わる全てのユーザ対しSREとしての活動をしていくことが大事であることを表しています。 チームの働き方は? 昨今のコロナ禍ということもありBASEのSREチームにおいても現在は基本的にリモートで業務をしています。そんな中で、日常的なコミュニケーションとしては基本的にはSlackでテキストコミュニケーション、ミーティングはZoomで行っていて、最近はSlackハドルミーティングも活用してより気軽に音声だけでのコミュニケーションを取るようにもしています。 またSREチームの1週間のイベントとしては大きく2つで「朝会」と「SyncSRE」があります。 朝会 - 月〜金 12:00〜12:30 - リリースや大きな作業などのカレンダー確認など - 各自の進捗 - 共有/相談タイム 朝会の様子 朝会はタスクを進めている中での困りごと/相談の時間を特に大事にしていて、より話を掘り下げる必要があるときは30分を超えることもあります。また当然ながら何かあればZoomやハドルミーティングで朝会以外でも都度相談しあっています。 SyncSRE - 隔週金曜 17:00〜 - フリーテーマでチームみんなで会話をする時間 SyncSREは朝会とは異なり、特に議題は決めず主に最近起きたことや感じたことなどをざっくばらんに話してチーム内の認識や意識を合わせる時間にしています。これを始めたのは、リモートワークが始まる前は日常的に自然発生していた会話がリモートワークとなり減ってしまっていることに危機感を感じ、このような時間を意図的に作りました。チーム内での意識/認識をSyncする良い時間になっていると思っていて、実際に1つの事例として最初に書いた「チームで大事にしていることは?」で紹介した2つの言葉を話し合うキッカケになったのはこのSyncSREでの話題からでした。 チームの業務は? ここについては色々ありますが、今回は最近注力している業務や各開発プロジェクトとの関わり方について簡単に紹介したいと思います。 トイル削減/サービスの運用効率化 現在BASEのサービス拡大に伴ってインフラリソースの作成/変更/運用の機会が増え続けており、日常の運用負荷であったり環境の再現性の担保という点において課題があると認識しており、今後の様々な改善を加速していくためにもここを最優先で改善する必要があると考えています。そのため、今更ではありますがInfrastructure as Codeの実践というところでインフラ環境のコード化+断続的な構成レビューのフロー整備を試行錯誤しながら進めているところです。このあたりはある程度形ができた段階でチームのメンバーがブログを書いてくれることを期待しています。 他チームや開発プロジェクトとの関わり 他チームや各開発プロジェクトとの関わりという点では、例えば必要となるAWS上のインフラに関わる業務があります。ただ、単に機能要件を満たすだけの作業をするだけではなく、各開発プロジェクトにおいて機能要件以外に「非機能要件」という観点でもっと事前に考慮しておくべきことはないか?など、リリース後に小さく積み重なっていくと重い課題になりがちな懸念点をSREからも伝えていけるようにしています。 まとめ いかがでしたでしょうか。少しでもBASEのSREチームの雰囲気が伝わればと思います。 さて、引き続きSREチームでは成長するサービスを一緒に支えていっていただける仲間を募集しています! BASEのサービスとSREチームに少し興味が出てきたのでもっと話を聞いてみたい! 似たような試行錯誤をしているのでカジュアルに話してみたい! サービス成長とチームの成長に対して一緒にチャレンジしてみたい! など、少しでも興味を持っていただけましたら、悩み辛みの情報交換でも構いませんし、ここに書ききれなかった話など、ざっくばらんにカジュアル面談でお話ができればと思いますので、お気軽に以下までご連絡いただけると嬉しいです! → カジュアル面談はこちらまで! ← ※参考 open.talentio.com 余談 まとめを書いたあとに余談ですが、最近以下の記事を読みました。 「これでよいのか: SREチームの成熟度評価について考える」 cloud.google.com ここに書かれている「このグループはこの職務をSREと呼ぶために必要な原理と実践の適用において十分なレベルに達しているでしょうか?」という問いに対し、記事内の4つの原理と自分たちがSREとして進めていっている活動を照らし合わせると、方向性としては間違っていないとは思いつつ、実際SREとしての振る舞い/行動ができているかというとまだまだであろうという感想を持ち、この記事については近々SyncSREで改めて話題にしてみようと思います。
アバター
はじめまして、フロントエンドエンジニアの @rry です。 BASE では社内勉強会として「 Frontend Weekly LT 」を毎週開催しています。 今回は Vite 特集で LT をしたので、Frontend Weekly LT の紹介も兼ねて内容を発信していきたいと思います。 Frontend Weekly LT とは BASE はフロントエンドエンジニアが、業務委託の方も合わせて約20名ほどいます(7/16現在)。 普段は別チームに分かれており、プロジェクトもそれぞれ別で開発を進めているのですが、この勉強会ではそんなフロントエンドエンジニアが一堂に集まってわいわい LT するイベントです。 LT イベントは基本的に毎週2名ずつ登壇して、 新しく BASE でリリースされた機能の紹介 いま気になっている&プロダクトで使用している技術について 自己紹介 LT などなど、毎回さまざまな内容で LT しています。 7/16 は Vite 特集の LT 会 Vue.js v-tokyo オンライン Meetup#13 - connpass 最近の v-meetup でも Vite 特集があったりと、最近 Vite がとてもアツいです。 BASE ではまだプロダクションでの利用はありませんが、Vite v2 が Vue や React などライブラリに限らず利用できるというのもあり、期待を寄せているビルドツールです。 今回の LT 登壇者は @rry と @gatchan0807 がそれぞれ発表する流れでした。 次世代のフロントエンドビルドツール Vite 登壇資料はこちらです。 最初は Vite x React で何か作ってみようと思っていたのですが、Vite について調べていくうちにそもそものビルドツールの特徴についてのほうが気になってしまい、結果としてこのような内容の LT になりました。 登壇してみて Vite のドキュメントを改めて全部読んだり、Vite に関係する記事をいくつか読んだりできたので良かったです。 今回参考にした記事: https://speakerdeck.com/kazupon/native-esm-powered-web-dev-build-tool https://speakerdeck.com/kazupon/vue-with-vite https://ics.media/entry/210708/ https://tech.recruit-mp.co.jp/front-end/post-21250/ Frontend Weekly LT はみんなの思い出作り 最近のテックブログで @fshin2000 さんがこんなことを書いていました。 そもそも、オンラインコミュニケーションにおいては何が足りないのだろうか?と思っていたのですが、一言でいうと、 良い思い出を作るイベントが圧倒的に少ない ではないか?という事を考えています。 リモートワークの弊害は職場でのよい思い出が作りにくいこと - BASEプロダクトチームブログ この Frontend Weekly LT は今年の 1/15 から始まった取り組みです。 コロナ禍でコミュニケーションがオンラインになる中、こういったみんなで集まってわいわいできるようなイベントを毎週開催するのは、良い思い出作りになっているのではないかなと考えています。 これからも継続して Frontend Weekly LT を盛り上げていきたいなと思います! 💪 おわりに いつものやつですが、BASE では一緒にフロントエンドを盛り上げていく仲間を募集しています。 Vue や React など新しい技術で EC サービスを支えていきたい!という方のご応募お待ちしております ☺ 1.BASE_フロントエンドエンジニア - BASE株式会社
アバター
Owners Experience Backend Group で Engineering Manager をしています、炭田( @tac_tanden )です。 BASE では決済などの複雑な事業ドメインに立ち向かうために、ドメイン駆動設計(DDD)を使った開発を進めているチームがあります。 そんな中で先日、DDD の有識者として松岡幸一郎( @little_hand_s )さんをお招きし、DDD モデリングデモ会社内で開催しました。 その模様をレポートします。 開催の経緯 BASE では日頃から DDD に関する質問ができるように、松岡さんを Slack チャンネルにお招きし、開発や DDD の勉強会で出てきた困りポイントや疑問を質問させていただいています。 そんな中で「DDD の勉強をしているけれど、モデリングを実際にどうするのか見てみたい」、「自分でモデリングにトライしてみたけれど、このやり方でよいのかいまいちピンとこない」などの声が上がっていました。 なので、実際にモデリングをどうするのかのイメージを掴み、今後 DDD での開発に取り組む際のモデリング作業に活かせるよう、この度 DDD のコーチとして日頃お世話になっている松岡さんをお招きして BASE の決済領域のモデリングデモ会を開催することになりました。 参加者 BASE の決済領域のモデリングということで、エンジニア以外のメンバーにも有意義なのではと思い、Slack の #general にて開催の告知をしました。その甲斐あって、当日はエンジニアメンバー以外にも、プロダクトマネジメントチームやデザイナーチームのメンバーの方たちにも参加していただくことができました! Zoom での開催だったので、気軽に多くの方が参加できたのもあり、夜の遅い時間だったのですが当日は 40 名近くの方に参加いただきました。企画者として嬉しかったですね。ありがとうございました。 当日 デモ会当日は、BASE からは長年決済領域の開発に携わってきた Payment Group のエンジニアメンバーがドメインエキスパートとして参加しました。 ドメインエキスパートの知識をもとに、松岡さんが画面上にモデリングをしていくのですが、複雑だと考えていた決済領域の関係が次々に明らかになっていく過程は壮観でした。 当日のデモによって出来上がったドメインモデル図をブログ上でお見せできなのは残念ですが、各概念の関係性が分かりやすく表現されていて、これがあれば実装に入れそうだなと自然に思えるような完成度で驚きました。 また、モデリングの過程も、具体的にどういう手順でモデリングをしているくのかステップバイステップで説明/デモをしていただいたので、終わったあとには「自分でもできそう」と思えるようになりました。 2 時間という短い時間でしたが、松岡さんやドメインエキスパートの方の協力もあり、とても有意義な時間になりました。ありがとうございました。 参加者の感想 エンジニアメンバーのお二人の感想をお届けします。 ドメインエキスパートとして参加 今回ドメインエキスパートとして参加した Product Dev Division 所属の島田です。 脳内にあった抽象的な全体像をドメインモデリングを行ったことでより具体的にできました。 そのおかげで概念を言語化したり図にすることができ自分以外の人でも共通認識できるようになりました。 共通認識ができるようになるとドメインに対して色々な意見が出るようになりドメインの精度が上がったように思いました。 聴講者として参加 同じくProduct Dev Divisionに所属している 大津 です。 今回のドメインモデリングを見て、「ドメインモデリング自体がドメインを理解する行為である」ということが腹落ちできました! 当日の Slack の一連の流れを見ても今回のテーマである BASE の決済に対して理解を深められた参加者が多く、現状の認識のすり合わせとしてドメインモデリングが有効ではないかと感じました! 参加者で記念撮影 最後に 改めて、モデリングデモ会を通じてモデリングとはこんな風にやるのかと具体的なイメージを持つことができました。講師としてモデリングのデモを行っていただいた松岡さん、本当にありがとうございました! BASE では複雑な事業領域に正面からタックルしてユーザーのためにサービスを一緒に開発してくれる仲間を募集しています! まずはカジュアル面談でお話できることを楽しみにしております!よろしくお願いします! open.talentio.com open.talentio.com
アバター