TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

576

はじめに この記事はBASE Advent Calendar 2021の15日目の記事です。 BASE株式会社 Owners Experience Frontend チームのパンダ( @Panda_Program )です。 2021年の5月に入社してから、アサインされるプロジェクトの仕事以外に社内 UI コンポーネントライブラリ「BBQ」のメンテナンスに取り組んでいました。 その中でも特に Storybook 周りの整理をする過程、Storybook の v5 から v6 へのバージョンアップとその自動化のプロセスを以下の記事にまとめました。 Vue2 + Storybook v5 のコンポーネントを v6 向けに書き換える TypeScript Compiler API で40の Storybook コンポーネントを storiesOf から CSF(Component Story Format)に置換した 本記事はこの続きにあたります。 BASE の社内 UI コンポーネントライブラリ開発において、ここまでは Storybook というツールをしっかり使うということを目的としていました。 しかし、ここからは Storybook の力を引き出して開発者にその恩恵を感じて貰い、DX を向上させるところまで進めたいと考えています。 本記事では、現状の BBQ の課題を解決しつつ、Storybook をフル活用するために Chromatic の利用を検討し、社内で議論して実際に使った後、導入に至った過程を紹介します。 このため、本記事は「BBQ の課題 → 課題の解決策の検討 → ツールの選定 → ツール導入後の開発フローの変更の紹介」という構成を取ります。なお、Chromatic のインストール方法や具体的な使い方は本記事では扱わず、 別の記事で紹介しています。 社内UIコンポーネントライブラリ「BBQ」の課題 BBQ とは何か BBQ とは、BASE で利用している社内用の UI コンポーネントライブラリです。 Vue2 と TypeScript、scss で記述しており、Storybook で表示確認をしています。Web アプリケーションの BASE とは独立したレポジトリで管理、運用されています。 一例を挙げると、商品が未登録のときに表示される「EmptyBox」というコンポーネントは、この BBQ の中に作られており、import で呼び出すだけで利用できるので各所で再利用が可能です。 商品が購入されていないことを表すUI Storybook ではこのように記述されています。 const Template = (args, { argTypes } ) => ( { components: { EmptyBox } , props: Object .keys(argTypes), render(h) { return ( <div> <section> <h1>・ボタンあり</h1> { /* empty box */ } <bbq-empty-box size= {this .size } icon= {this .icon } > <div slot= "text" > <p><span>まだ商品が登録されていません</span></p> <p><span>商品を登録してあなたのネットショップを作りましょう</span></p> </div> <bbq-button type= "submit" slot= "action" width= {this .buttonSize } icon= "plus" >商品を登録する</bbq-button> </bbq-empty-box> </section> <section style= { { marginTop: '30px' } } > <h1>・ボタンなし</h1> { /* empty box */ } <bbq-empty-box size= {this .size } icon= {this .icon } > <div slot= "text" > <p><span>商品が見つかりません</span></p> </div> </bbq-empty-box> </section> </div> ) } , } ); このように、 BASE のデザインを体現した再利用可能なコンポーネントライブラリが BBQ です。 他にも Button や Modal、Calendar などの多数のコンポーネントがあります。コンポーネントの粒度は Atomic Design の Atoms から Molecules 相当に絞られています。 BBQ の直近の課題 さて、BBQ の直近の課題は大きく以下の2点です。 既存のコンポーネントを改修した際に発生する DOM、CSS に起因する表示崩れを自動で検知できないこと 依存モジュールのバージョンアップに時間がかかること BBQ にはテストがありません。 ビジュアルリグレッションテストやコンポーネントテストがあれば、上記の2点は解決できるはずです。以前はスナップショットテストがあったそうですが、私が入社した時には既に CI 上でもローカルでも動かされていませんでした。 テストがないと、開発者は動いているコードの改修に対して萎縮してしまいます。 自分がバグを仕込まないか戦々恐々としながらコードを書き換えて何度も動作確認をし、最後は勇気を振り絞ってリリースをします。 もしそれでバグが出てしまったらロールバックをしなければなりません。その時間と手間に加えて、原因分析、事象の共有、再発防止策を考えるなど多くのことに時間が割かれてしまいます。 テストがある場合は、ローカルや CI でテストが落ちていればそれを修正するだけでいいのにもかかわらず。 また、共通コンポーネント集はどの企業にとっても素早い開発速度を支える重要なツールであり、デザインや開発のコストを大幅に減らし、ユーザーにリリースを届けるスピードを加速させる大事な資産です。 その資産を守り、健全に育てるためにはやはりテストの導入が必要だと考えました。 そこで、まずは何らかのテストを導入することを BBQ のメンテチームのメンバーに相談しました。 BBQ に対するテスト方法、ツールの選定 BBQ にはメンテナンスチームがあります。メンバーは自分を含めた5人のフロントエンドエンジニアで、プロジェクトの合間にボランティアでメンテナンスをしています。 ライブラリのバージョンアップや、コンポーネント全体の見直しで発見した issue の対応などを行なっています(12月からはアドベントカレンダー 6日目のアクセシビリティの記事 を作成した @rry が加わって6人になりました)。 上記の BBQ の課題をメンテチームに共有し、E2E や VRT(ビジュアルリグレッションテスト)、コンポーネントテストやユニットテストなど色々なテスト方法がある中で、 BBQ に最適なテストは何か議論をしました。 みんなで議論した結果、VRT が BBQ の課題を解決する手段として最も良いと結論づけました。 議論の中で挙げられた検討事項は以下のようなものです。 E2E テストはそもそも今回の課題に見合っていないとして除外。 BBQ はコンポーネントのライブラリ集なのでページをまたぐ画面遷移やバックエンドまたはモックも必要になるようなログインといった粒度の大きなテストは不要だから ユニットテストも除外。BBQ は Vue.js を クラスコンポーネントで記述しており、state を利用したロジックがほとんど。 このため、ロジックをユニットテストに切り出しにくいから コンポーネントテストは、少し議論したけどやはり却下。 BBQ のライフサイクルを考えると、たくさんコンポーネントを追加・改修していくフェーズは終了しており、開発自体は活発ではない。 全てのコンポーネントに対してテストを書くコストは大きい一方で、既存のコンポーネントは数年間運用してバグが出ていないことが確認されており、既存のコンポーネントに対する新しい機能の追加も今はほとんどないため VRT(ビジュアルリグレッションテスト) はちょうど良い。 現在は CSS の修正や、ロジックにあまり関係しない DOM のちょっとした修正がメインであり、開発者は表示崩れの有無を主に確認したいため。 スクリーンショットを撮って差分を表示するだけで実現できるため、コンポーネントテストより記述は少なくなる 以上が VRT を採用した理由です。 E2E やユニットテストを今は採用しなかった理由も記述しました。議論の中では最初に候補から外されたくらいメンバー間で不採用の理由は自明でしたが、本記事は意思決定の過程を紹介する記事なのであえて書き残しておきました。 コンポーネントテストは私から提案しました。BBQ は Input や Modal といったエラー状態や開閉状態を持つコンポーネントが存在するので、それらのテストもでき、ロジックが壊れていないことを担保できると考えたからです。 しかし、メンバーと議論して BBQ に対する理解を深めていく過程で、開発のホットな箇所をこそ守るべきだと考えを改め、VRT が適切だと判断しました。 幸い私は Storybook の VRT ツールである Chromatic の活用経験があったため、BBQ の課題は VRT で解決できるであろうことが予想できました。 課題の解決手段から考えるのではなく、テスト対象のアプリケーションの性質とその課題を考えることが重要だねとチームで確認しあいました。 そして VRT を導入しようとチームで決めた後は、 VRT を実施するツールの検討に入りました。 なお、これは将来に渡ってコンポーネントテストやユニットテストなどを導入しないということではありません。 実際、VRT の導入と並行して、BBQ にバグが出たときの再発防止策としてコンポーネントテストが導入され、過去にバグがあった一部のコンポーネントでデグレが起こらないようにテストが記述されました。 今回は BBQ 全体に関わるテストの導入という文脈であることをご理解頂ければと思います。 VRT を実施するツール。reg-suit と Chromatic VRT とは何か そもそも VRT(ビジュアルリグレッションテスト)とは何でしょうか。 システム開発の文脈でリグレッションという単語は「新規開発に伴って、既存の機能が正常に動作しなくなること」を指します。これはデグレとも呼ばれます。 リグレッションテストは、「コードを書き換えても以前の機能が正常に動作することを担保するテスト」です。それを視覚の面からテストしたものがビジュアルリグレッションテストです。 具体的には、画面(ブラウザ)にページやコンポーネントを表示してスクリーンショットを撮影し、現行の UI と改修後の UI を比較して差分の有無を検出することで実現されます。 reg-suit と Chromatic この VRT を実施するツールとして有名なのは reg-suit です(「レグスイート」と呼んでる人もいますが、suit の発音は「スーツ」です)。 reg-suit について調べた結果、今回は以下の理由から採用を見送りました。 reg-suit は画像の差分を解析して diff を表示してくれるツールであるため、画像は自分たちで別途準備する必要があるため reg-suit には便利な plugin が揃っているが、plugin の選定をしたり、設定ファイルを書いたり、S3 のバケットを準備したりする必要があり、Chromatic と比較すると準備量が多いため 先程記述したように自分は Chromatic の利用経験があったため、コマンドを一行実行するだけで Storybook をビルドして publish できる体験が忘れられませんでした。 しかも全てのストーリーに対してコミットごとの差分を解析、表示してくれる Chromatic は、導入するのも捨てるのも楽だと考えていました。 特に、Chromatic は Storybook のメンテナーが作成したツールであり、BBQ は Storybook で表示確認をしている点も考慮すると、今回は Storybook のエコシステムに乗った方が良いという判断をしました。 reg-suit が悪いわけではなく、こちらも上記のテスト方法の検討結果と同様に BBQ というアプリケーションに対しては Chromatic の方が適切だと判断しただけです。 なお、 Chromatic の具体的な使い方については別の記事をご参照ください。 以上のような意思決定の過程と結論をマネージャーに共有したところ、まずは無料プランで導入し、 BBQ のメンテナンスチームの開発メンバーが Chromatic を使った開発フローを体験した上で、良し悪しを判断しようということになりました。 そこで、実際に Chromatic を一部のブランチに適用していく準備を始めました。 Chromatic を効果的に活用するための準備 ストーリーの数を増やす Chromatic を BBQ に導入する前に、コンポーネントのストーリーのバリエーションを増やしました。 デザイン崩れを検知するためには、コンポーネントの様々なバリエーションを予め表示しておき、カバー範囲を広げないと VRT の効果が半減するからです。 コンポーネントを改修した際に、特定の Props を与えたときにだけ表示される状態の考慮が漏れていて、気づかないところでバグが発生している可能性もあります。 そのようなバグを未然に防ぐために、様々な Props を与えたコンポーネントのストーリーを作成して管理下に置くことが理想です。 具体的には、以下に挙げる Search コンポーネントのように、Storybook の CSF(Component Story Format)の機能を利用して、 Props を部分的に変えて一つのコンポーネントのバリエーションを網羅するようにしました。 import { action } from "@storybook/addon-actions" ; import { Story } from "@storybook/vue" ; import Vue, { VNode } from "vue" ; import README from "./README.md" ; import Search from "./Search.vue" ; export default { title: "Elements/Search/Search(Vue)" , parameters: { notes: { README } , } , } ; type Props = { name: string placeholder: string keyword: string disabled: boolean } const Template: Story<Props> = (args, { argTypes } ) => Vue.extend( { components: { Search } , props: Object .keys(argTypes), render(h) { const { name, placeholder, keyword, disabled } = this .$props return ( <div style= {{ padding: "40px" }} > <bbq-search name= { name } placeholder= { placeholder } disabled= { disabled } vModel= { keyword } onSearch= { (val: any) => this .search(val) } onChange= {this .change } onInput= {this .input } /> </div> ) as VNode } , methods: { input(val: string) { action( 'input' )(val) } , search(val: string) { action( 'search' )(val) } , change(val: string) { action( 'change' )(val) this .$props.keyword = val } , } , } ); export const Default = Template.bind( {} ) Default.args = { name: 'search' , placeholder: '商品名・説明から検索' , keyword: '' , disabled: false , } ; export const NoPlaceholder = Template.bind( {} ) NoPlaceholder.args = { ...Default.args, placeholder: '' , } ; export const WithKeyword = Template.bind( {} ) WithKeyword.args = { ...Default.args, keyword: 'おいしい肉' , } ; export const Disabled = Template.bind( {} ) Disabled.args = { ...Default.args, disabled: true , } ; さまざまな Props を渡す もちろん Search 以外にも Button や Input のような全てのコンポーネントに対してこのような変更を加えています。 Props のバリエーションを増やすことで、結果的にストーリーの数が当初の79から148に増えました。 GitHub Actions でコミットを push するたびにビルドする Chromatic を導入しても常に活用しなければ意味がありません。 そこで、ブランチごとに、またコミットの push のたびに Chromatic をビルドするために GitHub Actions(GHA) を活用しました。 Chromatic 用の GHA のテンプレートは公式で用意されています。 このため、開発者としてやるべきことはこのテンプレートを使うことと、ビルド用のトークンを GitHub レポジトリのシークレットに埋め込むことだけした。これで各ブランチで Chromatic をビルドする準備ができました。 Chromatic の効用をチームで体感する dependabot が作成するブランチに適用する 便利なツールを導入した後はチームで運用し、ツールに対して知見をチームで溜めていくことが次のステップです。 BBQ のチームメンバーが Chromatic を体験してみるにあたり適切な粒度のタスクを探したところ、モジュールのバージョンアップデートがちょうどいいように思われました。 従来、 dependabot によってバージョンアップデートのブランチが作成されたときは、担当しますと手を上げた開発者のローカル環境で手による動作確認がされていました。 具体的には、 Storybook をローカルで立ち上げて全部のコンポーネントを表示、チェックして、以前のバージョンと比較して問題ないことを確認していました。 しかし、この手法は確認に時間がかかる上に、「ビルドは通ってるし目視確認はしたけど本当にバグは出ていないと自信を持ってまでは言えない」という雰囲気がメンテチーム内にありました。 このため、心理的な負担もありモジュールのバージョンアップデートの対応は即座になされるわけではありませんでした。 ただし、脆弱性のあるパッケージのバージョンが上がっていないなどクリティカルな問題があるというわけではなく、dependabot があるパッケージのバージョンアップのブランチを作ってみんな気づいているけど、2〜3週間放置されている間に次のバージョンがリリースされたというような状況です。 パッケージのバージョンアップデートの理想状態は、CI でテストとビルドをしてエラーが出たらアップデートをやめ、CI が Fail しなければ master マージするという運用です。 テストがある場合は人がアドホックに動作確認をする必要がなくなります。しかし、BBQ にはテストがないためそれができていなかったのです。 差分がないから安心してリリースできる この課題は Chromatic で一定程度解決できると予想していました。 BBQ は UI コンポーネント集のため、ロジックに関するライブラリはほとんど入っていません(date-fns くらい)。依存ライブラリは Webpack の loader や eslint、stylelint といったビルド時に必要なツールくらいです(このため、パッケージのアップデートが遅れてもクリティカルな問題にはならなかったのです)。 dependabot が作成したブランチの CI を実行し、Chromatic で Storybook をビルド、表示するようにしました。 Chromatic 上ではブランチの baseline と呼ばれる時点のスクリーンショットとの差分が解析されます ( 詳しくはブランチとベースラインの解説は公式ドキュメントをご覧ください。 )。 テストと同様に、差分があれば Chromatic 上で目視チェックをし、差分がなければ問題ないとして master マージをすれば良いのです。 Chromatic が Storybook をビルドして URL を発行してくれるため、わざわざローカルで Storybook を立ち上げる手間が省けます。 また、差分があるコンポーネントは Chromatic がピックアップしており、差分のがある箇所もハイライトをつけてくれているため、抜け漏れがありません。 これは動作確認の時間の大きな節約になる上に、開発者としても不安なくパッケージアップデートを取り込んだ新バージョンの BBQ リリースできます。 実際にいくつかのパッケージのバージョンを上げましたが、以前と違ってとても楽になりました。 人間の目では気づかない差分もカバーしてくれる Chromatic の試用期間中に、アイコンに変更があるという差分が検知されました。 しかし、元のアイコンと新しいアイコンを見比べてみても全く同じように見えます。エンジニアメンバーが手を加えた覚えはなく、Chromatic のご検知かと疑いながら念のためデザイナーのチームに確認してみました。 すると、デザイナーから「特定の環境下で表示がおかしかったので、少し前に SVG の不要なパスを削除した。見た目には変更がない」とコメントがあり、それはまさにこの差分のことでした。言われてみると、確かに以前のアイコンの線が少し太いように見えます。 Chromatic が検知したアイコンの差分 今回は拡大しているのでなんとなく差が分かりますが、Storybook 上のアイコンは 14px という小さいサイズで表示されているため、目で見ても差分が分かりませんでした。 Chromatic はこんなところまで検知してくれるのかという驚きで、BBQ メンテチームが盛り上がると共に Chromatic に対する信頼感が増しました。 VRT がある開発フロー 開発フローに Chromatic を組み込む Chromatic は開発フローの中で、コードレビューのフェーズで活用しています。 コードと表示に変更があった場合、その表示の変更は意図したものであるかを確認するためのレビューです。コードレビューの中に、デザイン面の差分のレビューを組み込むイメージです。 Chromatic は GitHub と連携可能であるため、PR の画面からビルドの状態を知ることが可能です。下記の画像では「UI Review」と「UI Tests」に緑のチェックマークがついていますが、もし表示に差分があるときは黄色の丸いマークが表示されます。 GitHub の PR の Chromatic の状態のフィードバック Chromatic のサイト上で差分のあるコンポーネントの画像がピックアップされているので、レビュワーはそのコンポーネントに対するレビューをします。 気になる点があれば Chromatic 上でコメントを記入し、問題なければ Chromatic 上で Accept をします。 上記の画像に緑のチェックマークがついているのは、そもそも差分がない場合か、全ての差分に対してレビュワーが Accept をした場合のどちらかです。この画像では差分があったけど Accept しているため、「UI Tests」の説明に「4つの変更が基準として Accept された」と書かれています。 「レビュワーの責任範囲はコードレビューまでで、動作確認や表示確認はブランチの開発者の責務」と考える方もいますが、BBQ では Chromatic の導入に伴い表示確認もレビュワーにお願いすることになります。 ただし、Chromatic が差分を予め検出しているため、レビュワーの負担が大きく増えるようなことはありません。 むしろ、レビュワーが表示の変更点を網羅的に知れるという知識の標準化に役立つ上に、見た目の変更を伝えるために前後比較のスクリーンショットを撮って PR に貼り付けるというレビュイーの手間も省けます。 これが VRT を組み込んだ開発フローです。 Chromatic に対する BBQ メンテチームのメンバーの意見 BBQ メンテチームのみんなに Chromatic を一通り触ってもらった後、マネージャーに Chromatic を触った感想を報告する会が開かれました。そこでは以下の声を聞くことができました。 手元で Storybook を立ち上げていたが、その必要がなくなった 変更点をまとめて Chromatic で見れる 変更があったコンポーネント数が表示される 差分が全て記録されているので安心感がある 人の目でわからない差分を検知してくれた アイコンが変更されていた もちろん懸念点も少しありました。例えば「Chromatic 上のコメントが PR や Slack に通知できず、メールでの通知だけである上に、URL がコメントへのリンクになっていない」というものです。ただ、懸念点がどれもツール側の話なのでこちら側でできることはなく、とりあえず改善要望を出しておきました。 以前から折を見て議論を重ねており、マネージャーにも逐次共有して意見を聞いてきたため、BBQ に VRT を導入することに対する反対や Chromatic ではない別のツールが良いなどの声は出なかったためホッとしました。 以上が、BASE の BBQ という共通 UI コンポーネントライブラリのレポジトリに Chromatic を導入するという意思決定の過程でした。 終わりに フレームワークやライブラリ導入の技術選定に関する記事は枚挙にいとまがないですが、外部ツールを選定するためのプロセスや意思決定に関する記事はあまり見ないなと思い、本記事を書いてみました。 Chromatic の導入を例に挙げていますが、以下の点を意識して読んで貰うとより一般的な視点が得られると思います。 適切なツールを選ぶためには、まず現在開発しているアプリケーションの性質をつぶさに観察すること 社内で使用するツールはマネージャーと二人三脚で選定すること。ツールの調査や社内に使い方を広めるのは現場のエンジニアの役割であるが、導入可否の最終決定者はマネージャーであるため ツールを使い続けるためにチームからフィードバックを受けること。一人が特定のツールの導入を陣頭指揮してもいいが、ツールを使うのはその人だけではないため なお、 Chromatic の具体的な使い方については別の記事で紹介します。 Chromatic の導入や使ってみたいツールの導入に役立てば幸いです。 明日の記事は坂東さんの「10人以上のPJでリモートランチ会をするときの工夫したこと」です。
アバター
この記事は BASE Advent Calendar 2021 の14日目の記事です。 こんにちは。UIデザイナーのノムラ( @nomjic )です。2021年の初め頃にデザインリサーチPJを開始して、3〜4名のメンバーでここ一年間、定性リサーチにトライしてまいりました。その内容を本記事に書いていきたいと思います。 デザイン業務の傍らで実施した活動につき、リサーチ内容の深度・精度はいささか低めです。すでにがっつりリサーチに取り組んでいる方よりも、「 定性リサーチ活動を始めようとしている人、始めたいと思っている人 」にとって参考になる記事にできたらいいなぁと思いつつ、書かせていただきます。 デザインリサーチとは 我々の行っている活動を「デザインリサーチ」と呼んでいますが、 定性リサーチ、UXリサーチ、ユーザーリサーチ 、といった言葉に読み替えていただいても問題ありません。ゆくゆくはプロトタイピングやユーザビリティテストも取り入れていきたいと思いますが、現状では「 ユーザーにインタビュー調査をしていろいろ実態を探っています 」という一言でほぼ言い尽くせるリサーチ活動です。 以前に書いたブログ記事 で私の思うところの「デザインリサーチとは」を述べていますので、ご興味お有りでしたら併せてお読みいただけると幸いです。 今年実施した内容・概要 4つのリサーチを行いました。1回のリサーチで数名〜10名弱の方からお話を聞いています。 インタビュワーや書記役は主にPJメンバーが行っていますが、他の社内メンバーに参加してもらうこともあります。 「まずはユーザーとデザイナー(およびPM、エンジニア)が対話する場を作ろう」という形で動き出し、得られた知見を蓄積しつつ、なるべく多くのメンバーを巻き込んで社内に定性リサーチ文化を醸成する、ということを目指してきました。 実施した4つのリサーチについて、以下、概要を述べます。 リサーチその1 「まずは話を聞こう」 リサーチ内容 2021年 2月にインタビューをし、3月にかけて分析をしました。9ショップ・10人のBASEショップオーナーさんから話を聞いています。アパレル系、雑貨系、食品系などさまざまなジャンルのショップから話をお聞きしました。 ※ インタビュー設計や分析の流れは、 以前の記事 に書いたデザインリサーチワークショップの内容に沿っています。 所感 特に仮説等を立てずに「とにかく話を聞いてみよう」と始めたリサーチだったため、得られた成果は少々散漫というか、どう活用するかをイメージしづらいものでした。 とは言え、何はともあれ 「ユーザーに連絡して日程調整し、話を聞かせてもらって分析し、社内に報告する」という一連の流れ をデザイナー主導で行えたというだけでも、一つの大きな成果であったと思っています。 リサーチその2 「属性ベースでセグメント切って深掘り」 リサーチ内容 2021年 5月にインタビューをし、6月にかけて分析をしました。7ショップ・9人のアパレル系BASEショップオーナーさんから話を聞いています。 所感 「インタビュー対象の絞り込み(アパレル限定)」「質問項目の具体性アップ」を行うことで、1Qで得られた成果に比べてより具体的・実用的な情報を得られました。 基本的には「その1」と同じ流れでリサーチを行なったため、だいぶスムーズに一連の流れをこなせています。また、この回ではリサーチPJの外部に協力者を募り、他職種メンバーも巻き込んでいます。(インタビュワーや書記を行ってもらいました。) リサーチその3 「退会したユーザーとも話してみよう」 リサーチ内容 「その2」と並行して、2021年 6月にインタビューと分析を行なっています。BASEから別のECサービスへ移転した3名のショップオーナーさんからお話をお聞きしました。 このリサーチではいくつか仮説を立てて臨みました。結果、かなり具体的で、開発に役立ちそうな情報を得ることができています。 所感 BASEにマッチしなかったユーザーからも意見を集めたい、という想いから実施した「退会したユーザーへのインタビュー」だったのですが、実際に話を聞けた3名は「一度BASEから移転したが、今はBASEに戻ろうとしている」という人たちでした。非常に参考になるお話をたくさん聞けたものの、いささか偏りの大きいリサーチになってしまいました。 リサーチその4 「関心ごとベースでセグメント切って深掘り」 リサーチ内容 2021年8月にショップオーナーさんの関心ごとを問うアンケートを行ったのち、アンケート回答者を対象に 9月にインタビューをし、10月・11月にかけて分析をしました。アンケート結果から「集客・広告に強い関心を持っている」と解釈できる9ショップ・9人のBASEショップオーナーさんから話を聞いています。 所感 このリサーチでは、「傾向」と「課題」の2つの観点で、得られた情報をまとめています。 リサーチも4回目ということで、インタビューおよび分析はだいぶスムーズになってきました。反面、「得られた結果が開発に役立つか?」という面ではまだ不十分である感は否めません。 「その2」同様、この回でもリサーチPJ外からの参加者を募っており、より多くの社内メンバーに定性リサーチを経験してもらえました。 定性リサーチ結果を、どう開発に役立てるか リサーチ結果・知見の社内シェア この一年間で行なったリサーチで得られた情報(議事録、録画データ、インサイト)はスプレッドシート上に一覧表の形でまとめて、社内でシェアしています。また、上記「その1〜4」のリサーチごとに報告会を行うことで、口頭による社内へのシェアも行なっています。 リサーチその2、その4ではリサーチPJ外のメンバーにもインタビューに参加してもらっており、直接的に定性リサーチを体験してもらう形で、ユーザー実態に触れてもらっています。 このように、 シート(ドキュメント)に情報を蓄積する 報告会を実施して知見・成果をシェアする 直接参加して体験もらう という3つのルートで組織内にアウトプットしているのですが、この3つの中で 「直接参加」が圧倒的に意義が大きい(学びが深い) というのが、リサーチPJを率いている身としての本音だったりします。 ドキュメントや報告会では全然伝わらないナマの実態というものがあり、それはプロダクト開発へのマインドにも大きく影響する、と思っています。 (これってライブイベントみたいだなぁ、と思って書いた note記事 などもありますので、ご興味あったらお読みいただけたら幸いです。) 「直接的な体験」と、「蓄積したドキュメント」のバランス 直接参加してもらうのが一番学びが深いのは明らかなのですが、現実に「社内メンバーみんなに参加してもらう」というのは流石に無理があります。(その状態を目指したい、という想いはあるのですが) どうしても参加者が特定のメンバーに限られてしまいます。 組織で活動する以上、その活動の成果は蓄積して「新規メンバーでもアクセスできるように」「当時の担当者が不在でも活かせるように」しなくてはいけません。 いわゆる「属人化を避ける」というやつですね。 なので、直接参加してもらって体験してもらうだけでなく、ドキュメントや映像のような、後から第三者がアクセスできる形で蓄積していかなくてはいけません。 落とし所としては、以下の2つを交互に行うのが良さそうかなと思っています。 実態探索型リサーチ なるべく社内メンバーを広く巻き込みつつ、ユーザーの実態を探るインタビューを行う。 直接的にプロダクト改善に寄与する情報を得ることよりも、「ユーザーを 知ること 」「ユーザーと 対話すること 」に重きを置く。 仮説検証型リサーチ 具体的な仮説を立てて、インタビューにより仮説を検証する。 ユーザーを知ることよりも、プロダクト改善につながる情報を得ることを重視し、ドキュメントとして社内で共有した際に 共感 や 納得感 を呼び起こせる、プロダクト開発メンバーの行動につながりやすいアウトプットを目指す。 そもそも、リサーチ活動の在り方として 現在のデザインリサーチPJは、開発PJや企画PJとは独立した形で、ユーザー実態情報の収集とリサーチノウハウの蓄積を行っています。 この状態では、プロダクトの直接的な改善には繋がりにくく、そのためプロダクトを良くすることに心燃やしているBASE社員たちの関心を集めづらいです。リサーチ活動への参加を促したりリサーチ成果の共有を行っても、期待したほどの反響は得られません。 開発PJや企画PJの内容を鑑みて、それらを評価・検証するような動きをするとか、それらPJの発足の手前段階で関わっていくなど、リサーチ活動の在り方そのものを考えるべきでは?という想いもあり、検討をしているところです。 まとめ いろいろ書きましたが、今の活動形態での良き成果を目指しつつ、活動形態や体制の在り方を検討(MGRと相談)して、より質の高いリサーチ活動を行っていきたいと考えております。 以上です!最後までお読みいただいた方、ありがとうございます。 明日のアドベントカレンダーはフロントエンドエンジニアのkushibikiさんの記事です。お楽しみに!
アバター
この記事は BASE アドベントカレンダー 13 日目の記事です。 はじめに こんにちは。 BASE BANK 株式会社 Dev Division にて Software Developer をしている永野( @glassmonekey )です。 普段はバックエンドエンジニアとして、Go/Python/PHP を主に書いてたりします。 最近はチームの分析基盤づくりとかもやってたりします。 そのことについて先日書いたりもしたので、もし良かったらご確認ください。 devblog.thebase.in 私達のチームでは、「BASE」でショップを運営しているショップオーナーが簡単に資金調達をできる「 YELL BANK 」というサービスの開発・運営しています。 thebase.in あるプロジェクトで、ショップオーナーへのコミュニケーション手段に HTML メール を新しく作ることになりました。その際に HTML メール のコーディングも自分たちで行う必要がありました。 今回は その際に行った HTML メール 制作を MJML を使うと楽にできたのでその紹介になります。 HTMLメールの事情 大前提として、 HTML メール を使うとリッチな内容でコミュニケーションを取ることができます。 昨今のメーラーだと基本的には対応しているので使いたくなります。 弊社でも HTML メール の事例で 4月に購入完了メールをリニューアル しました。 では早速 HTML メール を作りましょうと言っても、なかなかそうは行きません。 なぜなら一般に、 HTML メール のマークアップと Webページ のマークアップと毛色が違うからです。 特に、 HTML メール のマークアップは 基本的なCSS はインライン 、 どのような環境でも安定しているテーブルレイアウト が基本となる事情があります。メーラーそれぞれの描画任せが要因だったりするようです。 参考: How to Create HTML Emails Using the Table Element [+ Templates] では HTML メール でどのタグが利用できるのでしょうか。 それに関しては大体は以下のサイトで簡単に調べることができます。 www.caniemail.com 例えば、「BASE」ならぬ <base>タグ だと以下のような対応状況のようです。緑は完全に対応、黄色は部分的に対応、赤は未対応です。 ちなみに対応状況は手動で調べられてるようです。とはいえ最初に見る状況としては大変助かっています。 Because every test is done manually, some features might not have been tested on every email client. Can I email… Email Client Support Scoreboard また製作者の方である @HTeuMeuLeu さんの記事の1つの Outlook is not an Email Client からは各社メーラへの対応状況の複雑さの苦労が垣間見えます。 以上のことから基本的には HTML メール のコーディングは避けたほうが無難です。無理してコーディングする必要がなければ、 Send Grid などの SasS を使うことが良いでしょう。 しかし、コーディングしないといけないときもあるでしょう。そのときに便利なものが MJML となります。 MJMLとは MJML とはレスポンシブな HTML メール を作ることのできるフレームワークです。 mailjet社 が開発しています。 だいたいのことは ドキュメント に載っているので参照ください。 最初は オンラインエディタ で試しながら始めてみるのがおすすめです。 これを使うと複雑なテーブルタグを書かずとも、簡単に HTML メール を書くことができます。 具体例を見てみましょう。 mjml.io 例) < mjml > < mj- body > < mj- section > < mj-column > < mj-image width = "100px" src = "https://mjml.io/assets/img/logo-small.png" ></ mj-image > < mj-divider border - color = "#F45E43" ></ mj-divider > < mj-text font- size = "20px" color = "#F45E43" font-family= "helvetica" > Hello World </ mj-text > </ mj-column > </ mj- section > </ mj- body > </ mjml > 見た目はこのようになります。 ちなみに、実際の HTML はこのような形で出力されています。 MJML で書くと大分簡略化されていることがわかります。 <!doctype html> < html xmlns= "http://www.w3.org/1999/xhtml" xmlns:v= "urn:schemas-microsoft-com:vml" xmlns:o= "urn:schemas-microsoft-com:office:office" > < head > < title > </ title > <!--[if !mso]><!--> < meta http-equiv = "X-UA-Compatible" content = "IE=edge" > <!--<![endif]--> < meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1" > < style type = "text/css" > #outlook a { padding : 0 ; } body { margin : 0 ; padding : 0 ; -webkit- text-size-adjust : 100% ; -ms- text-size-adjust : 100% ; } table , td { border-collapse : collapse ; mso- table -lspace: 0pt ; mso- table -rspace: 0pt ; } img { border : 0 ; height : auto ; line-height : 100% ; outline : none ; text-decoration : none ; -ms- interpolation-mode : bicubic ; } p { display : block ; margin : 13px 0 ; } </ style > <!--[if mso]> <noscript> <xml> <o:OfficeDocumentSettings> <o:AllowPNG/> <o:PixelsPerInch>96</o:PixelsPerInch> </o:OfficeDocumentSettings> </xml> </noscript> <![endif]--> <!--[if lte mso 11]> <style type="text/css"> .mj-outlook-group-fix { width:100% !important; } </style> <![endif]--> < style type = "text/css" > @media only screen and ( min-width : 480px ) { .mj-column-per-100 { width : 100% !important ; max-width : 100% ; } } </ style > < style media = "screen and (min-width:480px)" > .moz-text-html .mj-column-per-100 { width : 100% !important ; max-width : 100% ; } </ style > < style type = "text/css" > @media only screen and ( max-width : 480px ) { table .mj-full-width-mobile { width : 100% !important ; } td .mj-full-width-mobile { width : auto !important ; } } </ style > </ head > < body style = "word-spacing:normal;" > < div style = "" > <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> < div style = "margin:0px auto;max-width:600px;" > < table align = "center" border = "0" cellpadding = "0" cellspacing = "0" role = "presentation" style = "width:100%;" > < tbody > < tr > < td style = "direction:ltr;font-size:0px;padding:20px 0;text-align:center;" > <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> < div class = "mj-column-per-100 mj-outlook-group-fix" style = "font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;" > < table border = "0" cellpadding = "0" cellspacing = "0" role = "presentation" style = "vertical-align:top;" width = "100%" > < tbody > < tr > < td align = "center" style = "font-size:0px;padding:10px 25px;word-break:break-word;" > < table border = "0" cellpadding = "0" cellspacing = "0" role = "presentation" style = "border-collapse:collapse;border-spacing:0px;" > < tbody > < tr > < td style = "width:100px;" > < img height = "auto" src = "/assets/img/logo-small.png" style = "border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width = "100" /> </ td > </ tr > </ tbody > </ table > </ td > </ tr > < tr > < td align = "center" style = "font-size:0px;padding:10px 25px;word-break:break-word;" > < p style = "border-top:solid 4px #F45E43;font-size:1px;margin:0px auto;width:100%;" > </ p > <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 4px #F45E43;font-size:1px;margin:0px auto;width:550px;" role="presentation" width="550px" ><tr><td style="height:0;line-height:0;">   </td></tr></table><![endif]--> </ td > </ tr > < tr > < td align = "left" style = "font-size:0px;padding:10px 25px;word-break:break-word;" > < div style = "font-family:helvetica;font-size:20px;line-height:1;text-align:left;color:#F45E43;" > Hello World </ div > </ td > </ tr > </ tbody > </ table > </ div > <!--[if mso | IE]></td></tr></table><![endif]--> </ td > </ tr > </ tbody > </ table > </ div > <!--[if mso | IE]></td></tr></table><![endif]--> </ div > </ body > </ html > MJML入門 では実査に、 MJML を触ってみましょう。 プロジェクト構成 最初にメールを作成するためのプロジェクトを準備しましょう。 今回説明するディレクトリ構成は以下としました。 ├── dist ├── mjml │ ├── section │ └── template ├── package.json └── yarn.lock 各ディレクトリの責務は以下の形です。 今回、メールのコンポーネントは再利用 する/しない ぐらいの区別しかしていません。 MJML によるメール作成の知見が社内で成熟してくれば、変わる可能性はあります。 dist …生成される html を出力します。 mjml … 生成するための MJML ファイル群を置きます。 section… Header や Footer など再利用するコンポーネント用 mjml ファイル群を置く所 template ... 1 メールのバリエーションと対応した mjml ファイル群を置く所 インストール 基本的には npm で mjml-cli を入れたら終わりです。 $ npm install mjml package.json には npm run build などで build できるように、以下を追記しておきます。 " scripts ": { " build ": " mjml ./mjml/template/*.mjml -o ./dist " } 基本構成 基本的には ドキュメント を見つつ、 テンプレート を見て、参考にさせてもらうのが近道です。 基本的には HTML と同じ用なセマンティクスを持っているのでそれに従って記載するといいでしょう。 ローカルで開発時は VS codeの拡張 を入れると previewで確認しつつ編集できるので便利です。 基本構成例 < mjml > < mj- head > < mj-attributes > < mj-text color = "red" /> < mj-class name = "content" color = "black" /> </ mj-attributes > </ mj- head > < mj- body > < mj- section > < mj-column > < mj-text > Hello, World(Red) </ mj-text > < mj-text mj- class = "content" > Hello, World(Black) </ mj-text > </ mj-column > </ mj- section > </ mj- body > </ mjml > 基本的なマークアップの規則としては、いわゆるグリッドレイアウトになります。 メールは複数のセクションから構成されますが、基本的な 1 つのセクションの構成は mj-section > mj-column > コンテンツ要素 から成り立ちます。 主に利用するタグは以下の流れになります。 mj-section … 基本的なコンテンツの区切りを定義します。 mj-column … 複数使うことでモバイルでは 1 列、PC では 2 列といったレスポンシブを実現します。 コンテンツ要素 mj-text … テキスト表示 mj-image … 画像表示 などなど 共通の装飾について 文字色などの装飾は個別の要素に指定すれば変更できます。 < mj-text color = "red" > 赤色 </ mj-text > しかし、余白や文字サイズなどは統一感出したいでしょう。その場合は mj-class を使って予め用意しておくことで同時に複数の要素に共通的な装飾を施すことも可能です。 準備は以下のように mj-attribute に class 名を用意しておきます。 < mj- head > < mj-attributes > < mj-class name = "content" font- size = "14px" font-family= "Arial" line- height = "24px" padding= "0" /> < mj-class name = "annoation" font- size = "12px" font-family= "Arial" line- height = "18px" padding= "0" /> </ mj-attributes > </ mj- head > 利用自体は簡単で mj-class を指定するだけです。 < mj-text mj- class = "content" > 半角スペースで複数のクラスの適用もできます。 < mj-text mj- class = "A B" > ファイル分割について ある程度メールの種類が増えてくると、ヘッダー部分などの共通部分を分離したくなりますよね。 それには、 mj-include を使うことで実現します。 たとえば共通の分割線パーツコンポーネントを section/common/divider.mjml で用意してたとします。 < mj- section > < mj-column > < mj-divider border - width = "4px" border - color = "#F0F1F4" padding= "0" /> </ mj-column > </ mj- section > 呼び出し側としては以下のように相対パスで記述するだけで完了です。 略… < mj-include path= "../section/common/divider.mjml" /> 基本的にはセクション単位で扱うことが多いはずなので、共通のパーツも section 単位で作っておくと再利用がしやすいです。 パーツ分割における注意事項としては、パーツ側のコンポーネント編集時には VS Code の preview が動作しないことが挙げられます。 おそらく preview には mj-body などの要素が必要だが、パーツ側コンポーネントにはそれらが含まれていなからだとは思われます。 なので、最初から分離しようとせずにコーディングがある程度完了してから分離したほうが作業効率は良いです。 余談 余談ですが私自身は MJML のことは知らず、同僚の @sam8helloworld が同僚となる前に教えてくれたという個人的エピソードもあります。大変助かりました。 htmlメールのいい感じのフレームワークとかない? sendgridで素直に作れ?はい。 — エターナル・フィールド (@glassmonekey) 2021年5月6日 HTMLメールはご存知かもですが仕様が独特かつHTMLのルールも古いので人がコーディングするのはただただコスト高いっすね それでもコーディングベースで細かくやりたいならMJML, SaaSで基本ノーコードでやりたいなら無難にsendgrid, mailchimp ,b-dashってところですかね — さむ (@sam8helloworld) 2021年5月6日 おわりに MJML を紹介しましたがいかがだったでしょうか? HTML メール は令和となった現代においてもなかなか難しいので、ぜひ皆さんも MJML をご活用ください。 無論実際のメーラー上の確認は最終的には必要ですが、手元でプレビュー見つつローカルで大半の作業を完結できるので、開発体験は最高でしたし何より工数の削減にも繋がり大変重宝しました。 最後に宣伝ですが、まだまだやれてないことがたくさんあるので、一緒にプロダクトを成長させていく仲間を募集中です。 open.talentio.com 明日は 2021 年のデザインリサーチ振り返りです。楽しみですね〜
アバター
この記事はBASE Advent Calendar 2021の12日目の記事です devblog.thebase.in ごあいさつ はじめましての人ははじめまして、こんにちは!フロントエンドエンジニアのがっちゃん( @gatchan0807 )です。 この記事は Denoの公式Docsを読んでみた前編 の続きで、前回得た知識を使って実際にDeno + GitHub Codespaces環境でSlack Botを開発してみよう!の回になります。 もしDenoについて詳しく知りたい方は前回の記事からご覧いただけますと幸いです!🙏 どんなSlack Botを作るのか まず完成品のイメージを共有してから実際に実装する内容に触れていきます。 少し話は脱線するのですが、BASEではコミュニケーションツールにSlackを使っており、その中で独自のSlack Appを作成してChatOpsを実現しています。 主だったところで言うと、STGとは別に複数用意されている検証環境のデプロイや起動、特定の開発中ブランチの適用などをエンジニアはもちろん、PMの方々も日常的に行っています。 下記はあくまでイメージですが、下記のようなコマンド的なテキストをBotにメンションして、デプロイを行ったりしています。 @dev-bot deploy repository inspection1 feat/something-branch-name 詳しくは、以前投稿されたこちらの記事をご覧ください。 BASEにおけるSlack活用術を大公開!〜Slackで始める業務改善〜 - BASEプロダクトチームブログ そういった環境があるため、Slack Botを使ったコマンドの実行に一定のリテラシがある前提で下記のようなサブコマンド付きのSlack Botを作成していきます。 作る予定のBotとそのサブコマンドたち 今回作成するのはご覧の通り、登録されたメンバーの一覧を色んなパターンでランダムにごにょごにょするBotです! @random-bot [command] [group] [member-name] ~~~~~~~ 便利コマンド系 ~~~~~~~ @random-bot (help|h) 👉 ヘルプの表示 @random-bot ping 👉 起動確認 ~~~~~~~ データ登録・削除系 ~~~~~~~ @random-bot create [group-name] 👉 グループ(メンバーを追加する枠)の作成 @random-bot (disband|delete-group|remove-group) [group-name] 👉 グループ(メンバーを追加する枠)の解散 @random-bot list [group-name] 👉 グループ内のメンバー一覧の表示 @random-bot add [group-name] [member-name] 👉 グループにメンバーの追加(メンバー名はテキストもメンションもどちらも登録可能です) @random-bot (delete|remove) [group-name] [member-name] 👉 グループからメンバーの削除 ~~~~~~~ シャッフル実行系 ~~~~~~~ @random-bot random-sort [group-name] 👉 グループ内のメンバー一覧をシャッフルして並び替え @random-bot pick [group-name] [number] 👉 グループ内から指定の人数をランダムでピックアップする @random-bot separate [group-name] [divide-count] [merge-option] 👉 グループ内のメンバーを指定の人数ごとにグループ分けする BASEのSlackで下記のようなユースケースを観測していたので、それらのニーズにマッチしたシャッフルのパターンをいくつか用意しています。 デイリーの今日の一言でのランダム化のニーズ 自分が今所属しているプロジェクトチームでは、デイリーで今日の一言とその日やっていることを共有する時間を取ってコミュニケーションと進捗共有を行っています。 その発表順が固定化されないように、 スッキリす の運勢ランキングが一番良い人からやってみたり、 順番ぎめ.com を使ってランダムに並び替えたりしてみていました。 @random-bot random-sort [group-name] はこのユースケースを満たすために実装していきます。 peer1on1でのランダム化のニーズ 続いて、こちらも自分が所属しているフロントエンドのチームではpeer 1on1という制度を運用しており、ランダムに選ばれた2〜3人で雑談をする時間を週に1回取っています。 peer 1on1の詳しい解説はこちらも過去投稿されたこちらの記事に譲りますが、現在もGASで作られたスプレッドシート上で管理されています。 そのシャッフル、本当にシャッフルですか?何気ない落とし穴にハマった話 - BASEプロダクトチームブログ @random-bot separate [group-name] [per-number] はこのユースケースを満たすために実装していきます。 読書会の司会選出でのランダム化のニーズ 最後に、別のチームで定期的に行われている読書会の司会選出のニーズです。 @random-bot pick [group-name] [number] はこのニーズを満たすために実装していきます。 こちらは標準搭載されている Slackのカスタムレスポンス機能 を利用して、(Slackbotのレスポンスを複数パターン登録して)ランダム機能が実現されています。(これを初めて見た時、正直めっちゃ賢いな…!って思いました) ただ、この方法だと想定外のチャンネルで反応してしまうことがあるので、指定するキーワードには注意ですね⚠ 以上のようなニーズがあったので、今回のSlack Bot作る題材にちょうど良いや!と思ったのが作ろうと思った理由です。 さらに、Slackのリマインダー機能と組み合わせて、決まった時間に自動的にシャッフルした結果を出してくれるようになれば手間も減るので良さそうです🙆 実際にDeno環境でSlack Botを作っていく 前回の記事の最後で、Slack社が発表した Denoで提供されるSlack Appの次世代開発プラットフォーム のベータプラットフォームに参加できればそちらで開発する!と言っていたのですが、残念ながらベータプラットフォームへの参加は叶わなかったので、有志がNode.js用Slackライブラリを Deno向けに書き換えて提供してくれているライブラリ の方を利用していきます。 前回の記事がほとんどの項目をStep by Stepで書いたためにめちゃ長記事になってしまったのを反省したので、詳細は公式ドキュメントに譲り、ハマりどころとポイントをピックアップして書いていくことにします! また、今回のコードの全容は こちらのGitHub に公開しているので、もしご興味あればご覧ください👀 コミットメッセージ込みで試行錯誤の履歴が残っていますし、記事公開後もちょこちょこリファクタしたりする予定です(PRやIssueも歓迎です🙆) 実際に作ってみた所感 実際に作ってみてどうだったのよ?という感想を見に来られている方もいらっしゃるかと思うので、まず最初にそちらを書いておきます。 DenoでのSlack Botの開発はロジック部分は基本的にはNode.js上の開発体験とほぼ変わらない。でも、周辺知識の入れ直しで大変。 特に https://esm.sh や https://skypack.dev などの挙動を読み取りながらライブラリバージョンを指定するの辛かった。 まだまだエコシステムもライブラリも発展途上のため、詰まった時に情報がなくライブラリの中を読みに行って自力でデバッグになるのは大変。 逆にライブラリたちがURL管理なのでライブラリを読みに行く労力は少し減っている部分はある。 Deno Deployはまだまだベータ版。すぐに対応できなさそうな問題にぶち当たったので代替としてCloud Runを使った。 今回根幹として使っていたライブラリがDeno Deploy実行環境から提供されていないAPIを使っていて死ぬという事があった😢 GitHub Codespaces上でSlack Botを開発するのは、体験が良い面もあれば微妙な面もあった。 良い面👍: ポートフォワーディング機能で簡単にHTTPSのURLを公開できるので、SlackのWebHookやEvents APIの指定がngrokとか用意しなくていいのめちゃ楽。 良い面👍: 手元のPCのVSCodeからも、iPadのブラウザ上からも同じ環境を見れるので出先でちょこっと直しやすかった。 微妙な面🤔: ポートフォワーディングのタイムラグが体感1分ぐらいあるので、再起動時とかはSlack Bot側の反応が全くなくなってどこまでデータが届いているのか分からずにちょくちょく集中力が切れる。 微妙な面🤔: Denoのライブラリキャッシュ取得後の反映に微妙にラグがあって何度かファイルの読み込み直しやVSCode/ブラウザの再起動が必要だった。 今回公式ドキュメントから仕入れた知識のみで開発することで、開発自体は問題なく行えるものの所々辛い部分を見てきたので、次回以降は aleph.js や packup 、 Velociraptor 等のDeno周辺ツール群も組み合わせて実装を進めてみたいなと思いました🙆 当初の予定と実際のギャップ 合わせて、実際に作ってみてわかった、初期の自分の中での期待とギャップがあった部分(主に詰まった部分)もまとめていきます。 Deno Deployはimport-map機能にまだ対応していなかった。 Deno Deployで提供されていないAPIを使っているライブラリがあるとデプロイ時に???となる。 利用したライブラリの型情報が古く、 --no-check と一部 any を使ってしまった。 Deno Deployはimport-map機能にまだ対応していなかった これは先に調べておけば…。という話ではあるのですが、現時点ではDeno Deployは import-map には対応しておらず、基本的に deps.ts のパターンで実装する必要があります。 https://deno.com/deploy/docs/runtime-api#future-support ある程度、実装を進めたタイミングでデプロイしようとしてこれに気づいたため、完全に「にゃーん」となりました🐱 今更気づいた。Deno Deployやとimport_mapまだ対応されてへんやーん https://t.co/Nh2rcuXhXM — がっちゃん@えんじにゃ⛺ (@gatchan0807) 2021年12月8日 とはいえ、そこまで労力はかからないのでササッと deps.ts パターンに書き換えたのですが、再度デプロイしたところ次の問題が襲ってきました。 Deno Deployで提供されていないAPIを使っているライブラリがあるとデプロイ時に???となる ※あくまで状況証拠を並べてこれが原因っぽい。という推理のもと書いている点だけご容赦ください🙇 先程の import-map 問題を対応して再度アプリケーションをデプロイしてアクセスしてみたところ、相変わらず502を吐いてページが見れませんでした。 ログを見ると上記のようなエラーを吐いてアプリケーションの起動に失敗していました。 該当ファイルの該当行 を見にいってみたものの、エラーメッセージの reading 'deno' とは異なるデータを参照しているので違いそう…。 該当の 'deno' プロパティはファイルの中だと ${Deno.version.deno} しか使われていないけど、発生行数違うな…? と思いながら、色々調べて改めて https://deno.com/deploy/docs/runtime-api を見たところ、 It's different from Deno but aims to have similar APIs where applicable. の一文を見つけました👀 提供されている同様のAPIリストを見た感じ Deno.version の名前はない(あくまでDenoを模したランタイムなので提供されていない?)のと、上記の発生行が違う疑問は、実行時にコメントを削除されたファイルだとしたら計算が合う点から、 slack_bolt ライブラリの依存先の slack_web_api ライブラリ内の参照が問題で落ちているのだ。と自分の中で結論づけました。 今回利用している slack_bolt ライブラリは今回のアプリケーションの根幹に置いていたものだったこともあって、ある程度実装した上でこれを外して再度実装し直すのは時間がかかりすぎるし、別のライブラリでうまくいく保証は無い。と考え、 結果としてはDeno Deployを利用するのを諦める形で意思決定しました 。 利用したライブラリの型情報が古く、 --no-check と一部 any を使ってしまった 今回利用した slack_bolt には一部依存ライブラリ側での型情報の融通が出来ていない(多分一部のライブラリのバージョンアップ忘れが原因)バグがあったため、 --no-check のフラグを付けて実行し、型情報を無視するようにました( ここ 見る限りv1.17.0からは --no-check=remote が使えて外部ライブラリだけ型チェック無視になるみたいなので、Dockerfileの指定ではすでにこっそりこれを指定して使ってみてる👀) また、最終更新が10ヶ月前のライブラリだったため、現在のレスポンスには実際に返却されているデータが型情報に載っていなかったため、一度 any 化し、 as で型をつけ直すという対応を(型拡張するのをめんどくさがって)行いました。 https://github.com/gatchan0807/deno-slack-random-bot/blob/e6a655c74e25f8e765988e0f9c1f2ac1231737ad/src/app.ts#L21-L24 この辺りは次世代化が発表されたこのタイミングというのもあって、「既存の非公式ライブラリをメンテナンスするよりもSlack公式の次世代開発プラットフォームが全体に公開されるのを待つほうがコスパが良い」という思いになりPR作成は見送りましたが、本来であれば積極的にPR出して行きたいものですね👀 ざっくりとした実装の流れ 最後に、Slack Botが実際に動くようになるまでに行った対応の流れを紹介しておきます。 実際に動いた!の図 Slack Boltの公式チュートリアルを参考に、Slack Appを作成開始する slack.dev 基本的には各ステップのインポート部分の書き方を読み替えてステップを進めていく形で問題ないです🙆 Slackのワークスペースに関しては、権限があれば本番のワークスペースでやっても問題ないです。 ただ、自分はいきなり本番のワークスペースで試すのが怖いのと、管理者画面にどんな設定項目があるのかわからないと困ることがたまにあるので個人のワークスペースを遊び場として作成して、そこで実行できるかチェックしたりしています。 Codespaces環境内に環境変数を指定する 上記の公式チュートリアルの中で、環境変数にSlackのトークンを設定する作業がありますが、exportするだけではせっかく設定した環境変数がCodespacesが再起動されるたびに吹っ飛んでしまうので、提供されているCodespaces secretsの機能を使って環境変数を設定しましょう。 Codespaces の暗号化されたシークレットを管理する - GitHub Docs 下記のように設定できていればOKです🙆 そして、設定した環境変数を適用するにはCodespaceの再起動が必要なので、下記の通りVSCodeコマンドパレットを使って停止→起動を行いましょう。 Using the Visual Studio Code Command Palette in Codespaces - GitHub Docs 再起動後、 $ export | grep SLACK_BOT_TOKEN / $ export | grep SLACK_SIGNING_SECRET を叩いてきちんと環境変数に設定されていることを確認しましょう。 その後、指定のファイルを deno run してみて、問題なく起動できればOKです🙆 Events APIを使ってメンション&サブコマンドの命令を受けられるようにする 上記のチュートリアルを通して、Codespaces上で起動している環境に「リクエストを受け取る→Slackにレスポンスを返す」ことが確認できたので、 app.message メソッドを使って、メンションとサブコマンドの認識ができるようにしていきます。 細かい説明は省いてしまいますが、 app.message は第一引数に正規表現を設定することで正規表現に当てはまるメッセージを検出した場合に、第二引数の関数を呼び出される仕組みになっています。(いわばRouterやEventListnerのようなもの) そして、その関数の引数には各種リクエスト時に受け取ったtimestampやテキストの内容などと、レスポンス用の say 関数が詰まったオブジェクトが渡ってくるので、それらを利用してメンションから受け取ったデータの保存やメンションへの返事などを実装する形になります。 今回利用しているDeno用Bolt SDKの更新が10ヶ月前で止まっているためか、実際に返却されるオブジェクトと event に設定されている型情報が一致していなかったため、一度雑にany化してしまうというTypeScript的には禁忌なことをやってしまっていますが、本来は型情報を拡張して適用するようにしましょう🙏(時間があれば対応します…) import { SubCommandPattern } from "./subcommands.ts"; /** ~~ 省略 ~~ **/ // グループの入れ物の作成コマンド app.message(SubCommandPattern.create, async ({ event, say }) => { const _anyEvent = event as any; const text = _anyEvent.text as string; const user = _anyEvent.user as string; const [_botName, _subcommand, groupName] = text.split(" "); console.log("[INFO] Create: ", _anyEvent.text); await say(`<@${user}> 【 ${groupName} 】グループの作成が完了しました🎉`); }); /** ~~ 省略 ~~ **/ この対応の全貌は↓のコミットに https://github.com/gatchan0807/deno-slack-random-bot/commit/7e27039539013946be6663244ea4dd2762fe5b8d データストアを構築する ここまででSlack Botがコマンドを受け取って返信できるのを確認できたので、続いて作りたいコマンドを実現するために必要なデータの置き場所をつくります。 今回は本番では Deno Deploy Cloud Runを使う関係上、データストアを外に持つ必要があり、個人開発で使った経験があるFirebase(Firestore)を利用して行こうと思います。 Deno Deployの公式ドキュメントに似たような処理の書き方のチュートリアルがあるのですが、FirebaseのSDKバージョンがv8の頃の記法のまま(namespace形式)なので、基本的にはFirebaseの公式ドキュメントを参考にしながら、データ永続化処理を実装していきます。 firebase.google.com この辺り を参考にしながら、Firestoreの環境を構築し、Firebase Configを取得して、Slackのトークンと同じように環境変数に設定しちゃいましょう。 ※ FIREBASE_CONFIG は読み込み時に JSON.parse(Deno.env.get("FIREBASE_CONFIG")) のような形でparseされるので、JSONの記法に合わせてプロパティ名をダブルクォーテーションで囲むのと、改行の削除を行ったうえで環境変数に設定しています。 ライブラリを deps.ts に追加する export { App } from "https://deno.land/x/slack_bolt@1.0.0/mod.ts"; export { initializeApp } from "https://cdn.skypack.dev/@firebase/app@v0.7.10?dts"; export * from "https://cdn.skypack.dev/@firebase/firestore@v3.4.0?dts"; Skypackを利用する場合、 ?dts を設定しないと返ってくるライブラリの型情報がなくなってしまうので、忘れずに設定しましょう。(流石にFirebaseのSDKで型情報がないのは辛い) データ永続化の処理を実装する /** ~~ 省略 ~~ **/ console.log("[INFO] Execute add command:", _anyEvent.text); const docRef = doc(db, "groups", groupName); const docSnap = await getDoc(docRef); if (!docSnap.exists()) { console.info(`[INFO] The specified group name does not found.`); await say(`<@${user}> 【${groupName}】グループは登録リストに見つかりませんでした!`); return; } const groupRef = doc(collection(db, "groups"), groupName); const usersRef = collection(groupRef, "users"); await addDoc(usersRef, { userName: targetUserName, timestamp: timestamp, }); await say( `<@${user}> "${groupName}"グループに【${targetUserName}】を追加しました! Welcome.👏👏👏`, ); /** ~~ 省略 ~~ **/ この対応の全貌は↓のコミットに https://github.com/gatchan0807/deno-slack-random-bot/commit/8281e1a8b1cda83f3828fdc4347001c8c0d8495d ランダム処理を実装する 初期時点では、ランダム処理は雑にコピってきた arr.sort(() => Math.random() - 0.5); を使って実装していましたが、下記の記事で触れられている通り、ランダムといいつつある程度の偏りが発生してしまいます。 devblog.thebase.in そのため、記事に習ってフィッシャーイェーツのアルゴリズムを拝借して実装し直しました🙆 一通り実装できたら、デプロイする Deno Deployにデプロイするプランは残念ながら頓挫してしまったので、代わりにDockerに詰め込んでしまって、GCP上のCloud Runにデプロイするプランに切り替えました。 実際に処理を詰め込んだDockerfileは↓です https://github.com/gatchan0807/deno-slack-random-bot/blob/main/Dockerfile Cloud Runについての詳細な説明は こちらの紹介ページ に譲りますが、今回のユースケースで利用量であればデイリーの無料枠で事足りるレベルなので、Firestoreの無料枠とも相まって基本無料でSlack Botを作成できる見込みです。 - 200 万リクエスト(1 か月あたり) - 360,000 GB 秒のメモリ、180,000 vCPU 秒のコンピューティング時間 - 1 GB の北米からの下り(外向き)ネットワーク(1 か月あたり) cloud.google.com GitHubからの自動デプロイ設定はGUIからポチポチと タイトルのとおりですが、Dockerfileをリポジトリに置いた上で、Cloud Runの設定(もしくはCloud Buildの設定)でGitHubにCloud Build Appのインストールを行えばほぼ自動でGitHubへのPushをトリガーにした自動デプロイ環境を用意できます。 cloud.google.com Cloud Runの環境変数設定を忘れずに 実行環境の環境変数設定もGUIから実施できるので、設定を忘れずに。 忘れてしまうと 指定のポートでアプリケーション起動してないよ 的なエラーが発生してデプロイ失敗してしまいます🙏 cloud.google.com デプロイが完了したら、再度Slack Botの設定画面に行ってRequest URLをCloud Runのサービスに紐付いたURLに書き換えてしまいましょう🙆 Verifiedになれば、 @random-bot くんのバックエンドがCloud Runの本番環境に切り替わり、いつでもBotを利用できるようになっているはずです! これにてSlack Bot完成! まとめ もし前回のDeno基礎知識 + 環境構築編からここまでお付き合い頂いた方がいらっしゃれば、長々とお読みいただいて本当にありがとうございました!! Denoに対しての興味から調査を開始し、Slack Botの作成まで行なって記事にしてきましたが、久しぶりに知らないエコシステムをガッツリ触るということをやったので、とても楽しかったです🙆 全体的な感想としては、2年たったとはいえまだまだDenoも発展途上なんだな!という印象を受け、これから色々な進化を見せてくれるのを追うのが楽しみになりました👀 先にも上げたaleph.jsやpackupなどもこれから触りつつ、Node.jsエコシステムに比べてまだまだ参入余地が沢山あるDenoのコミュニティに機会があれば貢献していきたいなと思いました! 皆さまもぜひ、言語はJavaScript/TypeScriptで馴染みがあるけど、エコシステムは新しいが故にどことなく不思議な感覚になるDenoの世界に飛び込んでいただければと思います! 明日のアドベントカレンダーはBASE BANKのアプリケーションエンジニアの @glassmonekey さんの記事です!お楽しみに!
アバター
BASEアドベントカレンダー2021 11日目の記事です。 BASEアドベントカレンダー2021 DataStrategyチームの齋藤( @pigooosuke )です。 DataStrategyチームでは機械学習のモデルや集計結果をAPI経由で配信することが多く、社内の他チームと連携する際にも、どういうリクエストパラメータが存在し、どういうレスポンスが期待されるのかを共有しています。 弊チームでは、AWSのAPI Gatewayを利用しているので、API Gatewayの機能として提供されているドキュメントパーツを利用して共通フォーマットのAPI仕様書を管理しています。 markdown記法によるドキュメント管理に比べ、各APIのリクエストパラメーターやレスポンスモデルの視認性の改善や更新漏れが減るようになりました。 今回は、どのようにAPI仕様書を管理しているのかを紹介したいと思います。 *現在利用している Redoc のデモ画像 Redocのデモ画像 API Gateway ドキュメントパーツ API Gatewayでは各APIリソースに紐づくレスポンスモデル、クエリパラメータ、リクエストヘッダーなどに対して、ドキュメントを設定することができます。かつ、設定されたドキュメントを全て結合して、OpenAPI 3もしくはSwagger形式のjson/yamlを出力することが出来ます。 AWS デベロッパーガイド: API Gateway での API ドキュメントの表現 例えば、 /ysaito/recommend という「おすすめコンテンツを配信するAPI(GET method)」を提供していると仮定して、 このAPIでは、パラメータとして ユーザーID と 最大取得件数 を指定できるとします。 その場合、 /ysaito/recommend に対するドキュメント /ysaito/recommend に含まれるクエリパラメータ ユーザーID(user_id) に対するドキュメント /ysaito/recommend に含まれるクエリパラメータ 最大取得件数(max_size) に対するドキュメント をそれぞれ個別に管理することが出来ます。 今回のケースでは、コンソール画面上では以下のように登録されます。 合わせて、APIレスポンスに関しても、 API Gatewayのモデル管理でモデル登録&APIに紐付けしておくことで出力ファイルにレスポンスモデルを含めることが可能です。もちろん200以外のステータスコードの登録も可能です。 またこのドキュメントのリリースバージョンは、API Gateway本体のデプロイと同様にバージョン管理されているので、必要に応じて、過去のリリースバージョンにロールバックなども容易に可能になっています。 概略 全体のフローとして以下の図の通りです。 API GatewayにAPI定義をTerraformを利用して登録する ビルド用のrepositoryがAPI GatewayからOpenAPI/swagger仕様のAPI定義ファイルを取得する RedocというOpenAPI/swaggerファイルをHTMLファイルに変換するツールでビルドする S3にアップロードし、ドキュメント公開用サーバー上で公開する 処理詳細 API GatewayにAPI定義をTerraformを利用して登録する Document PartsはTerraformで管理することができるため、APIを作成する場合に合わせて登録するようにしています。 Terraform: Resource: aws_api_gateway_documentation_part ビルド用のrepositoryがAPI GatewayからOpenAPI/swagger仕様のAPI定義ファイルを取得する ビルド用のRepositoryのGitHub Actionsでは、AWS CLIにてAPI Gatewayからのドキュメント取得を以下のコマンドで取得しています。 aws apigateway get-export --parameters extensions='apigateway' --rest-api-id `<API ID>` --stage-name prod --export-type oas30 --accepts application/yaml `<出力先path>` RedocというOpenAPI/swaggerファイルをHTMLファイルに変換するツールでビルドする OpenAPI/swaggerファイルをHTML変換するツールはいくつか公開されているのですが、 視認性の観点で Redoc を採用しています。 おわりに 今回は、API仕様書の管理について紹介してみました。 ドキュメントの管理は、「昔の情報から更新されてない」「誰が管理するの?」といった長年の課題ではありますが、 API Gatewayの開発と連動して、自動でドキュメント発行できる仕組みを運用することで負荷軽減に繋がりました。 明日のアドベントカレンダーはフロントエンドエンジニアのがっちゃん( @gatchan0807 )の記事です。お楽しみに!
アバター
BASE BANKでエンジニアをしている @budougumi0617 です。 この記事はBASE Advent Calendar 2021 10日目の記事…ではなく、New Relic Advent Calendar 2021 10日目の記事です。 qiita.com TL;DR ソフトウェア開発チームのパフォーマンスを示す 4 つの指標がある https://www.devops-research.com/quickcheck.html デプロイの頻度 変更のリードタイム 変更障害率 サービス復元時間 New Relic Oneを使って「デプロイの頻度」を計測してみた Event APIとNRQLでデプロイ回数をNew Relic Oneのダッシュボードに図示できる https://docs.newrelic.com/docs/data-apis/ingest-apis/introduction-event-api ダッシュボード 自分たちチームのパフォーマンスはどれくらいなのか? 2009年に「10 deploys per day」というタイトルと共にDevOpsというキーワードが産まれて 1 から10年以上が経ちました。 10+ Deploys Per Day: Dev and Ops Cooperation at Flickr from John Allspaw www.slideshare.net 市場へサービスのリリースを繰り返し、より短時間で多くのフィードバックループを回すことがソフトウェア開発チームの生産性向上の鍵なようです。 この事実を具体的な指標にしたのが DevOps Research and Assessment(DORA)チーム の研究から導き出されたソフトウェア開発チームのパフォーマンスを示す 4 つの指標です( Google Cloudブログより引用 )。 デプロイの頻度 - 組織による正常な本番環境へのリリースの頻度 変更のリードタイム - commit から本番環境稼働までの所要時間 変更障害率 - デプロイが原因で本番環境で障害が発生する割合(%) サービス復元時間 - 組織が本番環境での障害から回復するのにかかる時間 LeanとDevOpsの科学[Accelerate] や クイックチェックツール では具体的なパフォーマンスレベルも示されています。 performance ( 表はさきほど同様 Google Cloudブログより引用 ) しかし、「 ではあなたのチームはどのレベルなの? 」と聞かれると「多分先週は毎日デプロイしてたはず…」なんて曖昧な回答しかできない状態でした。すでに他社ではこの質問に答えるためのアプローチ方法がいろいろ取られています。 生産性を可視化したい! / SUZURI's four keys https://speakerdeck.com/udzura/suzuris-four-keys-ce85acb4-bb98-4b6c-b6d8-0e62b5253872 普段「推測するな、計測せよ」「可観測性!!Observability!!O11y!!」なんて言っているのにこれではいけない…ということでチームの各指標をちゃんと計測しようと思いました。本記事ではまずは手始めにデプロイの頻度を計測した方法の紹介です。 デプロイ頻度を集計したい デプロイ頻度を計測するには様々なツールがあると思いますが、我々はNew Relicを使ってデプロイ頻度を集計することにしました。 New Relicを使ってデプロイ頻度を集計する理由は次のとおりです。 New Relicで集計している他のメトリクス情報と組み合わせて別の情報を引き出せる可能性がある 新たなツールを導入するよりも日々使うツールは少ないほうがよい 同様に確認が必要なダッシュボードは少ないほうがよい NRQLの便利さとグラフの作りやすさ サービスをデプロイしたという事実を集計する New Relicには Deployment Marker という他のパフォーマンス情報とアプリケーションのデプロイを関連付ける機能があります。 docs.newrelic.com qiita.com Deployment Marker を利用して Deployment イベントを記録すると、APMのグラフにデプロイタイミングを示す線がプロットされます。 New Relic One上でデプロイタイミングを記録しておくことでデプロイがメトリクスにどのような影響を及ぼしたのか解析できます。 Deploymentが一緒に描画されたメトリクス 最初はこの機能を使って記録した Deployment イベントをNRQLで集計すればよいと考えていました。 しかし、当時(そして2021年12月現在も、) Deployment はNRQLによって集計できるイベントはありませんでした。 デプロイ頻度を可視化するには集計してグラフ化する必要があります。そこで我々はデプロイのたびに独自のカスタムイベントを発行することにしました。 Event APIを利用した独自イベントの発行 New RelicのNRQLで集計できる type (NRQLの FROM に指定できるデータ)は Log や Transaction など多岐に渡ります。 そしてNRQLはユーザーが独自に定義した type のデータを集計することも可能です。独自定義のイベント( type )を作れるのがEvent APIです。 docs.newrelic.com Event APIを実行するために必要なもの Event APIを利用するために必要な情報は次の2点です。 License Key Account ID New Relic Oneを利用している場合、Event APIはLicense Keyを確認するだけで実行できます( 以前はInsert Keyが必要だったはずなのですが、2021年12月現在は非推奨 )。 docs.newrelic.com Licence Key自体は次のURLをクリックすると遷移できる「API Keys」の中から確認することができます。 同ページを見れば自分たち組織のアカウントIDも確認できるはずです。 one.newrelic.com Event APIの実行方法 Event APIの基本的な利用方法は先ほど記述したLicense Keyを利用して任意のJSONを curl コマンドで POST するだけです。 docs.newrelic.com 細かい仕様はありますが、任意のキー/バリューを含めることで記録する情報を自由に付与することができます。 今回は次のようなJSONフォーマットで独自イベントを発行することにします。 [ { " eventType ":" Deploy ", " deploy_user ":" ${DEPLOY_USER} ", " service ":" ${SEPLOY_SERVICE_NAME} " } ] たとえひとつのイベントだけでも配列で定義しないとうまくいきません。 時刻情報はAPI実行時の timestamp が自動的に含まれるので自前の定義は不要です。 「誰がデプロイしたのか?」も情報として含めていますが、これを集計すると謎の力学が発生してしまうのであくまで参考値として記録しておくだけにしておきます。 CircleCI上でEvent APIを実行する 上記のJSON定義をNew Relicのエンドポイントに送信すればイベントの記録が始まります。 我々のチームはCircleCIを使ってサービスをデプロイしているので、CircleCIのデプロイ用ワークフローの最後のステップにEvent APIを実行するようにします。 各サービスの .circleci/config.yml の中に次のjobを定義しました。 record-deployment : executor : name : aws-cli/default # curlコマンドが使えれば何でもよい steps : - setenv_prd # 環境変数のセットアップ - run : name : Send Deploy event to New Relic command : | curl -i \ -X POST 'https://insights-collector.newrelic.com/v1/accounts/${NEW_RELIC_ACOUNT_ID}/events' \ -H "X-Insert-Key:${NEW_RELIC_LICENSE_KEY}" \ -H "Content-Type: application/json" \ -d \ "[ { \" eventType \" : \" Deploy \" , \" deploy_user \" : \" ${CIRCLE_USERNAME} \" , \" service \" : \" ${CIRCLE_PROJECT_REPONAME} \" } \ ]" NEW_RELIC_ACOUNT_ID と NEW_RELIC_LICENSE_KEY という環境変数を設定する必要があります。 CIRCLE_USERNAME 、 CIRCLE_PROJECT_REPONAME はCircleCI実行時に自動的に設定される値です。 circleci.com あとはこのjobをデプロイフローの最後に設定しておきます。 デプロイフローの実行完了(デプロイ成功)のタイミングで Deploy というイベントが記録されます。 test-build-deploy : jobs : - test : filters : branches : only : main - build-push : requires : - test # .... - deploy-prd : requires : - approve-deploy-prd - record-deployment : requires : - deploy-prd Event APIを利用した独自イベントの集計 データが取得できるようになったならば後は集計してダッシュボードに掲載します。 New Relic Oneを開いたらQuery Builderに移動して Deploy イベントを集計していきます。 良いグラフができたら右下の「Add to Dashboard」ボタンを使ってダッシュボードにグラフを追加していきます。 参考までに我々が作成したグラフとNRQLのクエリを紹介します。 ダッシュボード 月間デイリーデプロイ数 SELECT count (*) FROM Deploy TIMESERIES 1 day FACET service SINCE 30 days ago 月間デイリーデプロイ数 まずは過去30日の日毎のデプロイ数です。サービス別にカウントしています。 NRQLは独自イベントに付与している独自属性を使って FACET を書くこともできます。 今週のデプロイ総数(先週比較) SELECT count (*) FROM Deploy SINCE this week COMPARE WITH 1 week ago ウィークリーデプロイ数 次に先週比較の数値です。 COMPARE WITH を使うと比較対象との比率まで簡単に示してくれます。 先週と比べて今週のデプロイ頻度は低いのか?気づかないうちにチームのパフォーマンスがブレてていないか?を確認したくて作りました。 週間デプロイ数 最後は直近4週間のデプロイ総数です。 SELECT count (*) FROM Deploy FACET weekOf(timestamp) UNTIL monday SINCE 5 week ago 週別デプロイ数 こちらもコンスタントにデプロイできているか?を確認するために作りました。 終わりに 本記事では「LeanとDevOpsの科学」などで示されているソフトウェア開発チームのパフォーマンスを示す4 つの指標のうち、デプロイ頻度をNew Relicを使って確認する方法を紹介しました。いくつか実際に利用している集計クエリも紹介しました。「こんな見方もできるのは?」「私たちのチームはこういう点をみている」とコメントある方は教えていただけると幸いです。 ソフトウェア開発チームのパフォーマンスの4つの指標ではデプロイ頻度の他に3つのキーが定義されています(再度 Google Cloudブログより引用 )。 デプロイの頻度 - 組織による正常な本番環境へのリリースの頻度 変更のリードタイム - commit から本番環境稼働までの所要時間 変更障害率 - デプロイが原因で本番環境で障害が発生する割合(%) サービス復元時間 - 組織が本番環境での障害から回復するのにかかる時間 デプロイ頻度以外については現状まだ計測できていません。 その他のキーについても可視化した際は本開発者ブログで共有させていただきます。 また、New Relic Oneでは最近サービスレベル目標(SLO)を設定、サービスレベル指標(SLI)、Error budgetを管理できるService Level Management機能もPublic betaになりました。 来年は4キーメトリクスの他に、SLIについても可視化していければなと思います。 docs.newrelic.com そして BASE Advent Calendar 2021 10日目の記事も書いたのでよかったら読んでみてください。 devblog.thebase.in 参考 LeanとDevOpsの科学[Accelerate] DevOpsの起源とOpsを巡る対立 エリート DevOps チームであることを Four Keys プロジェクトで確認する | Google Cloud Blog State of DevOps Report 2021を日本語で解説 https://puppet.com/resources/report/2021-state-of-devops-report https://twitter.com/t_wada/status/1420652243330605060 生産性を可視化したい! / SUZURI's four keys NewsPicksにCTOとして入社して1年でDX Criteriaを大幅改善した話 https://ubiteku.oinker.me/2015/07/01/devops%E3%81%AE%E8%B5%B7%E6%BA%90%E3%81%A8ops%E3%82%92%E5%B7%A1%E3%82%8B%E5%AF%BE%E7%AB%8B/ ↩
アバター
BASEアドベントカレンダー2021 10日目の記事です。 BASEアドベントカレンダー2021 10日目 BASE BANKでエンジニアをしている @budougumi0617 です。 マイグレーションファイルが含まれたPull Request(PR)が作られたとき、自動更新したER図をPRに追加するGitHub Actionsを作りました。 本記事では紹介するGitHub Actionsを利用すると次のようなメリットが得られます。 マイグレーションファイルをPRに出すだけでPRに更新されたER図が追加される 開発者は面倒なER図の更新作業から開放される レビューアはマイグレーションファイルを含んだPRをER図を見ながらレビューできるようになる プロジェクト関係者は常にメインブランチのマイグレーションファイルの状態と一致したER図を確認できる サンプルPR 自動生成したER図 TL;DR ER図はあれば便利だけれど運用しているとメンテが大変 k1LoW/tbls をGitHub Actionsで動かすとマイグレーションファイルのPull Request(PR)に自動更新したER図を追加できる RDBMSのスキーマをマイグレーションツールで管理しているのが前提条件 マイグレーションファイルの追加PRでマイグレーション後のER図を確認しながらレビューできる マイグレーションファイルを書くだけでER図をメンテする必要がなくなる サンプルリポジトリはこちらです。 github.com GitHub ActionsのYAMLを見たい方はこちらを参照してください。 https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/.github/workflows/tbls.yml k1LoW/tbls で自動生成しているER図はこのディレクトリにあります。 https://github.com/budougumi0617/sample_tbls_actions/tree/v0.0.1/schema/dbdoc マイグレーションファイルを含んだPRに対してGitHub ActionsでER図を更新しているPRのサンプルはこちらです。 https://github.com/budougumi0617/sample_tbls_actions/pull/2 ER図の必要性 Webアプリケーションを開発・運用しているならば、大抵の場合RDBMSを利用していると思います。 私のチームではRDBMSのテーブル定義を rubenv/sql-migrate によるマイグレーションによって管理しています。 github.com マイグレーションによるテーブル管理を行なっているとき問題になるのがER図の管理です。 マイグレーションファイルとER図を両方利用しているとどうやってER図を最新の状態と一致させるのかが運用上の課題になります。新規開発サービスだったり機能追加が活発なサービスの背後にあるRDBMSは毎週のようにマイグレーションが実施されます。このようなRDBMSの状態を手動でER図に反映し続けるのはかなりの労力が必要です。 また、マイグレーションファイルのレビューをするときはDDLとして正しいかだけではなく、DBの設計として妥当であるかもレビューする必要があります。そのためには他のカラム、他のテーブルを含めてマイグレーション後の姿を俯瞰的に確認する必要があります。 ER図を運用する時の課題 前述したとおりER図はあったほうが良いですがER図を作り維持し続けるには様々な課題があります。 本番環境のテーブルの状態とER図がかならずマッチしている保証がない マイグレーションファイルが真か?ER図が真か? ER図が古いだけなのか?それともマイグレーションが意図通り行われなかったのか? マイグレーションファイルを書くのが先か、ER図を書くのが先か 俯瞰的にレビューするならばER図がほしい オレは ADD COLUMN したいだけなんだ… マイグレーションをする側からすると少しDDLを書くだけで済むはずなのにマイグレーション結果に影響しないER図の修正まで気を使うのは大変… 単純なカラム追加だけならばよいですが、外部キー制約付きのテーブル追加のようなDDLを書く場合ER図の更新は非常に煩わしいものでした。 k1LoW/tblsという救世主 メンテフリーなER図を実現するための救世主が github.com/k1LoW/tbls です。 github.com qiita.com tbls のいいところそれだけで2記事くらい書けるので省略しますが、シンプルに設定した接続先のDBのスキーマ情報を(デフォルトでは)MarkdownとSVGファイルに出力してくれます。 自動生成したER図 CI-Friendly とREADMEにかかれている通りシングルバイナリで svg ファイルまで生成してくれる点もとてもありがたいです。これ系のツールでありがちな「まずは Graphviz をインストールします」ということもないのでCI上でも簡単に実行できます。 しかし、 tbls は「データベースに接続してER図を自動生成するツール」です。 つまりマイグレーションを当てた状態のデータベースを用意しないと tbls を使うことができません。 マイグレーションの妥当性を確認する際もER図を見たいのに、マイグレーションしないとER図が生成できません。 そこでマイグレーションファイルを含んだPRが作られるたびにActions上でRDBMSを起動するようにActionsを書きました。 マイグレーションを含んだPRでER図を自動更新するGitHub Actions 今回作成したGitHub Actionsの全容は以下のとおりです。 https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/.github/workflows/tbls.yml name : update er graph on : pull_request : paths : - schema/sample/** - schema/tbls.yml jobs : tbls : name : generate-and-push runs-on : ubuntu-latest services : mysql : image : mysql:5.7 options : --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10 ports : - 3306:3306 env : MYSQL_ALLOW_EMPTY_PASSWORD : yes MYSQL_DATABASE : sample MYSQL_USER : sample MYSQL_PASSWORD : sample steps : - name : Checkout uses : actions/checkout@v2 with : ref : ${{ github.event.pull_request.head.ref }} token : ${{ secrets.PERSONAL_ACCESS_TOKEN }} - name : Setup go uses : actions/setup-go@v2 with : go-version : '^1.17.1' - name : Execute migration run : | go get -u github.com/rubenv/sql-migrate/sql-migrate make up ENV=ci working-directory : ./schema - name : Execute tbls run : | curl -sL https://git.io/use-tbls > use-tbls.tmp && . ./use-tbls.tmp && rm ./use-tbls.tmp tbls doc -f working-directory : ./schema # tbls実行後、差分有りもしくは新規ファイルの数をカウントする - name : Count uncommit files id : check_diff run : | git status --porcelain | wc -l file_count=$(git status --porcelain | wc -l) echo "::set-output name=file_count::$file_count" working-directory : ./schema - name : Commit ER graph # 更新したER図をPRにコミットする if : ${{ steps.check_diff.outputs.file_count != '0' }} run : | git config user.name github-actions git config user.email github-actions@github.com git add . git commit -m "generate er graphs from actions" git push working-directory : ./schema # PRへ自動コミットしたらPRにコメントしておく - name : Report commit on pull request if : ${{ steps.check_diff.outputs.file_count != '0' }} uses : actions/github-script@v4 with : script : | github.issues.createComment({ issue_number : context.issue.number, owner : context.repo.owner, repo : context.repo.repo, body : 'Actions committed new ER files🤖' }) Actionsの主な流れは次のとおりです。 PRにマイグレーションファイルの変更が含まれていた場合実行する 事前にMySQLを起動しておく マイグレーションファイルを実行する tbls コマンドを実行してER図を生成する もし既存のER図と差分があったら、PRに対してその差分をコミットする PRにマイグレーションファイルを更新した旨をコメントする ひとつひとつ該当するYAMLの抜粋と一緒に解説していきます。 PRにマイグレーションファイルの変更が含まれていた場合実行する GitHub Actionsは特定のディレクトリに更新があったときのみ起動する設定が可能です。 私たちのリポジトリはアプリケーションコードと一緒にマイグレーションファイルがコミットされていますが、この設定によりアプリケーションコードを変更するだけのPRで意味もなくCIが実行されることを防ぎます。 on : pull_request : paths : - schema/sample/** - schema/tbls.yml 事前にMySQLを起動しておく GitHub Actionsはワークフロー中にサービスコンテナという名前でDBなどのDockerコンテナを起動しておく事ができます。 docs.github.com 今回は次のような宣言でMySQLを起動しておきます。 mysql : image : mysql:5.7 options : --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10 ports : - 3306:3306 env : MYSQL_ALLOW_EMPTY_PASSWORD : yes MYSQL_DATABASE : sample MYSQL_USER : sample MYSQL_PASSWORD : sample コードをクローンしてGitHubの設定をする コードをクローンしてGitの設定も行ないます。ここで設定したGitの設定は後半の更新したER図のコミット・pushにも利用されます。 - name : Checkout uses : actions/checkout@v2 with : ref : ${{ github.event.pull_request.head.ref }} token : ${{ secrets.PERSONAL_ACCESS_TOKEN }} マイグレーションファイルとアプリケーションコードを同じリポジトリに入れている場合、Actionsで利用するトークンはデフォルトのトークンではなく、払い出したトークンを利用したほうがよいです。 これはデフォルトのトークンでPRに対してコミットをpushすると、そのコミットに対しては(無限ループ事故の防止のため)Actionsが起動しないためです。 docs.github.com 本記事で紹介しているActionsは起動しなくても問題ありません。しかし、アプリケーションコードとマイグレーションファイルを一緒にPRで変更していた場合ActionsからのpushでActionsが起動しないことになります。 ただ起動しないだけならばいいのですが、 CIのチェックステータスも消えるため、他のActionsでfailしていた状態も消えます 。 PRのBranch Protection RuleでCIのステータスを利用しているならば必ずトークンを払い出して再度Actionsが実行されるようにしておきます。 マイグレーションファイルを実行する 先ほど示したMySQLに対してマイグレーションを実行しておきます。 これでER図を生成したいDBがGitHub Actions上に構築されます。 - name : Checkout uses : actions/checkout@v2 with : ref : ${{ github.event.pull_request.head.ref }} token : ${{ secrets.PERSONAL_ACCESS_TOKEN }} - name : Setup go uses : actions/setup-go@v2 with : go-version : '^1.17.1' - name : Execute migration run : | go get -u github.com/rubenv/sql-migrate/sql-migrate make up ENV=ci working-directory : ./schema make up ENV=ci コマンドは次のMakefileの設定と、 dbconfig.yml で成り立っています。 https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/schema/Makefile ENV := "local" up: ## Apply migration files sql-migrate up -env= $(ENV) -config= dbconfig.yml https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/schema/dbconfig.yml ci : dialect : mysql datasource : root@tcp(127.0.0.1:3306)/sample?parseTime= true dir : sample tbls コマンドを実行してER図を生成する tbls をインストールして実行します。CIフレンドリー過ぎてやることはこれだけです。あっけないですね… - name : Execute tbls run : | curl -sL https://git.io/use-tbls > use-tbls.tmp && . ./use-tbls.tmp && rm ./use-tbls.tmp tbls doc -f working-directory : ./schema tblsの設定自体も接続情報と sql-migrate が利用しているマイグレーション管理用のテーブルを除外するようにしているくらいです。 https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/schema/tbls.yml dsn : mysql://root@127.0.0.1:3306/sample?parseTime= true er : comment : true distance : 9 exclude : - gorp_migrations もし既存のER図と差分があったら、PRに対してその差分をコミットする 先ほど実行した tbls によってER図のファイルに何らかの変化があった場合、gitのステータスに差分が出ます。 一つでも差分を見つけたらコミットを作り、PRのブランチに対してpushします。 このようにしておくことで空コミットが作られたり、Actionsが無限にループ実行することありません。 - name : Count uncommit files id : check_diff run : | git status --porcelain | wc -l file_count=$(git status --porcelain | wc -l) echo "::set-output name=file_count::$file_count" working-directory : ./schema - name : Commit ER graph # 更新したER図をPRにコミットする if : ${{ steps.check_diff.outputs.file_count != '0' }} run : | git config user.name github-actions git config user.email github-actions@github.com git add . git commit -m "generate er graphs from actions" git push working-directory : ./schema PRにマイグレーションファイルを更新した旨をコメントする 最後にPRにER図を更新した旨を書くコメントを残して終わります。 - name : Report commit on pull request if : ${{ steps.check_diff.outputs.file_count != '0' }} uses : actions/github-script@v4 with : script : | github.issues.createComment({ issue_number : context.issue.number, owner : context.repo.owner, repo : context.repo.repo, body : 'Actions committed new ER files🤖' }) あとは PERSONAL_ACCESS_TOKEN と言う名前でGitHubパーソナルアクセストークンを保存しておけば次のPRよりER図が自動生成されるようになります。 自動生成したER図が追加されたサンプルPR 上記のGitHub Actionsを使って更新されたPRが次になります。 github.com このPRではマイグレーションファイルが一つ追加されています。 PRに対してGitHub Actionsが実行された結果、更新されたER図が追加されています。 サンプルPR 開発者は何もせずマイグレーション後のER図をPRに追加できました。 レビューアは更新されたER図を確認することで、表で示されたテーブル構成や図示されたリレーションを見ながらレビューすることができます。 https://github.com/budougumi0617/sample_tbls_actions/blob/migrate-card/schema/dbdoc/cards.md このPRが妥当ならばマイグレーションファイルと更新されたER図が main ブランチにマージされます。 終わりに 以上のGitHub Actionsにより、我々の開発では以下の開発者体験が実現できました。 GitHub上でER図をいつでもテーブル定義を確認できる ER図とマイグレーション結果が一致していることを保証する 開発者はER図を書く必要がない マイグレーション後のER図を確認しながらマイグレーションファイルのPRをレビューできる 明日は @pigooosuke さんです!
アバター
BASE Advent Calendar 2021 9日目の記事です。 フリーランスのフロントエンドエンジニア 坪内です。 BASE のお手伝いをさせていただくようになって 1ヶ月が経ち、色々見えるようになってきた中で最も気になっていた点の 1つが、 「 HMR されていない 」 でした。 BASE の Web フロントエンドは webpack でビルドされているのですが、 ローカルの開発環境が Docker 上で動いている事もあってか、残念ながら HMR はされていない状況でした。 「Docker 環境だから無理?」 「“ webpack HMR docker ” とかでググると色々出てくるし出来そうな?」 さっそく取り掛かってみました。 HMR とは? 一応、HMR の説明をしておきます。 https://webpack.js.org/guides/hot-module-replacement/ H ot M odule R eplacement の略です。 js モジュールの変更を、ブラウザをリロードすること無しに動的に差し替えて反映する webpack の機能の事を指します。 歴史は割と古く、webpack 1系の頃から試験的に導入されていました。 似たものとして、ファイルの変更を検知してブラウザを自動でリロードしてくれる liveReload があります。 仕組み HMR はおおよそ以下の流れで実現されています。 webpack-dev-server が HMR 用のコードを含めて js を返す WebSocket に接続 webpack がファイルの変更を検知したら差分ビルド 変更があったことを WebScoket で通知 変更差分情報を取得 変更のあったモジュールを取得して動的に置き換え 置き換えができない場合は liveReload される 構成 一般的な SPA の場合、起動した webpack-dev-server にブラウザからアクセスし、API などへのアクセスは必要に応じてリバースプロキシするという構成を取ります。 起動した webpack-dev-server ( http://localhost:3000 など)にブラウザでアクセス webpack でビルドされた html を返却 webpack でビルドされた js を返却 webpack で管理していないリソースは backend へリバースプロキシ 当初 BASE のサービスは CakePHP を使用しているので、上記と同様に前段に webpack-dev-server を配置して CakePHP へリバースプロキシする必要があると思っていました。 また、ローカル Docker 環境は架空のドメイン(ここでは仮に *.base.test )上で動くようになっているので、webpack-dev-server へもその架空ドメインとしてアクセス出来るようにしてあげる必要があると考え、Docker 環境内に webpack-dev-server を建てました。 これはこれで動くので間違いではないのですが、 Docker上で動かすので単純に重くなる Docker上で webpack-dev-server を使うかどうかを切り替えるのが面倒そう node-gyp によるネイティブパッケージに依存していると、Docker上で yarn install する必要がある 内部のライブラリへの yarn link が厳しそう などの問題があり、色々と調整や解決が大変そうでした。 助言 そんな中、ある助言をもらいました。 アセットだけホスト側の dev-server に繋げないんだろうか ❗❗❗ リバースプロキシしなければならないと思い込んでしまっていましたが、 CDN から js や css を取得して動かせるわけなので、webpack-dev-server を CDN のように扱うこともできるのではないか? 助言後 webpack-dev-server はホスト側( http://localhost:3081/ )で起動する CakePHP で js をロードしている箇所の URL を http://localhost:3081/ に向ける 以上。 js の向き先を変えるだけで良くなったので、docker-compose には影響を及ぼさず、とてもシンプルな仕組みになりました! webpack の設定 最終的な webpack の devServer 設定は以下です。 devServer: { allowedHosts: [ '.base.test' ] , client: { overlay: true , webSocketURL: 'ws://localhost:3081/ws' , } , headers: { 'Access-Control-Allow-Origin' : '*' , } , port: 3081, proxy: { '*' : 'http://localhost:8081/' , } , static : false , watchFiles: [ 'app/**/*' ] , } , 考慮した点 CORS を解決する Docker 環境 https://*.base.test から見ると、 http://localhost:3081 はクロスオリジンなアクセスになるので、 devServer.headers として CORS ヘッダーを返す必要があります。 また、webpack-dev-server へのアクセスを許可するドメインを devServer.allowedHosts に追加する必要がありました。 devServer: { allowedHosts: [ '.base.test' ] , headers: { 'Access-Control-Allow-Origin' : '*' , } , } , HMR の WebSocket 接続先や差分取得先などを webpack-dev-server に向ける html は https://*.base.test/ から返されており、そのままだと HMR 用の WebSocket などもそちらを向いてしまうので、 devServer.client.webSocketURL で ws://localhost:3081/ws へ向けます。 devServer: { client: { webSocketURL: 'ws://localhost:3081/ws' , } , } また、 webpack-manifest-plugin を使用している関係で、 output.publicPath を指定していたのですが、 output: { publicPath: '/' , } , これが差分の取得先に使われているので、webpack-dev-server で起動している場合は localhost:3081 へ向くようにする必要がありました。 output: { publicPath: env.WEBPACK_SERVE ? 'http://localhost:3081/' : '/' , } , webpack 管理外の js への対処 webpack でビルドされていない古い js が残っており、そのままだとそれらへのアクセスが 404 になってしまうので、それ用に devServer.proxy で CakePHP へリバースプロキシしています。 devServer: { proxy: { '*' : 'http://localhost:8081/' , } , } , また、それらを含め CakePHP 側に変更があった際に liveReload されるよう、 devServer.watchFiles を設定しています。 devServer: { watchFiles: [ 'app/**/*' ] , } , CakePHP で js の向き先を変える HtmlHelper を使用して、 <? = $ this -> Html -> script ( 'foo' ) ?> のように出力していれば、 App.jsBaseUrl を設定する事で向き先を変えることができます。 <?php Configure :: write ( 'App.jsBaseUrl' , 'http://localhost:3081/js/' ) ; これをローカルでのみ有効になる Config ファイルに記述して、必要に応じて切り替えるようにしています。 おわりに Docker 環境でもシンプルな形で webpack-dev-server で HMR する事ができました! この形で HMR できるとなると、 Proxyman や Charles などの proxy と組み合わせてみたりなど色々応用もできそうですね。 明日は @budougumi0617 さんです!
アバター
Chromatic とは Chromatic とは、Storybook のメンテナーが作成している Storybook 用のツールです。Storybook をビルドして公開したり、ストーリーごとのスクリーンショットを撮影し、差分を比較してくれる機能を備えています。 Chromatic を使うことにより、UI の予期せぬ変更を事前に検知することができます。本記事では Chromatic の導入、活用方法をご紹介します。 なお、BASE 社では社内の UI コンポーネントライブラリである BBQ で Chromatic を導入、活用しています。その経緯はアドベントカレンダー15日目に公開する記事でご紹介します。 Chromatic をプロジェクトに導入する サンプルプロジェクトを作成する 今回は Storybook 公式で用意されている サンプルプロジェクト を利用します。 プロジェクト作成にあたり、以下のコマンドを実行します。 $ npx degit chromaui/intro-storybook-vue-template taskbox $ cd taskbox $ yarn これで taskbox の作成が完了しました。 次に以下のコマンドを実行して Storybook を立ち上げ、どのようなコンポーネントがあるか確認します。 $ yarn storybook Storybook の画面 Button や Header、Page といったコンポーネントが存在することが確認できました。 サンプルプロジェクトに Chromatic を導入する 次に、Chromatic にログインします。「Add project」ボタンから新規プロジェクトを追加できます。 Chromatic の管理画面 GitHub レポジトリかプロジェクト作成のどちらかを選択します。今回は右側のプロジェクト作成を選択しました。 プロジェクト作成用のボタンが二つ並んでいる すると、Chromatic 用のトークンが発行されます。 Chromatic のコマンドとトークンが表示されている 画面に表示されているコマンドを taskbox ディレクトリで実行します。 $ npx chromatic --project-token=your-token すると、以下のエラーが表示されます。 $ npx chromatic --project-token=your-token Chromatic CLI v6.1.0 https://www.chromatic.com/docs/cli ✔ Authenticated with Chromatic → Using project token '********da59' ✖ Retrieving git information → Chromatic only works from inside a Git repository. プロジェクトを git 管理下に置いていないため Publish に失敗します。このため、適当なレポジトリを作成して push しましょう。 レポジトリに push した後、再度 chromatic コマンドを実行します。 $ npx chromatic --project-token=your-token Chromatic CLI v6.1.0 https://www.chromatic.com/docs/cli ✔ Authenticated with Chromatic → Using project token '********da59' ✔ Retrieved git information → Commit 'b6eb197' on branch 'main'; no parent commits found ✔ Collected Storybook metadata → Storybook 6.4.0 for Vue3; supported addons found: Actions, Essentials, Links ✔ Storybook built in 22 seconds → View build log at /Users/panda/playground/vue/test/taskbox/build-storybook.log ✔ Publish complete in 14 seconds → View your Storybook at https://61aef69049f6bb003ac71914-qocfdyaegr.chromatic.com ✔ Started build 1 → Continue setup at https://www.chromatic.com/setup?appId=61aef69049f6bb003ac71914 ✔ Build 1 auto-accepted → Tested 9 stories across 4 components; captured 8 snapshots in 9 seconds ✔ Build passed. Welcome to Chromatic! We found 4 components with 9 stories and took 8 snapshots. ℹ Please continue setup at https://www.chromatic.com/setup?appId=61aef69049f6bb003ac71914 ログの中で表示されている https://61aef69049f6bb003ac71914-qocfdyaegr.chromatic.com/?path=/story/example-introduction--page という URL にアクセスすると、Storybook を閲覧できます(プロジェクトを削除したため、現在は表示されません)。 Chromatic のトップページに taskbox プロジェクトが追加されていることが確認できます。 Chromatic のプロジェクト一覧 taskbox プロジェクトを見る taskbox プロジェクトをクリックすると、次の画面に遷移します。 Chromatic の Build ページ メニューの内容は以下の通りです。 Builds:過去のビルド履歴を閲覧できる Library:Storybook のストーリー単位でコンポーネントを表示できる Manage:Slack 連携や URL からアクセスできる Storybook の公開範囲設定、コラボレーション相手の選択などができる メニューの中から Builds を開いてみましょう。 Builds の中を覗く Builds をクリックします。すると、以下の画面が表示されます。 コンポーネント一覧 Storybook のファイル単位ではなく、ストーリー単位でコンポーネントが区切られています。 一番上の Button をクリックします。すると、Button コンポーネントのキャプチャが撮影されていることがわかります。 ボタンコンポーネントのスクリーンショット また、右下に DOM が表示されていることがわかります。こちらは Header コンポーネントです。 ヘッダーコンポーネントのコードが表示されている 差分を追加する 次に、差分確認をします。Header コンポーネントと Page コンポーネントのコードを変更します。 変更点は以下の3つです。 Header で右上のボタンの位置を入れ替え Page のタイトルの Storybook を Chromatic に変更 Page の文章の一部を削除 変更前 変更後 変更前の Page コンポーネント Page コンポーネント変更後 この変更を加えた後、再度 Chromatic でビルドします。 $ npx chromatic --project-token=your-token すると、ビルド2 が追加されました。 Build 一覧 差分をレビューする ビルド2 を表示すると、変更があるコンポーネントがピックアップされています。 Build 2 のコンポーネント一覧 一番下の Page の「Logged Out」を表示してみます。すると、コンポーネントの Build1 と Build2 の見た目とコードの差分がハイライトされています。 単体 比較 Page コンポーネントの差分ハイライト Page コンポーネントの変更前と変更後が比較されている もし差分が意図通りであれば、右上の Accept ボタンをクリックします。そうでなければ Deny をクリックして、コードを変更した人に再度確認して貰います。 変更点を Accept するボタンが表示されている Accept をするのはコードを改修した人ではなく、別の人にレビュワーとしてクリックしてもらうのが望ましいです。BASE の BBQ の開発フローでも、コードレビューをするレビュワーが Accept や Deny をすることにしています。 全てのコンポーネントを Accept すると Build2 の表示が緑になります。 Build 2 のコンポーネントの差分が全て緑色になっている GitHub Actions で Chromatic を実行する Chromatic は GitHub Actions で実行できます。実際、 公式で yml の書き方が紹介されています。 # .github/workflows/chromatic.yml # Workflow name name : 'Chromatic' # Event for the workflow on : push # List of jobs jobs : chromatic-deployment : # Operating System runs-on : ubuntu-latest # Job steps steps : - uses : actions/checkout@v1 - name : Install dependencies run : yarn - name : Publish to Chromatic uses : chromaui/action@v1 # Chromatic GitHub Action options with : token : ${{ secrets.GITHUB_TOKEN }} projectToken : ${{ secrets.CHROMATIC_PROJECT_TOKEN }} CHROMATIC_PROJECT_TOKEN は、冒頭で取得したトークンのことです。このトークンをレポジトリの Secrets に設定すると、GitHub Actions から読み取ることが可能です。 Chromatic を CI に組み込むことで、PR から Chromatic のステータスをチェックすることができます。 GitHub の CI の結果一覧 差分がなかったり、あるいは全て Approve された場合には緑のチェックマークがつきます。差分がある場合は、黄色い丸のマークがつきます。 これで Chromatic をレビューの工程に組み込めました。 Chromatic のその他の機能 Chromatic は差分を表示する画面にコメントできたり、ビルド結果を Slack に通知したり、コメントをメール通知可能です(コメントの Slack 通知は未対応)。 また、プランをアップグレードすると、Chrome だけではなく Firefox、IE11 でのスクリーンショットを撮影可能になります。BASE では Starter プランに加入しています。 Chromatic の価格プラン一覧 また、Chromatic 上の Storybook に対して GitHub 認証を設定できるので、万が一 URL が漏れて外部の人が覗いてしまうということも防止できます。 おわりに Chromatic はセットアップや GitHub 連携がとても簡単です。無料プランもあるので、 Storybook を活用している会社・組織・チームであればぜひ活用を検討してみてはいかがでしょうか。
アバター
この記事はBASE Advent Calendar 2021の8日目の記事です。 こんにちは Slackの好きなショートカットは Shift + Esc の横山です。 SRE Groupに加わって4ヶ月が経ちました。 こちらの記事が書かれてからも4ヶ月が経ちました。 devblog.thebase.in ↑を未読の方がいましたらぜひぜひ読んでください。 同じ4ヶ月という事で振り返ってみたいと思います。 ちなみにこの記事を読む前に入社を決めていますがカジュアル面談の時に同じように説明して頂けました。 チームで大事にしていることは? ・「信頼性=ユーザの期待値を超え続けること」としてこれを維持し続ける ・BASEの機能等々の価値を高めるための時間を多く作っていけるようにする この2つを大事にしていると書いてあります。 私はOKRを決めるのが苦手なので、このようにチームで大事にしているものがわかっていると目標が立てやすかったです。 チームの働き方は? 3ヶ月経ちましたが、COVID-19の影響でリモートで業務を行なっております。 基本的にはSlackやZoomを使用してコミュケーションを取っています。 また、朝会(月〜金、12:00〜12:30)とSync SRE(隔週金曜)を行なっていると上記の記事には書かれていましたが、現在は少し変わっています。 Sync SREが隔週金曜から月〜金の16:30〜17:00になりました。 変わった理由ですが、これは私が入社するにあたって朝会だけではなく夕方にも情報共有や質問をしやすいようにと配慮してくださいました。 Slackで気軽に聞くことが出来る環境が整っているとはいえ、文字するのと口にするのとでは違うと思っているのでこういう場を設けてもらえたのはとても感謝しています。 ※Sync SREとは 進捗の確認、相談をしてチームに情報共有します。 チームの業務は? トイル削減/サービスの運用効率化のためのTerraformを使用してInfrastructure as Codeが進んでいます。 各開発プロジェクトからのAWS上のインフラリソースの作成、変更の依頼があり対応をしていると本来進めたい業務が滞る事があります。 なので、それら依頼からインフラリソースのコード化をしています。 このまま進めると作成、変更の依頼から作成、変更が記述されたPull Requestをレビューするという形になる予定です。 ただ、いきなりTerraformを書くことを強要するのは正しくないので、他チームや開発プロジェクトと上手に連携してSREだけではなく、みんなでInfrastructure as Codeが出来るとより良くなると思っています。 おわりに 前職ではチームがなかったので、意見の交換をする機会がありませんでした。 ですので、SREチームに転職してみて色んな考えに触れ、私の考え方のアップデートする事が出来たのではないかと思います。 まだ大きな成果は出せていませんが、コツコツと積み重ねて気がついたら大きくなるように貢献していけたらと思います。 拙い文章ではありましたが、最後までお付き合い頂きありがとうございました。 明日は「Docker 環境から webpack-dev-server に繋いで HMR する」になります。楽しみですね。 おまけ Youtubeで「大規模サービスの運営を支える、BASEのSREとして働く醍醐味と目指すチームのあり方」という内容でインタビュー動画を公開しています。 宜しければご覧ください。 【前編】大規模サービスの運営を支える、BASEのSREとして働く醍醐味と目指すチームのあり方 【後編】大規模サービスの運営を支える、BASEのSREとして働く醍醐味と目指すチームのあり方 また、少しでも興味が湧きましたらさくっとカジュアル面談が出来ますのでいつでもご連絡ください。 こちらよりお願いします 応募要項なども こちら にありますのでどうぞ。
アバター
この記事は BASE アドベントカレンダー と Looker アドベントカレンダー 8 日目の記事です。 はじめに BASE BANK 株式会社にて事業開発を担当している猪瀬 ( @Masahiro_Inose )です。 私達のチームでは、BASE ショップを運営しているショップオーナー様が簡単に資金調達をできる「 YELL BANK 」というサービスの開発・運営しています。 thebase.in 今回の記事は以下の二部構成となります。 前半部分は私から Looker という BI ツールを使って、サービス利用者の利用状況や関連情報を一元的に把握できる、「ショップカルテ」なるものを作成したことについて紹介します。 後半部分は Looker で扱いやすくするためのデータの加工を担当した永野( @glassmonekey )から、データ基盤周りやデータ加工の工夫した部分について解説します。 ちなみに過去の記事の宣伝ですが、弊チームでは BigQuery を活用して簡易的な CRM を作ったりもしました。 devblog.thebase.in ショップカルテとは 「ショップカルテ」とは、「YELL BANK」というサービスを利用しているショップの「サービスの利用状況」「アクセスログ」「ショップの運営状況(GMV や在庫の変動など)」等の複数のデータを 1 つのダッシュボードで一元的に把握できる仕組みのことです。ショップの健康状態を把握できるようなイメージから「カルテ」と呼んで愛用しています。 なぜ作ったのか プロダクトの機能改善を進めていく上では、「利用してくれているユーザーを ありありとイメージできるようになること 」が大変重要だと思っています。 例えば以下のようなことが具体的なアクションとして挙げられます。 BASE ショップを自分で作って販売をしてみるといった、いわゆるドッグフーディングをする 積極的にユーザーインタビューをしたり、オーナーさん直接会いにいく ショップ名を見るだけで「どのようなオーナーさんが、どんな気持ちで、どんな理由でサービスを使ってくれているか」が浮かんでくるようになると、日々のサービス改善案の仮説精度が高まります。 しかし、私が携わっている「YELL BANK」というサービスは、将来債権の買取という仕組みによってショップの資金調達を実現するという、世の中にあまり事例のないサービスです。自分自身も気軽に毎日利用できるようなサービスではないので、どうしてもユーザーの体験が想像しにくいという特徴がありました。 また、何かサービス改善のヒントになりそうなオーナさんの行動を発見した際に、いざその行動について分析を進めるとなると以下のような作業が必要でした。 複数のクエリを書いてデータを抽出 スプレッドシートでデータを加工 比較 分析 etc … これらの工数がかかってしまうこともオーナーさんの体験を想像しにくい問題に拍車をかけていました。 そこで、「ショップカルテ」を作成し、閲覧したいショップの ID をフィルター指定するだけで、「サービスの利用状況」や「ショップの運営状態」などの複数のデータをダッシュボード上にまとめて可視化する仕組みを整えました。これによりショップの行動とその理由をパッと簡単に把握しやすくしました。 「ショップカルテ」の構成 サービスの利用状況に関連する複数のデータを 1 つのダッシュボードにまとめています。 4 つのデータカテゴリによって構成されています。 閲覧できるデータカテゴリ 目的 基本的なサービス利用情報 利用開始日や累計の利用金額など、基本情報の把握 各種機能の利用推移、履歴 「YELL BANK」をどの程度使いこなしているかの把握 サービスページアクセスログ推移 サービスへの興味関心、利用熱意の移り変わりの把握 ショップの販売状況推移 ショップの運営状況とサービス利用状況の相関把握 を直感的にパッと把握できるようになります。 ※実際の Looker ダッシュボードの画像。データ部分はマスクしております。また、本当は縦に繋がっているのですが、画像が縦長になってしまうので半分に分割して横に並べています。 これによって、「このショップはなんでこういう動きをしたんだろう」という疑問が浮かんだ時に、ショップの ID を指定するだけでパッと仮説につながるインサイトを得ることができるようになりました。 データ基盤の構成 こんにちは、BASE BANK 株式会社にてアプリケーションエンジニアをしている永野( @glassmonekey )です。 私からはデータ基盤の構成やどのようにデータを準備したのか、 Looker で扱うために工夫した点を紹介します。 「YELL BANK」の業務データは基本的には MySQL 上に永続化しており、Fargate 上で Embulk のコンテナを動かして日時で BigQuery に同期する仕組みを取っています。 同期する仕組みの詳細は後日別の記事で公開予定です。乞うご期待!! ちなみに、バージョンは安定版の 0.9.23 を使用しています。 Embulkとは Embulk とは様々なデータソース(s3, RDB, etc…)からデータを加工しつつ転送できる、並列バルクデータローダーです https://www.embulk.org/ プラグイン形式で様々なデータ形式に対応しています。 プラグイン自体は Gemfile で指定します。 詳細はこちらのスライドシェアに載っています。 Embulk, an open-source plugin-based parallel bulk data loader from Sadayuki Furuhashi 必要なデータの種類 ショップカルテは基本的にはショップオーナの状態を我々が個別で見ることを目的としています。 それにあたっては大きく2種類のデータが必要になることがわかりました。 データは BigQuery に集約してるので、クエリなどは BigQuery のものになります。 それぞれで集計する単位や起点が変わってくるのでそれぞれエクスプローラを別に作りました。 現在の状態 (ショップ単位で集計したいデータ) 時系列の状態 (ショップと日時単位で集計したいデータ) 現在の状態について 「現在の状態」のデータというのは更新されうるデータのことを指します。 例えば、「YELL BANK」 の場合だと「現在の資金調達回数」という指標はこれに該当します。 ショップのユニーク性を担保する行が起点 になるようにエクスプローラを設計します。 時系列の状態 「時系列の状態」というのはある時点でスナップショット的に扱うデータのことを指します。 これらのデータはそのまま業務用テーブルを join すればいいわけではありません。 特に Looker においてはディメンションに対してメジャーを用意することになるので、 日付 + ショップのユニーク性を担保する行を起点 にデータを設計する必要があります。 そこで日時のカレンダーテーブルをショップごとに Cross Join して用意することにしました。 カレンダーテーブルは以下のような形で作りました。 2018 年 12 月 1 日から現在まで 1 日単位で、利用者を示すテーブルの ID と日付のペアを作っています。 ユーザーテーブル.作成日 <= timestamp(d.n)  で作成日以降のみ作られるようにしてるなど、不要なデータは極力作らないようにしています。 WITH date_map (SELECT day FROM UNNEST(GENERATE_DATE_ARRAY( ' 2018-12-01 ' , CURRENT_DATE , INTERVAL 1 day)) AS day ) SELECT ユーザーテーブル.id, TIMESTAMP (d.day) AS user_day FROM ユーザーテーブル CROSS JOIN date_map AS d WHERE ユーザーテーブル.作成日 <= TIMESTAMP (d.n) ORDER BY user_day 一度このデータを作ってしまえば Looker には dimension groupのtimeframe を用いて月ごとの集計や曜日などの集計にも 使い回せるのでかなり便利です。 dimension_group: created { type: time timeframes: [time, date, week, month, quarter raw] sql: ${TABLE}.user_day ;; } Lookerでのデータの扱い 基本的に BQ には業務データとほぼ同じテーブルスキーマを当て込んでいます。 そして、 Looker 上では派生テーブルを用いて BigQuery のクエリから中間データを生成して Looker で扱いやすくしています。 基本的に前述した集計する単位でエクスプローラをそれぞれ切りつつ、 JOIN が多段にはならないように view を作るのが良いと考えています。 基本的には 一意性を担保するカラムに primary_key を設定してやりつつ、メジャーには distinct 系を使ってあげれば重複を避けて集計を行えます。 しかし、多段的なJOINデータを扱うことは複雑性から意図せぬ挙動に繋がることが度々あり、メンテナンスも大変なので予めデータを集計しやすく派生テーブルを使っておくことが無難です。SQL からも派生テーブルは作れるので大変便利です。 docs.looker.com そこで設計する派生テーブルの構成は可能ならばスタースキーマやワイドテーブル(one-big-table)で作って上げるのが良いでしょう。 特に現代ならマシンパワーもあるので、個人的には設計コスト的や扱いやすさ的にもワイドテーブルが良いのではと思っています。実践はそこまでできてはいませんので今後の課題です。 スタースキーマとワイドテーブルの比較は Fivetran 社の Michael Kaminski 氏の記事に詳しく載っています。 fivetran.com 最後に Looker のダッシュボード作成の一例の参考になったのなら幸いです。 ショップカルテを作ることで、当初目的通り属人化せずにショップごとの状態を把握できるようになりました。これがデータの民主化なんだと改めて実感しました。 また 1 データごとにデータを検証しつつ作業を進めることができたので、 Looker 初心者の我々が最初に作るものとしては良かったなと思います。 最後に宣伝ですが、まだまだやれてないことがたくさんあります。一緒にプロダクトを成長させていく仲間を募集中です。 open.talentio.com
アバター
この記事は BASE Advent Calendar 2021 の7日目の記事です。 話題の「メタバース」を体験したい こんにちは、BASE株式会社でデザイナーをしている渡邊です。 最近なにかと話題になっている「メタバース」。『コンピュータやコンピュータネットワークの中に構築された現実世界とは異なる3次元の仮想空間やそのサービスのこと(Wikipediaより引用)』だそうで、オンライン空間で色々な活動ができることを想定しているそうですね。 私もVRには以前から興味があり、先日ついにOculus Quest2を購入しました。プライベートで音楽ゲームや釣りゲーム、VRSNSを存分に楽しんでいます。 特に仮想空間で色々な人とコミュニケーションを取る体験は最高に楽しく、「一度こっちの世界にハマったら戻れない」と言われる理由を痛感しています。 コミュニケーションがこんなに盛り上がるなら、仕事にも活かせるんじゃないか?というわけで、実際にVR仮想空間上でお仕事をしてみました! VR空間でデザイン相談会はできるのか? 同じデザインチームでVR環境を持っている人がいないか聞いたところ、Oculus Quest2を持っているメンバーが1人いたので、VRお仕事実験に協力してもらうことにしました。 デザインチームでコミュニケーションが必要なお仕事と言えばもちろんレビューです。 弊社ではデザインのフィードバックを気軽にもらってブラッシュアップする会のことを「デザイン相談会」と呼んでいます。 いつもはZoomと画面共有を使ってやっていますが、仮想空間ならより捗るのでしょうか...? 「horizon Workrooms」でチームメンバーに会ってみた 今回利用した仮想空間ツールは「horizon Workrooms」。最近Facebookから社名変更したMeta社の提供するオンライン会議ツールです。 www.oculus.com リモートでも直接会っているような体験が得られるそうですが果たして... あっ!!いた!!!! 今回協力してくださったチームメンバーのnomuraさんが、仏のような穏やかな顔(退席状態)で目の前に現れました。 音声が入らないトラブルがあったものの、なんとか仮想空間に集合することができました。リアルタイムで動くメンバーが視界に入ると、本当に「いるな...!」という実感がありました。私の手の動きも細部までVR化。けっこう感動します。 意外と快適!仮想空間でのデザイン作業 「horizon Workrooms」ではPCの画面をVR空間上にミラーリングすることができ、ヘッドセットを外さなくてもPCを操作することができます。 コントローラーを使わずに手のジェスチャーのみで操作できるようになっており、UIも丁寧に設計されています。 また、現実世界の手元のデスクの映像がモノクロで表示されるため、キーボードやマウス操作もちゃんとできます。入力のラグもほぼなく、ストレスは感じませんでした。 手元カメラの画質は荒いので、タッチタイピングができないと少し厳しいかもしれません。(録画映像には表示されないようで画像では真っ黒になっています) 録画のキャプチャなのでこの記事の画質はやや荒いのですが、実際に私が見た映像では画質が想像以上に高く、画面の小さい文字も問題なく読むことができました。 画面を大きく共有することもできるので、快適でした! トラブル発生と仮想空間故の移動の手間 が、ここでメンバーにトラブルが発生...。デスクトップのミラーリングがうまくいかなかったようです。 手元のPC画面が見れないので、画面共有の目の前に立ち一緒に見ながらフィードバックをもらうことに。 ホワイトボードや画面共有の前に立つには、現実世界で実際に席を立ち、別の方向に向かい直す必要があります。 画面共有前に立っている間はPCの操作ができないので、FBをもらうたびに席に戻る手間が発生してしまいます...。この手間がけっこうめんどくさく、「現実ならすぐなのにな」と思わざるを得ませんでした。 VR空間上で感じたメリット VR空間でレビューをはじめてすぐは「正直zoomとあんまり変わらないな...」という感じだったのですが、ある程度動作に慣れ見る余裕ができたとき、相手が傾聴姿勢なのがすごくリアルに伝わるのがとても嬉しかったです。 今回のツールでは顔の表情まではトラッキングされないのですが、体の傾きや身振り手振りだけでもなんとなく内容へのリアクションが伝わってきました。(気のせいかもしれませんが) ホワイトボードの前に立ち、説明した後振り返って「ここまではわかりましたか?」と確認するコミュニケーションがスムーズにできました。 画面共有とビデオの場合、この「ここまではわかりましたか?」のやりとりで反応が返って来ずちょっと寂しい思いをしたこともあるのですが、こちらを注視して、軽く頷いてくれるだけで聞いてくれている実感が得られるのはVR空間上ならではのメリットだなあと感じました。 現実世界の自分の体問題 VRを30分以上続けていると、ヘッドセットの重みでじわじわと頭が痛くなったり、怠くなったりしてきました。 チームメンバーのnomuraさんは、おそらく視力?の問題で、終始画面がややぼやけていたそうです。 仮想空間では没入感ある体験ができますが、こういったリアル体力の問題で目の前のことに集中できず、だんだん注意力が散漫になっていきました。 またVR特有の問題で目の前でチームメンバーの挙動がバグるという現象が多発し、真面目な話をしていてもどうしてもウケてしまいます。 私も一定時間腕が3mぐらいに伸びていたそうですが、それに気付かず熱弁していたため、話がひと段落着くまでnomuraさんは静かに耳を傾けてくれていました。VRで仕事をするには新時代の大人な対応が必要になってくるのかもしれません。 まとめ VR空間でデザイン相談会はできるのか? 今回の検証の結果「 できないこともないが、まだ早い 」という印象でした。わざわざやるほどではない...。現実快適すぎ...。というのが正直な感想です。 自分1人が気分転換がてらVR空間で作業をすることは問題なく出来そうでした。ぽつんとPC画面だけがある環境に放り込まれるので、サボりようがないのも良いです。 しかしながら、複数人で作業をするとなると急に難易度が上がります。 チームメンバーが全員万全の環境を整えない限り、ストレスなくコミュニケーションを取ることはできなさそうでした。 もしツールの機能をフル活用できたとしても、ヘッドセットの重さがある以上長時間の会議は難しいと感じました。 ただ、ハード面・ソフト面どちらもここから順当に進化していけば、近いうちに同僚と本当にオフィスで働いているような体験が出来そうだという可能性も感じました。 今回の体験からメタバースの最前線はどうなっていくのかさらに興味が強くなったので、未来に期待しながら引き続きVRに触れ続けたいと思います! 明日のアドベントカレンダーはエンジニアの横山さんです!お楽しみに!
アバター
この記事は BASE Advent Calendar 2021 の6日目の記事です。 フロントエンドエンジニアの @rry です。 今年の4月に BASE にジョインしてから、アクセシビリティに関する取り組みを少しずつ行ってきました。 BASE ではこれまでアクセシビリティに関する取り組みは局所的にしか行われておらず、また私自身もアクセシビリティについて知見が全くない状態でした。このような状態からアクセシビリティやっていきを具体的にどのようにして進めているかについてご紹介したいと思います。 アクセシビリティとは?何故アクセシビリティに取り組むのか? アクセシビリティとは | ウェブアクセシビリティ基盤委員会(WAIC) 一般にアクセシビリティとは、アクセスのしやすさを意味します。転じて、製品やサービスの利用しやすさという意味でも使われます。 似た意味をもつ言葉にユーザビリティがありますが、アクセシビリティはユーザビリティより幅広い利用状況、多様な利用者を前提とします。 アクセシビリティとは何か?については、こちらの URL に書いてあるとおりですが、 幅広い利用状況、多様な利用者でもアクセス(利用)しやすいか について言い表す言葉です。 よく誤解されやすいのが、「アクセシビリティは障がい者や高齢者のための取り組み」という捉え方をされがちですが、これはアクセシビリティ本来の主旨からずれた理解です。 例えば幅広い利用状況の例で言うと、 電車やオフィスなどでイヤホンがなく、音声が聞けない 眼鏡を忘れてしまい小さい文字が読みづらい パソコンのマウスが壊れてしまいキーボードでしか操作できない など、健常者であったとしても状況によって様々な問題が起こりえます。 もちろん障がい者や高齢者であっても、アクセス(利用)しやすい Web サービスが優れているということに変わりはないのですが、アクセシビリティとは本来そういったユーザーだけでなくもっと幅広いユーザーに対して有用な取り組みです。 BASE は BtoC なサービスのため、様々な状況で色んなユーザーがサービスを利用しています。 また、BASE のミッションである「 Payment to the People, Power to the People. 」にもあるとおり、BASE が作っているサービスは世界のすべての人に向けたサービスです。 このような背景があって、できることから少しずつ、アクセシビリティを向上させていきたいという思いがありました。 社内にアクセシビリティを取り入れたきっかけ BASE に入社してから、Slack 上でちらほらアクセシビリティに関する話題が上がっているのを観測していました。 これらの機運があり、まずはできることから始めてみようということで Slack に #study-a11y チャンネルを作り社内勉強会を開催することにしました。 エンジニアだけでなくデザイナーの方もたくさんチャンネルにジョインしており、みんなでアクセシビリティに関する話をする場を作れたのが良かったです。 余談ですが、 :a11y: というスタンプが押されたら Reacji Channeler でチャンネルに通知するように設定しておくのも、アクセシビリティに関する話題が拾いやすくてオススメです。 アクセシビリティ勉強会の開催 BASE では読書会の文化があるので、アクセシビリティに関しても読書会形式で知見を深めていこうと思い、以下の2冊を参考図書にしています。 デザイニング Web アクセシビリティ 読書会で読了 コーディング Web アクセシビリティ 次回読書会開催予定 デザイニング Web アクセシビリティについては、全8回の読書会が終わり色々と学びがありました。読書会はデザイナーさんがメインに参加しており、BASE の現状と照らし合わせながら具体的に見えてきた改善ポイントについてまとめたりしました。 コーディング Web アクセシビリティは、これからフロントエンドエンジニアがメインとなって読書会をしていく予定です。コーディングの観点からまた改善ポイントが出てくると思うので、そちらもまとめていきたいと思っています。 勉強会を通して気づいた改善ポイント 勉強会をしていると、回を重ねるごとにたくさん改善ポイントが出てきました。 たくさんある中からそのうちいくつか問題となっていることを挙げます。これらの問題は、同じように Web サービスを開発していく上でありがちかもしれないので、他の開発者にとっても参考になるのではと思い挙げてみました。 ※ これらは2021/12時点での問題で、今後改善されていく可能性が高いです カラーコントラスト比が足りていない 達成基準 1.4.3 を理解する | WCAG 2.0解説書 1.4.3 コントラスト (最低限) : テキスト及び文字画像の視覚的提示に、少なくとも 4.5:1 のコントラスト比がある。ただし、次の場合は除く: (レベル AA) 大きな文字: サイズの大きなテキスト及びサイズの大きな文字画像に、少なくとも 3:1 のコントラスト比がある。 付随的: テキスト又は文字画像において、次の場合はコントラストの要件はない。アクティブではないユーザインタフェース コンポーネントの一部である、純粋な装飾である、誰も視覚的に確認できない、又は重要な他の視覚的なコンテンツを含む写真の一部分である。 ロゴタイプ: ロゴ又はブランド名の一部である文字には、最低限のコントラストの要件はない。 デザインに関する問題で、今はデザイナーの方が主体となって具体的にどこがコントラスト比が足りていないか調査したりしている段階です。 カラーコントラスト比をチェックするツールは様々な用途からいくつかあります。 Accessibility Insights for Web - Chrome ウェブストア A11y - Color Contrast Checker – Figma Accessibility Addon | Storybook これらを利用して、実際の Web ページやコンポーネントのカラーコントラスト比をチェックすると、基準に達していない部分が分かります。 これらをすべて改善していくというのはとてもサイドプロジェクトでは収まりきらないのですが、問題として認識してはいるので、どこかのタイミングで改善に向けて動けないかなと機会を狙っています。 ユーザーに伝えたい文章が画像になっている(スクリーンリーダーで読めない) 達成基準 1.4.5 を理解する | WCAG 2.0解説書 1.4.5 文字画像: 使用している技術で意図した視覚的提示が可能である場合、文字画像ではなくテキストが情報伝達に用いられている。ただし、次に挙げる場合を除く: (レベル AA) カスタマイズ可能: 文字画像は、利用者の要求に応じた視覚的なカスタマイズができる。 必要不可欠: テキストの特定の提示が、伝えようとする情報にとって必要不可欠である。 注記: ロゴタイプ (ロゴ又はブランド名の一部である文字) は必要不可欠なものであると考えられる。 BASE のページでは、具体的にはこちらの404ページなどがそうなのですが、ユーザーに向けて伝えたい文章が画像になっています。 指定されたページが見つかりません | BASE 「404 PAGE NOT FOUND. お探しのページは存在しません」 スクリーンリーダーで訪れた場合など、上記の文言は読み取られない状態です。 BASE では今404ページのリデザインの話も上がっているので、こちらは案外早く改修するかもしれません。 とはいえこういう、本来テキストで表示するべきところが画像になっている、というケースは Web サービスを利用していてよく見かける気がします(特にレガシーな Web サービスや LP に多い印象) CSS や SVG や Web フォントなどを利用して、マシンリーダブルなコードを書いていきたいと思いました。 フォーカスが当たっているか分からない・そもそもフォーカスが当たらない 達成基準 2.4.7 を理解する | WCAG 2.0解説書 2.4.7 フォーカスの可視化: キーボード操作が可能なあらゆるユーザインタフェースには、フォーカスインジケータが見える操作モードがある。 (レベル AA) フォーカスが当たっているにもかかわらず、フォーカスが当たっていないときとスタイルが全く変わっていない BASE のカート(決済画面)の入力要素など、複数箇所あります。 こちらの改修はコンポーネントを作るときやコーディングするときにきちんとフォーカスがあたったときのスタイルをつけよう、で終了するお話ですが、 :focus や :visited のスタイルについてはなかなか見落とされがちであり、実装についても少し考慮が必要です。 まず分岐として、以下の2点で実装の難易度やデザインのクオリティが分かれてきます。 ブラウザが自動でつけるフォーカスをそのまま利用する メリット: フォーカスが当たったときのスタイルを書く必要がないため実装工数がかからない デメリット: デザインの雰囲気がブラウザによって左右されてしまう フォーカスが当たっているときのデザインを用意する メリット: どのブラウザで見ても統一性のあるデザインのフォーカスのスタイルが当たる デメリット: 実装工数がかかる・フォーカススタイルの付け忘れに気をつけなければいけなくなる 具体的に「フォーカスが当たっているときのデザインを用意する」場合は(例えばですが)以下のようなことを考えながら開発することになりそうです。 まずブラウザのデフォルトのフォーカスのデザインが邪魔になるため、CSS ですべての要素のフォーカスのスタイルを打ち消します。その状態でフォーカスのスタイルをうっかり付け忘れてコンポーネントを作ったりすると、例のフォーカスがどこに当たっているのか分からない残念なコンポーネントのできあがりです。 Figma などでデザインを作るときにフォーカスが当たったときのデザインをセットで作るなどでスタイルの付け忘れは防ぐなりする必要がありそうです。 では、次に outline プロパティを使ってフォーカスをつけるとします。 .outline : focus { outline : black solid 2px ; } 一見これで問題なさそうに見えます。しかし、もし要素が border-radius で丸められていたとしたらどうでしょう?試しに Safari で要素にフォーカスしてみましょう。 Safari(IE も?)では、outline が丸まらずに角張って表示されてしまいます。 そのためここで Safari だけ角張っても問題ないかデザイナーの方とすりあわせる必要があります。 それか角張りを許容できない場合は box-shadow を使ったスタイルを当てれば意図したスタイルになります。 .boxshadow : focus { box-shadow : 0 0 0 2px black ; } See the Pen Untitled by rry ( @ryamakuchi ) on CodePen . このようなことを考慮しなければならないため、フォーカスのスタイルは少し注意が必要です。 ちなみに tailwindcss もフォーカスのスタイルは box-shadow で実装されており、こういったフレームワークを利用するのも一つの手かもしれません。 Ring Width - Tailwind CSS そもそも div や span、href のない a タグなどで要素が作られておりフォーカスが当たらない こういった要素は、非常に多くの Web サービスで目撃しますが、BASE も例に漏れず沢山あります。ショップの管理画面の SP レイアウトのハンバーガーボタンなどがこれにあたります。 いわゆる「虚空問題」(1の虚空)です。 noteの虚空ボタンを顕現させる社内勉強会を開催しました|sawa / note inc. さっき会社で、アイコンフォントだけがリンクやボタンになっていてテキストノードがないものを「虚空」と呼ぶことになった — Rikiya Ihara / magi (@magi1125) March 1, 2019 note さんの記事に書かれているような取り組みまではまだ実施できていませんが、まずクリッカブルな要素は基本的に button や label に置き換えられそうなので、button や label を使った実装に置き換えていきたいと考えています。 来年以降にやっていきしたいこと 年末なので来年の抱負を掲げたいと思います。 まずは「コーディング Web アクセシビリティ」読書会を、フロントエンドエンジニア中心のメンバーで行うことです。 このブログでとりあげたこと以外にも、アクセシビリティを意識したコーディングの知見はたくさんあるため、開発メンバー主体となって学んでいければと思います。 また、本格的にアクセシビリティのチェックを行いたいとなったときには、 アクセシビリティ試験 を実施するのが良さそうです。 試験についてのQ&A | ウェブアクセシビリティ基盤委員会(WAIC) アクセシビリティ試験なるものはこちらの勉強会で知ったのですが、具体的にどこが問題となっているのかをチェックすることができてとても役に立ちそうです。 もしあなたが『アクセシビリティ試験』をやることになったら WP ZoomUP #68 - connpass おわりに BASE で取り組んだアクセシビリティに関してまとめてみました。 アクセシビリティ専門のチームがあるような会社でない限り、 「アクセシビリティについて気になってはいるけれどどこから手をつけたらいいか分からない」 「アクセシビリティは組織に浸透させるのが難しい」 というお気持ちがある方のほうが多いのではないかと思います。でもまずは できることから少しずつやっていこう というマインドで、他の人の力も借りながらやっていくのが良いと思いました。 読んでいただいて分かるとおり、まだアクセシビリティに対してほんの少ししか取り組めておらず、課題がたくさん見えてきた状態です。 どうしたらうまくやっていきできるか試行錯誤しているため、他の会社ではどんな取り組みをしているのかぜひお話を聞きたいです 🙌 ぜひ @rry まで気軽にお声がけください。 明日のアドベントカレンダーはデザイナーの @Fuki さんです!お楽しみに〜 🎅
アバター
この記事は BASE Advent Calendar 2021 の 5 日目の記事です。 基盤チームの右京です。 最近ひょんなことから browserslist の設定を見返したのですが「babel や autoprefixer で必要になったので導入した」以上はあまり触れられていなかったため、この機会にいちから見直してみようと思いました。 browserslist? https://github.com/browserslist/browserslist 簡単に言えば、クエリを書くとそれに該当するブラウザをリストで取得できます。babel(preset-env) や autoprefixer はここから取得出来るリストを利用して、必要な変換内容を決定しています。単純にバージョン指定でのクエリが記述できるだけではなく、利用統計に基づく絞り込みも可能となっています。例えば、0.2% 以上のシェアがあり、メンテナンスが行われているブラウザは次のクエリで取得できます。 > 0.2%, not dead このクエリは package.json の browserslist として指定するか、 .browserslistrc を作成して記述すると認識されるようになります。今回は package.json に書く形で進めます。 { " private ": true , " browserslist ": [ " > 0.2%, not dead " ] } この状態になればブラウザのリストを CLI か JavaScript で取得できます。試しに CLI を使って取得してみます。 $ npx browserslist and_chr 96 and_ff 94 and_uc 12.12 chrome 95 chrome 94 chrome 93 chrome 92 chrome 87 chrome 61 chrome 49 edge 95 edge 94 firefox 93 firefox 92 ie 11 ios_saf 15 ios_saf 14.5-14.8 ios_saf 14.0-14.4 ios_saf 13.4-13.7 ios_saf 12.2-12.5 ios_saf 9.0-9.2 opera 79 safari 15 safari 14.1 safari 14 safari 13.1 samsung 15.0 babel からの利用 取得できたリストを babel(preset-env) がどのように利用しているかを確認してみます。利用には @babel/preset-env を設定する必要があるので、これを追加して設定します。 { "presets": ["@babel/preset-env"] } 次のようなコードを例に考えてみます。このコードは古いブラウザでは async/await が実装されていないため、このままでは動作できず何かしらの形に変換される必要があります。 (async () => { const response = await fetch( 'https://api.github.com/users/yaakaito' ); console.log(response) } )() 先程の browserslist の結果には async/await を実装していない IE11 が含まれているため、babel で変換すると置き換えがおこるはずです。 Async functions | Can I use... Support tables for HTML5, CSS3, etc > npx babel src/index.js "use strict"; function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() { var response; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return fetch('https://api.github.com/users/yaakaito'); case 2: response = _context.sent; console.log(response); case 4: case "end": return _context.stop(); } } }, _callee); }))(); このように IE11 でも動作可能なコードに変換されました。ですが、BASE では IE11 のサポートを終了したため、対象ブラウザから IE11 を除外したいと思います。これもクエリを書くことで表現することが出来ます。 not IE 11 を追加します。 { " private ": true , " browserslist ": [ " > 0.2%, not dead ", " not IE 11 " ] } この状態で babel で改めて変換すると、対象にしているブラウザすべてで async/await が実装されているため変換が不要となり、出力されるコードが変化していることがわかります。 $ npx babel src/index.js "use strict"; (async () => { const response = await fetch('https://api.github.com/users/yaakaito'); console.log(response); })(); BASE の設定を見直す さて、きっかけとなった今現在 BASE で設定されている browserslist を見ていきます。browserslist は複数のリポジトリで共有しているため、プライベートな GitHub Packages として配信しており、このような形で利用できるようにしています。 " browserslist ": [ " extends @baseinc/browserslist-config " ] そしてその中身は...ちょっと頭が痛くなる感じでした。 ['ie >= 11', 'safari >= 7', 'iOS >= 10.0', 'and_chr >= 5.0', 'last 1 version', '> 1%'] and_chr 91 and_ff 89 and_qq 10.4 and_uc 12.12 android 91 baidu 7.12 bb 10 chrome 91 chrome 90 chrome 89 edge 91 edge 90 firefox 89 firefox 88 ie 11 ie_mob 11 ios_saf 14.5-14.6 ios_saf 14.0-14.4 ios_saf 13.4-13.7 ios_saf 13.3 ios_saf 13.2 ios_saf 13.0-13.1 ios_saf 12.2-12.4 ios_saf 12.0-12.1 ios_saf 11.3-11.4 ios_saf 11.0-11.2 ios_saf 10.3 ios_saf 10.0-10.2 kaios 2.5 op_mini all op_mob 62 opera 76 safari 14.1 safari 14 safari 13.1 safari 13 safari 12.1 safari 12 safari 11.1 safari 11 safari 10.1 safari 10 safari 9.1 safari 9 safari 8 safari 7.1 safari 7 samsung 14.0 明らかに内容が古いので、現在推奨している環境に合わせてクエリを見直します。 対応している利用環境・ブラウザは何がありますか – ヘルプ | BASE これをもとに考えると、このようなクエリが適用できそうです。 " browserslist ": [ " > 0.2%, not dead ", " not IE 11 ", " Safari > 11, iOS > 11 " ] 他のツールにも活用したい 最近、TypeScript の変換を速度面の課題から tsc → esbuild に置き換えたいと考えていて、実は今回見直すきっかけになったのもこれでした。Vue.js 2x での TSX サポートのために Babel を通す必要があるのですが、それ以外のものに関してはできれば esbuild のみで解決したい。esbuild の target はブラウザの指定が可能ですが、browserslist には対応していません。 esbuild - API Support browserslist · Issue #121 · evanw/esbuild これに browserslist を使えるようにしてみます。すべて対応しようとすると大変なので、ここでは chrome のみで考えます。 #!/usr/bin/env node const browserslist = require( 'browserslist' ); const { buildSync } = require( 'esbuild' ); const browsers = browserslist(); const target = [ browsers.reverse().find(t => t.startsWith( 'chrome' )).replace( ' ' , '' ) ] ; console.log(buildSync( { entryPoints: [ 'src/index.js' ] , target } )) 簡易的ですが、browserslist に含まれている chrome の一番古いバージョンを esbuild で解釈できる形に変換しています。これをビルドしてみると、先程と同じ結果になります。 > ./build.js { errors: [], warnings: [] } (async () => { const response = await fetch("https://api.github.com/users/yaakaito"); console.log(response); })(); 試しに async/await が実装されていないバージョン(chrome54)が含まれるクエリに変更してみます。 " browserslist ": [ " > 0.2%, not dead ", " chrome 54 ", " not IE 11 ", " Safari > 11, iOS > 11 " ] > ./build.js { errors: [], warnings: [] } var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; (() => __async(this, null, function* () { const response = yield fetch("https://api.github.com/users/yaakaito"); console.log(response); }))(); yield を使った結果に変化しました。簡単にではありますが、browserslist から esbuild の設定ができるようになりました。 おわりに なんとなく設定していた browserslist ですが、最初から見直してみることで考えられる活用の幅が広がったように思います。 今回は、すでに掲載している推奨環境からクエリを逆算しましたが、関係を入替えて browserslist から推奨ブラウザを生成し更新を半自動化するような運用も考えられそうです。 明日は @rry です、お楽しみに!
アバター
この記事はBASE Advent Calendar 2021の4日目の記事です devblog.thebase.in ごあいさつ はじめましての人ははじめまして、こんにちは!フロントエンドエンジニアのがっちゃん( @gatchan0807 )です 今回は、フロントエンドエンジニア界隈で話題になっているDenoについて調査し、プラスでSlack Botを書いてみることで現時点のDenoはどんな感じで使えるのかを検証したので、その経験をシェアできればと思っています!(実際にSlack Botを書くのは次回の記事の予定です) つらつらとDenoを触っていて気になったポイントを書いたためにかなり長い記事になってしまいましたが、お付き合いいただけますと幸いです! Denoの基礎知識 DenoはNode.jsの作者、Ryan Dahl( https://github.com/ry )さんがNode.jsの反省点を活かして よりセキュアで生産的になるように 開発されているJavaScirpt / TypeScriptランタイムです 2018年5月14日に Initial commit が発行 され、執筆時現在の最新版は v1.16.3 が最新となっていますが、開発スピードは非常に速く、執筆開始時点から約1週間でminorバージョン1つ、patchバージョンが3つ進んでいるので最新バージョンの情報は 公式ページ を確認してください⚠ 個人的には、日本Node.jsユーザグループ代表の古川さんの記事をきっかけに国内では話題になっていて知った記憶があります( Node.js における設計ミス By Ryan Dahl ) ※上記の記事の元になった、Ryan Dahlさん本人による発表・資料はこちら( YouTubeの発表動画 / 発表資料PDF ) また、国内コミュニティには日本語Slackチャンネルがあり、日本人Denoコミッターの kt3k さんもいらっしゃって、そこで色々情報交換などもされているので、少しでもDenoに興味があればぜひ参加されることをおすすめします( Deno-ja Slackへの参加方法 ) 公式でとりわけ特徴として上げている部分 ここからは公式ドキュメントで取り上げられているDenoの推しポイントリストを紹介しつつ、普段Node.js環境で開発をしている自分が触っていて特徴的だなと感じた部分も合わせて紹介していきます。 特徴的な機能 可能な限りWeb互換で利用できるようにする。例えば、ESModuleやfetchの対応など 明示的に指定しない限りネットワークやファイル、その他環境へのアクセスは行えず、セキュアに保つ インストールしてすぐにTypeScriptが使える(サポートしている) 単一の実行可能ファイル( deno ) ビルトインで様々なユーティリティツールが利用可能。( deno fmt : フォーマット機能、 deno lint : リンター機能、 deno test : テストランナー機能) Denoチームによって動作保証(レビュー)されている 標準ライブラリ 単一JavaScriptファイルへのバンドル機能 原文: - Web compatible where possible, for example through usage of ES modules, and support for fetch. - Secure by default. No file, network, or environment access (unless explicitly enabled). - Supports TypeScript out of the box. - Ships a single executable (deno). - Has built-in utilities like a code formatter (deno fmt), a linter (deno lint), and a test runner (deno test). - Has a set of reviewed (audited) standard library that are guaranteed to work with Deno. - Can bundle scripts into a single JavaScript file. https://deno.land/manual#feature-highlights 他にも概要を調べていて個人的に特徴的だな〜と思ったのが↓のあたりのポイントです URLによる依存ライブラリの取得と隠蔽されたパッケージファイル管理(No package.json & npm )。 一度ロードされたパッケージはローカルにキャッシュされている(バージョンごとに自動で入るので、手でパッケージ内の一部ファイルを消してしまったとかのよほどのことが無いと気にすることはなさそう) Comparison to Node.js (Node.jsとの比較)内にかかれている Deno always dies on uncaught errors. の通り、適切にキャッチして処理されないエラーは常に死ぬようになっている点 適切にキャッチして処理されないエラーは常に死ぬようになっている点に関しての補足(クリックで開閉) これを見て、最初は「とはいえ、死なないことなんて起きるのか…?」と思って調べていたのですが、Node.jsでは uncaughtException / unhandledRejection のイベントリスナーを設定することで、どこからもエラーがキャッチされずにprocessまで上がってしまった場合の後処理が書けることを知りました。 uncaughtException / unhandledRejection のイベントリスナーは本来、メモリリーク防止の為の後処理や、開いたまま終わると問題になるネットワーク・ファイルのクローズ処理を書くためにあるもので、Node.jsの公式ドキュメントにも注意して利用するように記載されています ( https://nodejs.org/api/process.html#process_event_uncaughtexception ) デフォルトではprocessまでエラーが上がった場合にはスタックトレースに出力してexit-code 1で終了させるだけですが、 ここを書き換えることによってexit-codeを0で終了させたり無理やり復旧させることができてしまうのが問題になっていました そのため、Denoでは適切に処理されない場合には常に死ぬように設定されているようです また、ちょうどこの辺りを調べていた時にDeno側でちょっと面白い議論を見つけて、記事執筆時の2日前に動きがあったのであわせて共有しておきます( https://github.com/denoland/deno/issues/7013#issuecomment-737621469 ) ざっくりいうと、「ライブラリ側で何らかの理由でキャッチされずに放置された Reject ( Promise の失敗時の対応)があった場合に有無を言わせずに死ぬので、どこで死んだかの調査がとても大変&アプリケーションになると現実的に無理ぽ。」というお話 これはLinterでもチェックされないし、人間は完璧にコードを書けるわけじゃないから対応してほしいよ〜😢というIssueがあり、ちょうど2日前に、ひとまずNode.js互換モードとして onunhandledrejection の実装が決まったようです https://github.com/denoland/deno/issues/7013#issuecomment-974256318 Denoの性質上、すべての非同期処理がPromiseで返されるので Unhandled Rejection を対応できるようにする方法は Uncaught Exception を対応できるようにする方法と同義になるんじゃね?とかのコメントを見て全体像が理解できたので、個人的にすごく学びがあったIssueでした あとついでに Foot-gun というワードとその意味を知れたのも学び https://twitter.com/MengTangmu/status/1069751647771877376?s=20 Deno CLIから提供されるツール群 Denoの基本的な思想も面白いのですが、CLIコマンドが豊富でNode.js環境だと乱立しているテストランナーやフォーマッターなどがランタイムに標準装備されているのも面白いポイントです(JSConfでも、yosuke_furukawaさんとkt3kさんが「揺り戻しのよう全部入りのランタイムになってきているというのが面白い」という話がありました) 主だったツールのコマンドと利用シーン 定義されたテストを実行する deno test chaiやsinonなどと同時に使うことも可能 指定のコードをフォーマットする deno fmt Lint実行を行う deno lint CLI上でJS/TSの実行結果をインスタントに試せる deno repl 依存ライブラリ一覧を表示する deno info ファイル指定を行わずに実行すればライブラリキャッシュの在り処を知れる スクリプトをCLIコマンドとしてインストールする deno install インストール時にきちんと --allow-net などで必要な権限を付与した上で実行する必要があるので注意 複数JS/TSファイルを実行可能・インストール可能な形にコンパイルする deno compile コンパイル時にも必要な権限を付与して実行する必要があるので注意 複数JS/TSファイルをES Module形式でまとめられる deno bundle 出来上がったファイルはES Moduleなので、直接ブラウザに読み込むことも可能 ドキュメントを自動生成する deno doc Denoの環境を作って触っていく ここまででざっくり基礎知識を得たので、実際にDeno環境を作って肌感覚を得ていこうと思います 今回は最近GAになり、なおかつ個人アカウントの場合に無料で利用できる GitHub Codespaces に環境構築を行おうと思います 雑なコードが残っているだけですが、こちらの リポジトリ に実際に試した設定ファイルなどが残っているので良ければご覧ください Codespacesの準備 この辺りはおなじみの光景なのでサクッと飛ばしますが、まずリポジトリを作成します 適当に README.md ファイルなどを置いて、ファイル一覧の右上に表示される < > Code ボタンを押下するとCodespaceを作成するボタンが出てくるので New codespace ボタンをポチッとしてください しばらく待つと、下記のように見慣れたVS Codeの画面がブラウザに表示され、ターミナルも起動されるので準備完了🙆 (作成中に表示される緑色の Open this codespace in VS Code Desktop ボタンを押すと、ブラウザではなく手元のPCのVSCodeが起動して、そこからCodespacesに接続する形になります) Denoのインストール ここからはCodespaces上に構築されたLinux環境にDenoをインストールしていきます https://deno.land/ まずは公式ドキュメントに書かれている通りにCurl / ダウンロードされるShellScriptを実行してダウンロード → deno -V でインストールの完了を確認します $ curl -fsSL https://deno.land/x/install/install.sh | sh サンプルコードの実行 動作環境が用意できたので、サンプルコードの実行を行っていきます ドキュメントに書かれている通り、 deno run コマンドの引数にURLでTypeScriptファイルを指定し、実行してみましょう $ deno run https://deno.land/std/examples/welcome.ts すると下記のように自動的にファイルがダウンロードされ、実行するファイルに必要な権限などのチェック後、そのファイルに書かれたコードが実行されます このようにコマンド一つでファイルのダウンロードから実行まで行ってくれるのもDenoの特徴ですね ※今回の例で使っているコマンドにはバージョン指定が入っていないので、「自動で最新のバージョンのコマンド実行するよ。」というWarningが出ていますが基本問題はないのでスルーでOKです Codespacesのコンテナ設定でDeno環境を整えるように変更 続いて、毎回Codespacesの環境にDenoのインストールするところから始めるのは面倒なので、 devcontainer.json という仕組みを使ってDenoを利用する設定をコード化し、Git管理化に設定ファイルを置いておきましょう やることはVSCodeコマンドを実行して、選択肢をポチポチするだけでOKです まずCodespacesが起動している状態で、 Cmd + Shift + P でCommand Palleteを起動し、 add development と入力して下記の Codespaces: Add Development Container Configuration Files... のVS Codeコマンドを実行します 続いて、(最初は deno を入力しても、よく使われるDefinitionsの選択肢には出てこないので、) Show All Definitions... をクリックして再度 deno を検索してください 最後に、ベースになるDebianのOSバージョンを聞かれるので、デフォルトの最新安定版を選択することで… ※ bullseye はDebianのバージョン11のコードネームです( https://www.debian.org/releases/index.ja.html ) 完成! .devcontainer ディレクトリと、その配下に devcontainer.json / Dockerfile ができているはずなので、それらをコミットしておいてあげれば次回Codespaceを起動しなおしたときに自動的にDenoの環境構築がされたコンテナが適用されます ちなみに、テンプレで作成されるDockerfileも基本やってることは一緒 # [Choice] Debian OS version: bullseye, buster ARG VARIANT=bullseye FROM --platform=linux/amd64 mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} ENV DENO_INSTALL=/deno RUN mkdir -p /deno \ && curl -fsSL https://deno.land/x/install/install.sh | sh \ && chown -R vscode /deno ENV PATH=${DENO_INSTALL}/bin:${PATH} \ DENO_DIR=${DENO_INSTALL}/.cache/deno 便利ポイントとしては同時にDeno用の公式VS Code拡張のインストールや初期設定をしてくれる https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno 他にも README や解説記事などでは初期設定の実行 Deno: Initialize Workspace Configuration を行うように書かれている事が多いですが、下記の通りすでに設定が入っているのでスルーでOKです { "name": "Deno", "runArgs": ["--init"], "build": { "dockerfile": "Dockerfile", // Update 'VARIANT' to pick an Debian OS version: bullseye, buster "args": { "VARIANT": "bullseye" } }, "settings": { // Enables the project as a Deno project "deno.enable": true, // Enables Deno linting for the project "deno.lint": true, // Sets Deno as the default formatter for the project "editor.defaultFormatter": "denoland.vscode-deno" }, // This will install the vscode-deno extension "extensions": [ "denoland.vscode-deno" ], "remoteUser": "vscode" } 公式ドキュメントを確認していく 以上でDeno環境の構築が完了したので、公式ドキュメントのTOPページを上からなぞって、書かれている公式ドキュメント等の役割をあわせて紹介していきます! 公式Docsのローディング画面めっちゃかわいい https://doc.deno.land/builtin/stable ビルトインの関数・メソッドたちを確認できるドキュメントを開くと、可愛すぎるローディングアニメーションを見ることができます。一旦可愛い恐竜くんたちを愛でましょう その後にちゃんと内容を見て、 Deno 名前空間と WebAssembly 名前空間が使われてるんだ〜とか思ったりしましたが、細かいところは使ってみながらの方が理解しやすそうなので、自分は一旦流し見で終了しました 公式マニュアルを眺めながら構築した環境で触っていく https://deno.land/manual 続いて公式マニュアル(Denoの開発方針から何から細かいことが書かれているドキュメント)を見始めたのですが、ここはボリュームがあるので後述の項で別途まとめていきます! 標準ライブラリ(標準モジュール)のドキュメントをざっくり確認 https://deno.land/std@0.116.0 http モジュールとか crypto モジュールとか uuid モジュールとかのNode.jsと同じような各種モジュールが std 配下から色々提供されています。これらは3rdパーティモジュールと同じようにURL経由で提供されていますが、Denoチームがメンテナンスし、動作を保証してくれているものです バージョンがものすごいスピードで上がっていて、この記事を書いてる5日の間にもにょきにょきバージョンが伸びていて、直近いろいろな改修が加えられているのをひしひしと感じています👀 3rdパーティモジュールをWebから読み込める https://deno.land/x こちらは deno.land/x 配下にGitHubでホスティングされているパブリックライブラリを登録できる仕組みです 実際には直接JSをダウンロードできるURLがあれば、ここに登録せずにそのURLからライブラリのコードを配布しても良いのですが、ライブラリ提供者がここに登録して提供することで、Denoユーザーが覚えやすいURL( deno.land/x で現在は固定)を払い出すことができるので、ぜひやってね〜というテンションで運用されているようです GitHub WebhookとGit Tagを使って設定を行うことで、特定のバージョンごとにDeno上にキャッシュを残してくれる動きをします ライブラリのドキュメントを自動で書き出してブラウザからも見れる https://doc.deno.land/ ライブラリのコード内にJSDoc記法で書かれたドキュメントを取得して、Web UIとして表示できるジェネレーター機能を持ったページです 利用しているライブラリのURLを直接指定してあげれば、自動で deno doc --json で出力されたJSONファイルを元にページをジェネレートして表示してくれます(ライブラリ作者はこのJSONファイルも一緒に提供しておくのが推奨されています) 公式マニュアルをなぞってDenoを触っていくぞ!! 一通りDenoの環境構築と、Denoが提供している公式ドキュメントの全体像がわかってきたので、 公式マニュアル を参考にGetting Startedからやっていこうと思います! 流石にここまでStep by Stepで書くのはあんまり意味ない気がするので、普段JavaScript/TypeScript + Node.jsを触っている人間が気になったポイントだけまとめていこうと思います セルフアップデートできるの便利よ https://deno.land/manual/getting_started/installation#updating Node.jsだととてもカオスになってたランタイムバージョン管理がランタイム自体でできるの地味に嬉しいですね nvm やら nodenv やら nodebrew やら n やら ndenv やらが不要なのが地味に嬉しいですね 特定バージョンを指定してのインストールもここでできるみたいです👀 CodespacesでもTCPリクエストのサンプルコード全然動かせる ここはDenoがどうこうという話ではなく、Codespacesの機能としてちょっと不安だったローカルのネットワークを触るサンプルコードがちゃんと動くのかを試して、問題なく動くのが確認できて感動した話です 上記の結果は このサンプルコード を実行して出てきたものなのですが、コード実行時にポップアップが出てきて、そのままブラウザの別タブに遷移できるようにしてくれるのでほとんど気にすることがなかったのが嬉しかったです またCodespacesの場合にのみ表示されるPORTSタブでは、自分でポートフォワーディングの設定を登録することも可能で、自動でドメインを発行して指定したポートをGitHub Authで守られた状態で自動的にバインドしてくれたりもします さらに、ここで特定のポートをクリックひとつで公開状態にできるので、バインドされたURLを他の人に共有してかんたんに動作確認をしてもらうことも可能なのが便利で嬉しいポイントです! 基本的にはコンフィグファイルいらないよ!!という圧を感じる { "compilerOptions": { "allowJs": true, "lib": ["deno.window"], "strict": true }, "lint": { "files": { "include": ["src/"], "exclude": ["src/testdata/"] }, "rules": { "tags": ["recommended"], "include": ["ban-untagged-todo"], "exclude": ["no-unused-vars"] } }, "fmt": { "files": { "include": ["src/"], "exclude": ["src/testdata/"] }, "options": { "useTabs": true, "lineWidth": 80, "indentWidth": 4, "singleQuote": true, "proseWrap": "preserve" } } } https://deno.land/manual/getting_started/configuration_file#example こんな感じで設定をファイルにまとめてできる(実際のスキーマは こっち )のですが、基本的にはデフォルトを使ってね。複数人プロジェクトでどうしても必要な場合に作ってね。というニュアンスで書かれていました また、このファイル自体は将来的に自動で探してきてロードするようになる予定なので deno.json / deno.jsonc で名前つけておくのがおすすめとのこと デフォルトではできないことリスト https://deno.land/manual/getting_started/permissions#permissions-list Denoではランタイムオプションを指定しないとできないことが意外とあるので、ちゃんとリストを知っておけると良さそうだなと思いました 一応、 -A, --allow-all : 全許可 なんてものもありはしますが、せっかくDeno使っているのであんまり使わないほうが良さそうかなぁ…と思います また、このランタイムオプションには 特定のコマンド / ファイル / URL / IPのみアクセスを許可することもできて、結構柔軟 なので、ライブラリや動作環境をセキュアに保つために設定しておくのが良さそうに感じました🙆 The Runtimeに定義されているAPI群をみて感じたWeb互換の意識の強さ 全体的に、普段ブラウザJSを書いていると馴染んでいるAPIが並んでいてびっくりしました Permission APIsはPWA機能を使うときによく叩くAPIだし、Program lifecycleはページロード時の処理とほぼ一緒だし、イベントの処理とかも普段使っている感じのやつだし… 特に Location API / Web Storage API に関してはそこまで考えて挙動作ってるの…?って思うぐらいに作られていてシンプルにすごいなと思いました… ただ、この辺りで1点わからないポイントがあり、1.16で追加された fetch のファイル読み込み機能はファイルの動的インポートに使うのが想定ユースケースなのかなんなのか…?という疑問点が残っているので、有識者の方いらっしゃったらコメント等いただけますと嬉しいです…🙏 Denoで新規プロジェクトを始めるときに気になりそうなポイント ここからは、次回のカレンダー(2021/12/12の1と2がいっぱいある日に公開予定)で実際にSlack Botを書いていく予定なので、Denoでコードを書く時のポイントの理解だけではなく、プロジェクトとしてすすめるのに必要そうな部分をまとめて紹介して行こうと思います lock fileの存在 Denoはnpm / package.jsonが無い代わりにURLベースの依存ライブラリ管理方法を取っているため、パッケージ配布元のURLが変わったり、 該当URLのライブラリが乗っ取られて悪意のあるコードが埋め込まれて内容に変わってしまった場合に気づく術が無い ため、Denoではデフォルトで整合性チェック用のHashを持っています 1人で1端末で開発しているだけならDenoがデフォルトで管理されている、 /deno/.cache/deno/deps/https/deno.land あたりの [hash].meta.json ファイルたちだけで問題はありません しかし、複数人での開発の場合はそれらのバージョン情報を共有する必要があるので、プロジェクトコードに lock.json を作り、Git管理下に置く方法が紹介されていました --lock=[path/to/lock.json] --lock-write をコマンド実行時に指定することでいい感じに作ってくれるので、プロジェクトではエントリポイント指定してGit管理下に置いておいた方が良さそうですね👍 Import mapsの存在 https://deno.land/manual/linking_to_external_code/import_maps#import-maps 同様に、package.jsonがないことのデメリットとして、インポートする依存ライブラリの指定が各ファイルに散らばってしまうこと(個人的にどう解決してるのか一番気になってた問題)の解決方法として、WICGで仕様が定義されている import-maps を使って一箇所で依存ライブラリのURL・バージョン指定をできるようにしています(公式ではこの方法が推奨されているっぽいですが、他にも deps.ts というファイルを作り、そこでライブラリインポートを管理して、他のファイルからそれをまるっと読み込むパターンも有るようでした) なので、プロジェクトの初期にこのファイルを作成しておいて、ライブラリの追加時にはここに追記して実際のアプリケーション側からはバージョン情報などを省いた省略記法で記述していくのが良さそうですね この辺りを見て個人的には、 deno.json => Linter、Bundler、Formatter等の各種設定ファイルの集合体 import_map.json => package.jsonの依存ライブラリ指定部分の代替 lock.json => package-lock.json / yarn.lockの代替 として3ファイルを作るのが自然なのかなという理解をしたので、次回のSlack Bot作成ではこの形で各設定を管理し、実装を行っていこうと思います! Deno基礎知識 + 環境構築編のまとめ さて、ここまでDenoの基礎知識から実際に触れる環境を用意してマニュアルをなぞってざっくり全体感を理解してきました 2年半ほど前の発表時に話題になっているのを見つけ、そこからの進化をちょろっと横目で見ていた程度だったので、どういう機能が実装されているのかなどを知らなかったのですが、これである程度はDeno環境でコードを書けるようになりました なので、次回の記事では社内のSlackで動くBotを実際に作って、Denoの触り心地をより理解していこうと思います! 先日Slack社が Slack Appの次世代開発プラットフォームをDenoを使って提供していくことを発表 しました。 自分はこれにクローズドベータの申込みをしたものの、まだ参加できていないので、 もし次回の記事執筆までにクローズドベータに参加できれば、そちらの新しいプラットフォームを使って slack コマンドを使ったApps開発をまるっと構築をする。参加できなければ(非公式ではあるものの) deno-slack-sdk で管理されているDenoライブラリが公開されているのでそちらを使ってSlack Botを構築してみる。という形で記事を書こうと思います! では、長々とお付き合いいただきありがとうございました!次回もお楽しみに! 明日のアドベントカレンダーは基板チームの右京さんの記事です!こちらもお楽しみに! 参考 Deno公式サイト https://deno.land/ https://doc.deno.land/ https://deno.land/manual JSConfでの @kt3k さんの発表 https://jsconf.jp/2021/talk/the-past-and-future-of-deno https://kt3k.github.io/talk_jsconfjp2021/#1
アバター
こんにちは。BASE BANK 株式会社 Dev Division にて、 Engineering Manager をしている東口( @hgsgtk )です。 弊チームではプロダクト開発のリズムの中で振り返りを継続的に行っていますが、YOT という振り返りワークを作成、使用しています。BASE 社内の他チームでも「YOT っていう振り返り方法があるらしい!」と興味を持ってもらい活用されていたり、社外でもスクラムやアジャイル関連のカンファレンスでの登壇でちらっと紹介した際に「それ良さそう!」と一定の反響がありました。しかし、その一次情報はインターネット上のどこにもない状態でしたので、作成の背景も含めて参考にできる YOT の一次情報をここに記します。 TL;DR 振り返りの場のファシリテーターには、発言量が少なくてうまく場が盛り上がらない、というあるあるな悩みがありますよね 起こったことや思ったこと(お気持ち)を気軽に共有しやすくすることを目的に、YWT をフォークした振り返りフォーマット「YOT(やったこと、思ったこと、次にやること)」を作成、活用しています 「今週はこういうことしたよ!」や「なんかこういうもやもやがあるんだよね〜」というお気持ちを話題に上げるハードルが下がることで、発言量が増える効果がありました 「話題が上げづらい」という課題 以前、 @takoratta さんの ツイート から「KPT (Keep/Problem/Try) で K があまり出ずに、P ばかりになる」といった課題から振り返り全般におけるみなさんの工夫がシェアされていました。 togetter.com BASE BANK チームにおいても KPT を利用しており類似した課題がありました。振り返りを日々の仕事に取り入れられている方では共感いただけるとおもいますが、「Keep って何を書いていいのだろうか」といった参加者の迷いで気軽に話題が上がらないといった課題がありました。 これは 2021 年 8 月 13 日に実際に行われた振り返りで作成された miro の付箋です。 2021 年 8 月 13 日に実際に行われた振り返り その場ではこのような話の流れがありました。 BASE BANK チームの人数が増え、一堂に会する人数が 12 名(2021 年 12 月 01 日時点) 弊チームは 2 つのスクラムチームに分かれていて、それぞれが 2 週間スプリントを回す スクラムチームとしては別れつつも 1 つの BASE BANK チームとしてお互いの境遇を共有するために全体でも振り返りをおこなっていた KPT を利用していたが 「あれ?こういう事書いていいんだっけ?」っていうのが分かりづらい というのが声があがった ex.「チーム外とのコミュニケーションで起こったこと」は自分しか経験してないことだけど書いていいのかしら? レトロスペクティブにはさまざまな経験をしたさまざまな職種の方がクロスする場所なので、スプリント内での経験をたくさん話してほしい!どうすればよいだろうか... >< 以前は人数も少なかったのである程度「愚痴っぽい感じ」とか「起こった出来事」とか「自分だけが出会った境遇でチーム全体ではそんなに関係ないしな」っていうような一見遠い課題も、KPT のワークの中でたくさん話題が上がっていました。 しかし、そこにはチームのなかで 暗黙の前提が有った ことに気が付きました。過去の傾向では「自分の身の回りに起こった出来事」や「思ったこと」をカジュアルに Keep や Problem で喋っておりました。長く在籍しているメンバーであればこの傾向を知っており引き継いで振り返りの場に臨みますが、新しく JOIN されたメンバーであれば 暗黙知となってるがゆえに戸惑いが発生している 状況だと分析しました。 よって、 「愚痴っぽい感じ」や「起こった出来事」を話していい場であることを明示的にする ことを課題解決の方針としました。 出来事ベースでお気持ちを話しやすい振り返りワーク「YOT」 結論から言うと当チームでは YWT をフォークした YOT という振り返りワークを実施しています。当ブログでは、まず YWT という振り返りワークを紹介し、何を課題と感じてフォークしたのか説明します。こぼれ話として、その過程ではいくつかコミュニティ内でのシェアされている振り返りワークも検討したのでご紹介いたします。 YWT YWT は日本能率協会コンサルティング(JMAC)が開発した日本出自の振り返りワークです。Y が「やったこと」、W が「わかったこと」、T が「次にやること」の略です。 当ワークは様々な振り返りの現場で活用されており、後ほど紹介する FDL(Fun / Delivery / Learn)や WLT(Win/Learn/Try)など他のワークにも大きな影響を与えています。 https://www.jmac.co.jp/glossary/2021/09/ywt.html から引用 弊チーム状況に対してマッチする良い点は Y(やったこと)が具体的で出しやすい点でした。K(Keep したい)ことは抽象度が高くて特に English native ではない日本人チームではそのニュアンスが掴みづらい側面がありますが、「やったこと」は何を話していいのかわかりやすいというメリットがあります。よって、出来事ベースで話しやすい場を作るためのワークには適しています。 一方でチーム内でこのワークを検討した際に、 「こんなもやもやがあった」とか「こういう点がわかってよかった」といった、お気持ちを話す余地が少ない点 が懸念となりました。 YOT YOT は YWT をフォークして前述したお気持ちを話す余地が生まれるように調整したものです。 Y: やったこと O: おもったこと T: 次にやること YWT の W(わかったこと)は日本能率協会コンサルティングの説明では次のような狙いがあります。 W(わかったこと)は、一人ひとりの気づきや学びであり、個性の表れである。同じことに遭遇しても、人によって気づきはさまざまである。それぞれの人生経験、価値観や感性の違いから、現実に認識や体感の味わい方もひとそれぞれである。 一人ひとりの気づきや自分が感じたことを自覚して表に出すことで、内省を深め自らを変えていく思いを強める ことができる。 W(わかったこと)を振り返ることで自分が得た学び、気づき、教訓といったポジティブな面を見つけることができる一方で、ネガティブな点、ニュートラルなお気持ちについて吐き出しにくい側面があります。 ちょっとしたもやもやだったり、大変だったなぁ〜って思ったことなど、ポジティブなこともネガティブなことも両方含めて共有できる場になるのではないかという議論の結果、O(おもったこと)としています。 進め方 弊チームで行っているワークの流れは以下です。 n 分間 Y(やったこと)、O(思ったこと)、T(次にやること)を書き出してもらう Y -> O -> T と順番に区切ってやる進行方向もありますが、オンラインでは Miro などのコラボレーションツール上で、他の人が書いてる内容を現在進行系で見れるため YOT すべて書く時間にしています 残りの時間で書いたことの共有、深堀り、対話 進行方向として人数次第で複数のパターンが観測されています 人数が 6、7 人以内くらいであれば一人ずつターンを分けて話してもらう ひとりひとりガッツリ時間を使えなそうであればグルーピング、ファシリテーターがランダムにピックアップ 実際に Miro で行った YOT ボード(中身の文章はすべてマスクしています) YOTの評価 『 振り返りを積み上げて自分たちのプラクティスとして昇華•体得していくための仕組みと考え方 』で紹介した「プラクティスの種投票ゲーム」も行い YOT 自体の今後の活用について議論しました。 「プラクティスの種ゲーム」で YOT 自体を振り返った結果 「 今週良かったことも悪かったこともなんも思いつかないときにも書けるのがいい 」という声が YOT 自体の振り返りであがったので、「話題が上げづらい」という課題は当ワークによって解消できました。 また、当ワークは BASE の他チームでも活用されていて、そこでも同様の評価が得られました。 同僚の Y さんの声 YOT チームのふりかえりで使わせていただきました!! 「思ったこと」へお気持ちを書きやすく、良い雰囲気でふりかえりができました! 同僚の Gさん の声 何回か使ってみたけどふわっとした思いが書きやすかったという所感です 一方で、改善点を見つけてフィードバックループを回していくことをゴールにするともうひと工夫必要、という課題も確認されました。振り返りの中では、「 書きやすくはなった一方で、共有の意味合いが大きくなったため、課題からのアクションアイテムの発掘が難しそう 」という声が上がりました。 たとえば KPT であれば Problem を書くので、わかりやすく問題が振り返りの調理場に並ぶため、その場での深堀りの入り口が見えやすいです。一方で、YOT の場合は出来事やお気持ちなどの一次情報が列挙される形になります。そのため、次のアクションを見つけようとすると、それらを深ぼっていく「深堀り力」が対話の中でより求められます。 BASE BANK チームではこの性質に対して振り返りの主目的を週毎に切り替えることで YOT で確認された共有しやすさを普段のプロダクト開発のリズムに取り入れています。 2 週間 Sprint の最終日:「フィードバックループを回す」ことに主眼 それぞれのスクラムチーム(各 5、6 人)で行う KPT, YOT その他場に応じて 2 週間 Sprint の真ん中:「BASE BANK チーム全体のコミュニケーションとしてそれぞれの状況を共有する」ことに主眼 BASE BANK チーム全員(12 人程度)で行う 共有しやすさ、話しやすさを重視したワーク -> YOT を活用する その他検討した振り返りワーク 以上が弊チームで行っている YOT の説明でした。おまけ情報として「話題が上げづらい」という課題のソリューションとして検討したその他の振り返りワークもご紹介します。 Activity: Timeline Timeline は『 アジャイルレトロスペクティブズ 強いチームを育てる「ふりかえり」の手引き 』という書籍の中で紹介されている振り返りワークの一つです。当書籍から説明を引用します。 長期のイテレーション、リリース、プロジェクトのレトロスペクティブで、データを収集するために使用する。 目的作業のインクリメントで何が起こったのかを思い出す。多くの観点を基にして、作業の全体像を作り上げる。誰がいつ何を行ったかという前提を調査する。パターンやエネルギーレベルがいつ変わったのかを調べる。「事実のみ」あるいは「事実と感情」をデータに使う。 当ワークは次のような流れで進んでいきます。 グループメンバーが、イテレーション、リリース、プロジェクトで起きた、記憶に残ったり、個人的に意味があったり、そうでなくても重要だったりするイベントをカードに書く それらを(大まかに)時間順に並べる イテレーション、リリース、プロジェクト中に起きたイベントをチームで議論 このワークで「個人的に意味があったり、そうでなくても重要だったりするイベント」もスコープに含めて、チームで議論するワークなので出来事ベースで振り返るワークとしてはマッチする点がありました。 使用用途としては長期の時間軸なので、隔週ペースくらいの振り返りで使う場合は「1、2 週間のなかでプロジェクトで起きた、記憶に残ったり、個人的に意味があったり、そうでなくても重要だったりするイベント」をタイムライン上で埋めてみる、というワークの実施方法はありそうだとおもいました。 日本国内の事例だと freee さんが Timeline を振り返りワークとして活用した ことを紹介しています。 ただ、中長期のプロジェクトの振り返りのほうが本来のワークの目的にも近いのだろう、ということで今回の課題に対するソリューションとしては採用していません。 Fun/Done(Deliver)/Learn Fun/Done(Deliver)/Learn はこれまた KPT や YWT の改善として提示された日本出自のワークです。 qiita.com BASE 内にも FDL を振り返りワークとして実践してみたチームがあります。「楽しさ」にフォーカスした手法で、前向きに議論しやすいため、多くのチームで活用されていることを観測しています。 弊チームではありがたいことに、比較的前向きな対話の雰囲気のなかで振り返りがやれている(主観ですが)ことが多いのでまだ活用していませんが、振り返りワークの引き出しを広げると意味でも使ってみると面白いかもしれません。 Win/Learn/Try Win/Learn/Try は mercari の wako さんが紹介したワークです。 engineering.mercari.com 当記事内の説明にある通り、KPT や Fun/Done/Learn からエッセンスを抽出したものなようです。 そこで今まで経験したKPTやFun/Done/Learnからエッセンスを抽出してWin/Learn/Tryを考えつきました 筆者個人的な理解としては、ポイント語彙をポジティブにすることでよりポジティブな切り口が重視する狙いを込めたワークフォーマットだと認識しています。「どういうワークがチームにとって良いだろうか」と試行錯誤した身としては課題としてあげられている点に共感しました。 Winはポジティブな気持ちになれ、かつ分かりやすい切り口ではあるものの、 個別の具体例が列挙されるため、Tryへ昇華するタイミングで深堀り等の工夫が必要 LearnはWinとは逆に、 起きた事象をどう学びに抽象化するかが必要なのでアイディアを出すタイミングでのハードルが少し高い Win/Learn/Try固有の問題ではないが、同じフレームワークを使っていると課題発見の切り口が同じになり、飽き/賞味期限がくるのでフレームワークのアップデートは常に必要 特に 1 と 2 の課題は前述した YOT の課題と類似していますね。振り返りワークを評価/調整/作成する際の 1 つの設計考慮点として次の点が重要です。 参加者に、話題に上げるまでに必要な抽象化の労力を、どこまで求めるか たとえば Win/Learn/Try では、Win は個別な具体例を書けば良いのでそこまで抽象化することを考えなくて良くて、逆に Learn は「学びってなんだろう?」と参加者各々に抽象化する労力を一定求めるゆえアイデア出しのハードルが高くなります。 「話題が上げづらい」という課題に対するソリューションを考える際に、この考慮点にどうバランスを取るかは難しいポイントだったので、勝手ながらとても涙ぐましい共感をしております。 おわりに 今回は弊チームで作成、活用している YOT をご紹介しました。YWT を超絶リスペクトした上で、より自分たちに合わせた形で調整したのが始まりだったのですが、社内外関わらず他チームに興味を持って実践いただいてます。当ブログエントリが公開されることによってインターネットで検索した際に一次情報にたどり着けようになるので、今回はじめて目に触れた方々も安心してお使いいただければと思います。 冒頭に紹介したとおり「発言量が少なくてうまく場が盛り上がらない」というのは、あるあるな悩みですよね。そんなときに YOT は場を活性化させるのにうまくハマるかもしれません。悩める振り返りリーダーの課題に寄り添うソリューションとなれば幸いです。
アバター
BASEにおけるIT全般統制とCSEグループが取り組んだ内容 はじめに この記事はBASE Advent Calendar 2021の3日目の記事です。 devblog.thebase.in BASE Corporate Engineering CSEグループ マネージャーの小林 ( @sharakova ) です。 タイトルに記載のとおり、BASEにおけるIT全般統制とCSEグループが取り組んだ内容を説明させていただきます。 BASE株式会社は、2019年10月25日に東証マザーズに上場しましたが、上場企業は、金融商品取引法(いわゆるJ-SOX法)の遵守が求められます。そのため、2021年度末までにIT統制として不十分な項目の是正・必要書類の作成などが必要となってきます。 私は、バックエンドのアプリケーションエンジニアとしてBASEに入社をして、2020年にコーポレートエンジニアのチームに異動しました。コーポレートエンジニアが取り組むべき内容なども分からないまま異動し、J-SOXの対応としてIT統制が必要だということ、さらに期限があり大変なプロジェクトだということは、異動してから知ることになります。 devblog.thebase.in 昨年のアドベントカレンダー記事で、CSEグループがIT統制の整備に取り組んでいる事を書かせていただきましたが、この1年で取り組んできたIT統制、主にIT全般統制(ITGC)の取り組み・整備についての概要を説明させていただきます。 社内説明会 CSE・情シス内での輪読会(2020年) CSEメンバーには、IT統制や内部統制について深く理解しているメンバーがいなかったため、プロジェクトを開始する前にメンバー全員でIT統制についての輪読会を実施しました。 金融商品取引法(いわゆるJ-SOX法)は2006年に成立し、2008年に適用が開始されました。上場企業においてはJ-SOX法に遵守するために、IT統制が必要になります。 ただ、2008年以降のIT業界の変化は激しく、2020年代においてアジャイル開発、GitHub、AWSの利用も主流となり、これらの技術やSaaSなどを利用しながら、どのような方法ならJ-SOXに遵守できるのか、メンバー同士で議論することになります。 エンジニア向け説明会の実施(2021年3月) 2020年末までの課題として、売上に関わるような開発が突発的にされており、IT統制として知るべき変更内容が共有されていない状況が続いていました。 是正するため、2021年最初に取り組んだのが、バックエンドエンジニア向けに社内説明会を実施することにしました。 CSEグループが取り組んでいる内容を共有しつつ、IT統制の必要性、考え方を説明。それに伴い開発ルールも変更されることを伝えました。ほぼ全てのバックエンドエンジニアやPMの日々の業務に関わるため、協力・理解してもらう必要があります。 説明会を実施後にも、多くの機能が開発・リリースされておりますが、売上に関わるような開発は、最初にCSEグループに相談してもらえるようになり、社内にIT統制の考え方が浸透するきっかけになったと思います。 IT全般統制 IT統制の中で、基本となるのがIT全般統制(ITGC)になります。 IT全般統制で売上を管理しているシステムを特に重要視する必要があり、特に以下の項目においては、操作ミスや不正などを防止(予防的統制)・発見(発見的統制)の2つの観点から仕組み・ルールを整備してきました。 BASE管理画面 GitHub データベース サーバーログイン・設定変更 会計ソフト 決済代行会社アカウント 以降、詳しく説明します。 BASE管理画面 BASE管理画面は、BASEの社員などが利用する社内用の管理画面の事で、購入を強制的にキャンセル、売上データの変更、割引率の設定、クーポンの発行などサービスを運営する上で必要な機能ではあるが、売上に関わるような重要な操作も可能で、さまざまな仕組みやルールを整備しました。 ロール / Access Control List (ACL) BASE管理画面では、利用者全員に管理者、経理、閲覧者、外部委託者などのいずれかのロールを設定する事で、操作可能な機能を制限しています。その上で、特に気をつける必要があるのが、管理者、経理ロールとなります。 管理者ロールは、アカウント作成・他のロール設定変更を含めて全ての機能が利用可能です。 経理ロールは、売上データを操作などの売上に直接関わる操作を可能としています。 権限の付与ルールの整備などを行いながら、アカウント台帳の作成とロール権限の再定義などをしました。これにより、想定外の人への誤ったロールを設定するなどのミスをなくし、売上に関わる操作でも、申請者と承認者を適切に分離する事で、誤った操作を防ぐ設計に変更しています。 操作ログの通知 上記のロールの設定をすることで予防的統制を実施し、発見的統制として重要な操作のSlack通知機能を実装し活用しています。特にロールに関する変更操作など、影響範囲が大きく、気づいたら「売上に関わる操作が誰でもできる状態」などならないように、監視する体制づくりも実施しました。 GitHub BASEのOrganizationでは、リポジトリが350以上存在していますが、すべてのリポジトリをIT全般統制の対象とする必要がなく、まず重要なリポジトリを精査し統制対象と定義しました。対象リポジトリにおいてコードレビューを必須化、変更理由の記載などより強いルールに変更することにしました。 対象リポジトリ BASEサービスの基本となるリポジトリ 売上を編集・操作できるリポジトリ インフラの設定変更が可能なリポジトリ 共通ライブラリなどのリポジトリ いままでのBASEでは、障害時などの緊急のコード編集が必要になることが考慮されているなどの理由から、コードレビューを厳格化できておりませんでした。この数年でエンジニアの採用も進み、またコロナ禍において、テレワークが定着化し、緊急時でも複数人で対応できる体制になったおかげもあり、Pull Request のレビューを厳格することができ、対象リポジトリにおいてレビューされてないコードがデプロイされる事はなくなりました。 データベース 近年ではデータベースと一括りにできない部分ではありますが、BASEでは、Amazon Auroraをメインのデータベースとして利用しています。 このデータベースのデータの健全性を守る事が最重要の項目であり、データベースの値に間違いが無いことを証明するため、上記のBASE管理画面、GitHubなどあらゆる手段で守っていると言っていいでしょう。 しかし、現実的にはSQLを流して、データを書き換えるなどの業務も発生します。 SQLでの更新検知 SQLを手動で実行してデータ更新については、他のエンジニアのレビューしてから実行するルールが確立されていました。さらにレビューした内容でのSQLを実行したのか、不正なSQLが実行されていないかを検知することをすることにしました。 まずは、”BASEシステムが利用するDBユーザー”と、”SQLでの書き換えできる人のDBユーザー"を完全に分離し担保する必要があり、その上で、手動でデータベースを更新をした場合に、Slack通知する事を構築しています。 バックアップデータからのリストア試験 データベースを復旧試験・手順書の見直しました。実際にはデータベースだけではなく、サービス提供全体の復旧試験などを行った方がよいです。しかし、実際の復旧構築の試験となると、他のSaaSとのつなぎ込み部分など多岐にわたるため、今年度は試験を範囲を限定し、毎年復旧試験の内容を変えて実行するなどを考えていきたいと思っています。 サーバーログイン・設定変更 アカウント管理 誰がサーバーにログインできる状態なのか、台帳管理などを用いて証明する必要がでてきます。BASEではChefを利用したサーバー構築、設定変更を行っているため、Chefを管理しているGitHubリポジトリへのアカウント追加のPull Requestが申請・承認となり、コード化された設定ファイルが証跡(台帳)となるように、監査法人へ説明しています。 臨時ジョブ(バッチ)の実行 通常のジョブ実行にはcrontabを利用したジョブ実行を行なっていますが、臨時的にジョブなどを実行したりする場合などがあります。そのため、社内でバッチサーバーと呼ばれるサーバーに、SSHログインし、手動でジョブを実行できる環境などが整備されております。 しかし、ログインできるアカウントを管理しているとはいえ、誰がどのような作業をしたのか把握できている状態とはいえず、どのサーバーでどんな作業をするのか、申請・承認を経て実行していただくように仕組み・ルールを変更を行いました。 ソースファイルの改ざん検知 GitHubでコードレビューを厳格化しても、サーバーにログインできると、ソースファイルをサーバー上で書き換えが可能な状態となります。SSHログインできるアカウントを管理しているとはいえ、リスクを伴います。 Dockerなどでサーバーを構築できれば、そもそもSSHログインできないなど、ソースファイルをそもそも書き換えできない状態にするのが望ましいとは思いますが、すでに確立しているデプロイシステムなどもあり、既存のEC2サーバーにファイル改ざん検知する仕組みを導入し、Slack通知で検知する仕組みを構築することにしました。 会計システム BASEでは、会社設立当初から会計システムとして外部のパッケージソフトウェアを導入し利用しています。 BASEシステムと同様に、売上を管理する重要なシステムでありながら、社内のエンジニアは運用方法を把握できておらず、IT統制の内容確認には苦労しました。 BASE社内で開発している製品ではないものの、ベンダーとの契約内容の確認・アカウント発行ルール・ロール設定・バックアップ・復旧手順の作成など、整備する項目はあります。 バックアップ/リスト手順 利用している製品のバージョンも関係しているのですが、日次の自動バックアップなどを設定できなく、経理部門が手動でバックアップしている状態でした。 ただ、ヒヤリングしてみると同じサーバー内にバックアップをとっている事が発覚し、障害が起きた場合に、結局復旧できそうもない状況であることがわかり、サーバーからGoogle Driveをマウントする形に設定変更をし、バックアップ先の変更をしました。 システムを外部ベンダーに依頼だけで、BASEのエンジニアが積極的に運用に参加していなかったなどの課題も発見しました。 決済代行会社アカウント BASEでは、クレジットカード、コンビニ払い、Amazon Pay、PayPalなどの多様な決済方法を提供しており、各決済代行会社を通じて決済させていただいています。購入時などは各社のAPIなどを利用することで決済をし、BASEのデータベースと決済代行会社の決済のデータが常に同期されている事を担保しています。 しかし、各決済代行会社の管理画面がそれぞれ存在しており、管理画面にログインを行うと、決済にまつわるさまざまな操作が直接可能になります。 権限によっては、決済を強制的にキャンセルなども行えるため、決済代行会社のアカウントは厳重に管理する必要があり、アカウント発行のルールの作成と、申請・承認・発行フローの整備を行いました。 終わりに すごく厳格なルールを整備してしまうと、私がBASE社に入社して最初に感じたスピード感が失われてしまう可能性もあります。スピード感を保ちながら、それでいて安全にするために、何ができるのかという観点でメンバーとも議論を重ねながら、さまざまな事に取り組んでまいりました。 1年を通じて、ルールを作成するたびにエンジニアへの説明会を繰り替えしながら、時にはエンジニアからの反発などもありましたが、一度作ったルールをそのままにするのではなく、継続的にルールを見直していく事が成長をするBASEらしさだと思うので、変化できるように体制強化を目指していきたいです。 またYouTubeの方で、”Web系ベンチャー上場後の「現場のDX」の難しさ”という内容でインタビュー動画を上げさせていただいているので、合わせて見ていただければと思います。 【前編】Web系ベンチャー上場後の「現場のDX」の難しさ - YouTube 【後編】Web系ベンチャー上場後の「現場のDX」の難しさ - YouTube 明日は、フロントエンドエンジニアのがっちゃんの「Denoの今を知る」の記事を公開する予定です。お楽しみに。
アバター
この記事はBASE Advent Calendar 2021の2日目の記事です。 devblog.thebase.in はじめに こんにちは!BASEでエンジニアをやっている @kimukei です。 現在BASEの顧客管理に関係する機能の開発に携わっています。 今回は、10/14 にリリースされた「BASE」の顧客管理がどのように開発されていったかについて、その一部についてお話ししたいと思います。 顧客管理とはどのような機能か 今回リリースした顧客管理は、 BASE U でも紹介されていますが、もともとあった拡張機能である「顧客管理 App」のリニューアルとなり、簡易的に提供されていた機能をパワーアップし、標準機能としてすべてのショップに提供したものです。 今回のリニューアルで顧客の生成元を見直し、購入回数や購入期間などの条件を指定して顧客グループを作成できるようになりました。 購入回数と購入期間から顧客をセグメントに分けることができ、そのセグメントを顧客グループとして扱えるイメージです。 顧客をセグメントに分けるイメージ この機能によりショップオーナーさんによる顧客分析を可能にし、限られた範囲にリーチさせたい施策需要(例えば、離反顧客にカムバッククーポンを配布するなど)を満たす土台ができました。 現在は「メールマガジン App」から顧客グループを利用でき、顧客グループに向けてメールマガジンの配信を行うことができます。 今後も顧客管理が連携できる他のAppや機能などは増えていくと思います。 以下では、主に技術的な構成についてお話していきます。 システム構成概要 はじめに顧客管理のシステム構成の概略図です。 ご覧の通り、非同期なアーキテクチャをしています。 また、一部簡略化、省略している部分があります。雰囲気を掴んでもらえればと思います。 このシステムを採用した理由やポイントがいくつかあるため以下で説明していきます。 システム構成の概略図 なぜ非同期にしたのか いくつか理由がありそれぞれ説明します。 顧客システムのエラーを理由に注文が失敗してはいけないこと 「BASE」のようなECプラットフォームにおいて最も重要なトランザクションは言わずもがな「注文」です。 お金が発生するトランザクションはビジネス上、法律上、UX上、あらゆる点において最も整合性が保証されていなければなりません。 「顧客」は購入金額などを持ち合わせますが、仮に同期的な処理を取った場合、顧客システムで発生したシステム不整合を理由にそれらの重要なトランザクションをロールバックすることが発生するためビジネス上、大きな打撃になります。 そのため、注文のトランザクションとは別に顧客システムのトランザクションを実行する必要があり、「顧客」に関係するトランザクションを逃そうと思いました。 処理する場所は一箇所にする 「顧客」の作成や変更が伴う操作(ここではこれを「顧客イベント」と呼称する)は、BASEのシステムのあらゆる箇所に点在します。 例えば、 注文 注文編集 注文キャンセル が顧客イベントの一例にあたり、注文はweb上からもできますし、Android/iOSで提供されている購入者向けの ショッピングサービス「Pay ID」 上からもできます。注文の編集やキャンセルはショップオーナーが利用する BASE Creator からも可能です。 注文以外でも「顧客」は作成され、 コミュニティへの入会 抽選商品の当選/落選 でも作成されたりします。 そのように数多く存在する顧客イベントの処理を一元化する上でも非同期アーキテクチャにすることは有用だと考えました。 マイクロサービスアーキテクチャへの移行 BASEのシステムは現在モジュラモノリスに近いものとなっております。 もし、部分的にマイクロサービス化をしていくなら、顧客システムはマイクロサービス化されるべきものの筆頭であると考えています。 顧客システムはそれ単体で十分成り立つ単位であり、扱うドメインはある程度閉ざされていて、継続的な機能開発が見込まれる領域でもあります。 組織がグロースするとおそらく顧客管理チームが出来上がるのではないか、とも予想されます。仮にそうなったら、逆コンウェイの法則から顧客システムは自然とマイクロサービス化へ向かうことでしょう。 これらの懸念があったことも大きな理由です。 また、余談ではありますが、完全なるマイクロサービス化はしていないためsagaパターンやAPIゲートウェイパターンは実践していないものの、 マイクロサービスパターン[実践的システムデザインのためのコード解説] で紹介される顧客サービスの仕組みは、私たちがつくった顧客システムとかなり近しいものでした。 ビジネス上のOKが出た 最終的にこれは大きいですねw どうやっても同期的に更新されないといけないものはありますが、顧客情報についてはその限りではなかったためここまで考えて決断することができました。 また、非同期と言っても5秒もあればほとんどの顧客情報の反映は終わっており、そういった遅れるとしても何秒くらいだろうという検証を事前に実施し、温度感をPdMと共有し議論できたのが良かったです。 非同期な仕組みにすることで考えることは増え、設計の複雑さや難易度が上がることは事実だと思います。 ただ、上記の理由より、顧客システムをこのタイミングで非同期な仕組みにしてアーキテクチャを分離しておくのは十分有用だと考えました。 それでは次にこのシステムを構成する上でいくつかポイントがあるのでそれを紹介します。 SNS -> SQS の冗長化 顧客イベントについては、メッセージキューイングの仕組みに1段SNSを挟んで冗長化させています。 これは、顧客システムの未来が確定していない中で、システムの拡張性を持たせたかったためです。 SNSがメッセージのpublisherになることで、subscriberの変更は容易になるし、複数のsubscriberに配信することもできます。 詳しくはクラスメソッドさんの、 【AWS】SQSキューの前には難しいこと考えずにSNSトピックを挟むと良いよ、という話 がとても参考になります。 顧客イベント駆動の処理をする上で いわゆるイベントソーシングというやつです。 「顧客」に関わるイベントをいくつか定義して、SQSのメッセージに顧客システムが処理できる情報を詰める 顧客システムの worker がメッセージを受け取る 顧客イベントを処理する その顧客イベントを処理した履歴を保存する 2 の worker とは、一般的にはwebサービスのバックグラウンドで特定の処理を実行するプロセスのことですが、ここでは、SQSのキューを監視し、キューに登録されたメッセージに対して何かしらの処理を行う常駐プロセスのことを指します。 Supervisor を用いてバッチ処理をデーモン化することで実現しています。 4 が最も重要なことで、これがあるからSQSを利用できているし、過去データを元にした「顧客」の作成もオンラインで実施できました。 まず、今回採用したSQSのキュータイプはFIFOキューではなく標準キューです。顧客イベントの量的に複数の worker プロセスで並列してメッセージを取得して処理したかったためです。 SQSの標準キューは少なくとも1つ以上のメッセージを保証しています( https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html )。 つまり、同じメッセージを繰り返し処理することがあり得て、処理した履歴を保存していないとそれが処理済みのメッセージかどうかが不明になり、2重計上の恐れが出てきます。 履歴を持つことで冪等性を持った処理が可能になり、2重計上を防げるということは、過去の注文データなどから顧客データの生成も安全に行えるようになります。 2重計上が防がれ安全にデータ移行が行われるイメージ図 これにより、今までのすべてのデータから「顧客」を生成する(チームではこれを通称「データ移行」と呼んでいます)大規模な操作をサービスメンテを入れずにオンラインで実施することができました。 仮にサービスメンテを入れるとなると、「BASE」ももう誕生して10年目を向かえているサービスで膨大なデータ量があり、それらをすべて処理する必要があるため1日はサービスを止めることになっていたでしょう・・ ちなみにこのデータ移行は確認を含め完了まで2営業日要しました。 データ不整合をリカバリする仕組みは必須 非同期な分、データ不整合はどうやっても起こり得ます。 SNSのpublishが失敗することがあるかもしれませんし、DB負荷など何らかの理由で顧客イベントをpublishする前に処理が落ちてしまうかもしれません。 それらのデータ不整合を検知して修正する仕組みは非同期なアーキテクチャをとる以上必要不可欠です。 また、データ不整合を検知する仕組みは、データ移行の際にも役立ちます。 データ移行を何をもって完了と定義するか、最終的な受け入れテストツールとしてデータ不整合を検知する仕組みが利用できるからです。 これは本当に大事で、データ移行ってだいたいバッチ処理とかで実行することが多いと思いますが、すべてのショップにデータ移行が正常に完了したかを別の仕組みを用いて確認するまでが1セットのデータ移行です。帰るまでが遠足みたいなことです。最初このことに気づかず慌ててデータ不整合を検知する仕組みを作りました。是非ここまで読んでくれた方が我が身となったときには慌てずにデータ移行を完遂してくれることを願っています 🙏 なので、データ生成と同じタイミングでデータ不整合を検知する仕組みを一緒に用意しておくと良いです。 おわりに BASEは現在、未来に向けた土台を作りながらも日々サービスを成長させていっている最中です。 最近ではNew Relicを用いたサービス監視も盛んになってきました! 興味のある方、やっていくぞ〜 💪 な方!お待ちしております!是非一緒に未来をつくっていきましょう! 下記のリンクや ぼく に連絡ください! https://open.talentio.com/r/1/c/binc/homes/4380 明日は弊社CSE(Corporate Solutions Engineering)チームによる、IT全般統制について取り組んだ話です。おたのしみに! それでは〜〜 👋
アバター
こんにちはBASEの開発担当役員をやっている藤川です。この記事はBASEアドベントカレンダーの一日目の記事です。今年もよろしくおねがいします。今年のスケジュールや去年までの記事に興味がある方は是非こちらをご参照ください。  devblog.thebase.in 2021年のアドベントカレンダー一記事目には、個人ブログで好評を得た「新人エンジニアリングマネージャを採用脳にする」を会社のブログ記事に書き換えて再配信したいと思います。 タイトルをエンジニアリングマネージャではなく、技術系マネージャとタイトルを変えているのは、2021年末時点の当社の組織において、EM(技術チームのリーダー)とグループマネージャ(EM of EM's)と2段階に構成されていて、現状、この記事で論点としているスカウトなどの重い採用活動はグループマネージャが担っているという状況があります。EMはよりプレイングな側で活躍してもらってることが多いです。 上記が抽象化した組織図ですが、緑色の部分がグループマネージャで、BASE事業を構成するKPIなどを元に目的別に分解をした事業ドメインのチームと一緒に動く開発チームになっています。(事業ドメイン側には、この人のエイリアスが存在していてOKRが共有されている)ある種、事業ドメインのVPoE的な役割を持っているのがグループマネージャです。本記事の対象はこのポジションの人になります。 こうすることで、Service Devと書いてあるエンジニアリングセクションは、採用、人材育成、組織構築を中心にサービスを向上するための開発目標の達成とサービス性維持を行うことをマネジメントしている組織ツリーと定義づけています。多くの会社だと採用を担う技術系チームはrecruting側に寄せたりしますが、我々はメンバーの採用、育成に責任を持つことこそが技術系マネージャの最重要課題だと定義して、技術チームの組織ツリーは構成されています。 BASE社に入社したエンジニアは、最初に藤川とCTOの川口と二人で技術に関するオリエンテーションを受けているのですが、そこでもマネージャが採用をやっているという話をしますが、よくよく考えるとマネージャが採用をやっているというのは、あまりピンとこない方も多いのではないでしょうか。昨今のネットのスタートアップ企業のCTOやマネージャ経験がある人なら当たり前になっていますが、他の業種やSIer、新卒採用を会社でやっている大企業などに関わった経験がある方だと、どちらかというと人事が人を集めてくるというイメージがあり理解し難い部分もあるかと思いまして、その活動の意義をできるだけ知ってもらおうと思って話をしています。 マネージャがスカウト行うメリット マネージャがスカウトを行う理由は、チーム組成を担う仕事として、プレーヤよりもマネージャの方が主体的に動けること、人を見る目を養っていく仕事が故に、良い人材へアプローチできることを意識してやっています。スカウトは、こちらが先んじてスクリーニングしているが故に、やはりエージェントさんの紹介よりもチャレンジングな人選も含めて人のマッチ率が高い、つまり、面接通過率が高いという特徴があります。 一方で、一言でマネージャと言っても、ずっとマネージャをやってきた人もいれば、ちょっと前までプレーヤーとして活躍して、マネジメント職について経験が少ない人達もたくさんいます。成長前提の組織においては、どこかしらどこかの役割において新人さんがいて、マネージャも例外ではなく、育成、成長を前提とした目標管理をしないと、ただそこに人がいるからと言って、特定の仕事に対して一人前のパフォーマンスを出せるわけではありません。 スカウトを行い面接を通じて人を採用するためには、テクニカルなノウハウは必要で、一例でいうと、 レジュメ等の限られた情報からの人選(見抜く目やマネジメントに自信がないとチャレンジできないかミスマッチを生む) スカウト文面の書き方 カジュアル面談や面接で聞くことの質の向上 我々の事業に共感してくれるアトラクトメッセージを洗練させる 人の特徴を知り、一緒に働ける人材であるかを見極める 採用する人の成長の道筋を見極めて、給料をあげていける人材か?それをうながせる相手であるかの覚悟ができる などなどがあります。 これはこれで重要で、実際に採用活動をしながらrecruitingチームと振り返りを行うことで精度を高めていきますが、とはいえ、PDCA的に採用の質をあげていくためにもある程度、スカウトを行い、カジュアル面談を実現し、ある程度、その人にとっての成功、失敗事例が存在してからが、この改善のチャンスだとも言えます。 よく経営者が話がうまいのは、社員、パートナー、投資家に話をする場数をこなしているからだと言われますが、採用も場数が重要です。コミュ力は生来備わってるものではなく、対話しながら培うものです。 なにより、エンジニア採用にあたって同じ目線で話すことができるのがエンジニアリングマネージャが採用に携わる理由と言えるでしょう。これまでのキャリアと自社のドメイン知識を活用しながら、やってほしいことをマネージャとしての責任を持って言葉として伝えていくことこそが内定承諾につながるわけです。 マネージャのマインドセット問題 そのためにも、まずは場数をこなしながら人をアトラクトするスキルを改善していくことが、本人の成長の早道となっています。しかし採用活動初期においては、まず、この採用活動の速度を出すのがそもそも大変だったりします。 当然、マネージャ職ですから時間の使い方は本人に委ねられているわけですが、それまで開発のプレーヤーとしてやってきているが故に、特に新人マネージャは、それまで目を配ってきた開発のことサービスのこと、そして、新しく始めたメンバーのマネジメントにマインドシェアを持っていかれがちです。ざっくり言うと、こんな脳内構成になっているでしょうか。 もっと明確にセキュリティとかサービスのパフォーマンスというキーワードを入れても良かったかもですね。僕たちは日常起きうるアラート対応が24/365で最優先にマインドシェアに存在していますよね?その時点でマインドシェアにゲタを履いているわけです。 これらをどうにかこうにか整理しながら、以下のように持っていく必要があります。 僕自身も以前CTOとして働いていた時でさえ、この状態に持ってくるのに苦労しました。 とにかく、採用活動が重要なタスクであり、チームを成長させる道筋なんだ、ということを体で理解するためのフェーズが存在します。ある種プレーヤーとしてのマインドセットをアンラーニングするプロセスが必要で、それが数ヶ月は必要です。組織の役割を整理し、自分の言葉で人を採用できるようになるまでに、人によっては半年ぐらいかかるのかもしれません。 僕の場合は、社長との1on1で、このことを言われまくって少しずつ心の中の自覚を持っていったという流れがあります。今の組織では、私であったり、セクションマネージャが担います。テクニカルな採用スキルは並行して身につけていくものの、このマインドシェアが実現できてなければ、すべては掛け算はゼロになり採用は実現できません。 僕の実体験としても、コードを書くために深く深く集中している時に違うタスクを振られるのは非常に苦手でした。一言でいうと不快になります。プレーヤとして技術に向うのが縦方向に深く集中するベクトルだとすると、人に会って面接をするのは、我々の取り組みについて表現可能なことを、心を平面方向に広げながら会話するという感じで、直行したイメージを持っていました。以前は面接の時間が来た時のスイッチングコストに苦労した時期もあり、そんな辛い思いをするならばと、段々、コードに向き合うのをやめていきました。プレーヤーとしてのマインドセットをまるごと他の人に任せていくことにしてマインドシェアの隙間を徐々に開けていったわけです。 採用活動をマネージするために それまで自身の強みとしていたプレーヤーとしてのエンジニアという仕事は、ものすごく細かいところに目を見張って、不具合発生可能性というリスクに目を向け、その改善に集中するというのをコードで表現するのがスキルセットであったりもします。自分自身も正直、採用なんてやったことなかったし、自分の得意な仕事でもなんでもないし、もっとコードやプロダクト開発、運営に集中したいと思ってしまいますし、実際、それを求めてフリーランスになる人もいてもいいぐらいのキャリアとしても大きな分岐点だとも思います。ただ、どうにかこうにか頑張って、組織が大きくなって、そこで活躍している人たちが増えてくれると、それはそれで楽しいものです。 人がいない時は、ただただ人がいないという前提に基づいた悩みしかないですが、人が増えると、コミュニケーションに関するいろんな問題や軋轢が増えたりして、今度は問題が起き続けるという状態になりますが、そっちの方が考えられることが増えて楽しい状態になったりします。 何より自分よりも優れた人材や成長期待の高い人材に自社のビジネスに共感してもらい、人材への投資の形で採用を実現し、プロダクトの質をあげ、ビジネスの成功につなげ、チームメンバーのさらなる成長や期待値のアップに連動する昇給を実現することで、自分自身の報酬自体もあがるという構図が作れます。 それまでのキャリアとは非連続な活動を求められて大変な仕事だなと思います。マネジメントに求められる、組織を作る、開発進捗の実現、採用の実現というのは、それまでやっていた要件から仕様を積み上げて実現可能性を確保するという、技術力を下地にコンピュータをロジックで制御するためにコードを書く仕事とは違って、対人に対して不確実性を御す仕事であると考えてみると、共通点が見えてくるというものではないでしょうか? 最後に繰り返しにはなってしまいますが、若手をチャレンジングに重用することも、他社での経験者を我々のスピード感にあわせてもらう(もしくは周囲がついていける形で引っ張ってもらう)のもマネージャの重要な仕事で、それを実現するためには技術力を下地にしたコミュニケーション力が必要です。採用する技術者のキャリア形成に責任を持つことは紛れもなく技術者だからこそできる仕事であること、35歳定年説で語られた「管理職」などという、どこか静的で収まった役割になるのではなく、常に動的に組織を構成していくために自分自身の成長を伴ったクリエイティブな仕事であるということを知っておいてもらえると助かります。
アバター
こんにちは。BASE BANK 株式会社 Dev Division にて、Engineering Manager をしている東口( @hgsgtk )です。 TL;DR 第 17 回 Ques にて「CI のためのテスト自動化」というテーマでの登壇依頼をいただき「Agile Testing を夢見たテスト自動化 〜ATDD への挑戦から始まる 1 年間の試行錯誤〜」というタイトルで発表しました 実際にうまく行かなかったことも含めてテスト自動化のしくじりを話しました Agile Testing・ATDD/BDD/SBE に興味がある方に参考になる資料を公開しました 第17回Quesとは Ques とは Software 品質保証に関わる QA エンジニアの活性化を目的とした QA 専門イベントです。 ques.connpass.com Software品質保証に関わるQAエンジニアの活性化を目的としたQA専門イベントです。 会社間の垣根を越え、情報交換・親交の場を提供することで 日本のSoftware品質保証を盛り上げていく場にしたいと思います。 当日のアーカイブは YouTube にて配信されています。 www.youtube.com 今回、 kyon_mm さんとのイベント『 TDDとATDD 2021 』や、公開していた資料『 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 』を見てくださっていた QUES 事務局の方から依頼を頂き 45 分ほど講演の時間をいただきました。 登壇に至る経緯 登壇内容 個人的に 3 部構成の最終盤という意気込みで作成した資料です。 TDDからATDDへ歩みをすすめる @ July Tech Festa 2021 Winter 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 @ PHPerKaigi 2021 Agile Testing を夢見たテスト自動化 〜ATDD への挑戦から始まる 1 年間の試行錯誤〜 @ 第 17 回 Ques <- New!! 登壇の際に用いた資料はこちらです。 タイトルの副題「ATDD への挑戦から始まる 1 年間の試行錯誤〜」にもある通り、実際に ATDD(Acceptance Test-driven Development)の取り組みとそこから始まる試行錯誤について赤裸々にまとめています。ダメだった・できなかったこと、その考察も含めているのはなかなか珍しいと思うので、参考になると思います(少なくとも挑戦前・挑戦中の自分が見て参考になるようには設計しました)。 たとえば、テスト自動化のしくじりとして次のようなことを紹介しました。 HOW から課題提起してしまう テストデータの用意が億劫で心折れる 「E2E テスト」の解像度で議論してしまう 人によって異なるE2Eのスコープ もしかしたらこの画像だけは事前に 小出しに上げていたツイート で見た方がいるかもしれません。 当日投稿した Tweet は多くの方に拡散いただき、リアルタイムで参加された方以外からも多くの反応をいただきました。 はてなブックマーク では翌日起きたら 100 はてぶ(当記事執筆時点で 155)を超えていて、自分・自チームの試行錯誤が少ならからず同じ戦場で戦う方々の刺激になるものになってよかったなと思います。 当日のツイートは QUES 事務局の方々にまとめていただいていたので、こちらも補足資料として紹介しておきます。 togetter.com Q and A 当日参加者の方に頂いた質問に対して答えたものを補足として置いておきます(その場だから喋ったみたいなクローズドなものもいくつかありましたので、それは当日参加の方だけのシークレット情報ってことで...)。 テストデータは毎回作るとありましたが、テスト終了後は消してクリーンアップするでしょうか? 実際まだテスト終了後のクリーンアップはしていません。新規にユーザーを作成すると gauge-xxx みたいなユーザーが貯まることにはなっているのが現状です。ちょうど先日、ゴミデータとして溜まってしまっていることが開発業務内で課題になったことがあり、定期的にクリーンアップするような試みを現在進行系で行おうとしています。 本筋から外れますが、Circle CI のフローで Security Scan と書いてあったところでは具体的には何を実施してるんでしょうか。差し支えなければ教えていただけたら幸いです。 Circle CI のフローとは こちら のスライドです。 Circle CI のワークフロー Security Scan という箇所では trivy というツールでコンテナイメージのセキュリティスキャンしてます。 Playwright の選定は Capture & Replay で比較的メンテがしやすそうで選定されたという認識ですが、比較的TestCodeを書くスキルのないQA専任者のような人でもメンテナンスし続けられるような狙いもあったのでしょうか? QA 専任者のような人でもってのも狙ってはいましたね。今回のペルソナになるメンバー(自分も含めて)はテスト自動化自体にしっかり時間を使って試行錯誤できないので、Capture & Replay でかつコミュニティの知見がある(ググったら出る)ってのも大事なポイントと捉えて選択しました。 おわりに ozonohiroaki さんが発表された『 これからのCI、これからのE2E自動テスト 』もとてもおもしろかったです! PR-Driven E2E Testing の構想と実行、それに至るまでの個人・チーム全体の課題意識が赤裸々に語られていて、まさに Ques でしか聞けない内容だったと思います。個人的には Pull request・CI といった用語にも丁寧に事前解説されていて職種の多様性もカバーしている良い試みだな〜とか、立って登壇することで身振り手振りがオンライン上からも分かるのいいな〜とかスピーカー目線で参考になったこともいくつかあり感謝です。この場を借りて、 ozonohiroaki さん素晴らしい発表ありがとうございました! また、参加者の皆様のリアクションがたくさんいただけたのもありがたかったです。自分は Twitter と Discord と Zoom の chat 欄を表示しながらリアクション見ながら発表していました。なのでリアルタイムでコメントがいただければいただけるほどありがたいのですが、たくさんいただけたので「この辺は参考になったんだよかった〜」とか 45 分の間も安心しながら話すことができました。参加者の皆様ありがとうございました! 最後に、QUES 事務局の皆様イベントのセッティングから事前ミーティングでの期待値調整や当日の段取り・司会、その後のアンケート・フォローバックまでありがとうございました。第 17 回 Ques での発表を通じて自分の中で 1 つ棚卸しできました。このような機会をいただき光栄でしたし感謝申し上げます。 積極採用中! おわりのおわりに、BASE では様々な職種採用募集中です! 採用情報 から BASE というサービスや組織のことや募集中の職種を確認できます。この記事に興味を持った方へのおすすめ職種を 2 点ご紹介して終わろうと思います。 [BASE BANK_Webアプリケーションエンジニア] open.talentio.com 当発表の舞台となったチームです。フルサイクルエンジニアを志向しており自分たちで開発したサービス・機能をグロース・サポートまで担当します。 BASE_QAエンジニア ネットショップ作成サービス「BASE」の品質保証業務を担っていただく役割です。
アバター