TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

576

この記事はBASEアドベントカレンダーの5日目の記事です。 こんにちは!BASEのCRM3チームでバックエンド開発を担当している オリバ( @toshi_oliver )です。2022年11月に入社したので、今回が初のブログとなります。 はじめに 前提 環境構築 デプロイ おわりに はじめに devblog.thebase.in さて、今回はAWSのサーバレスサービスを代表すると言っても過言ではない、AWS Lambda(以下、Lambda)に関する記事を投稿します。 BASEのバックエンドの大部分はPHPで開発されており、システムの一部にLambdaを使用しているのですが、Lambdaのランタイムでサポートされている言語は以下となっております。 Node.js Python Ruby Java Go .NET Core ご覧の通り、PHPはサポートされておりません。 では、PHPの使用は断念しなければならないのかというとそうではなく、カスタムランタイムを使用することでPHPの使用が可能となります。 しかし、 公式ドキュメント を見ると、ランタイム用のbootstrapファイルやシェルファイルを用意し、それをzip化しさらにLambdaレイヤー化する必要があります。 Lambdaはプログラム開発に集中するためのサービスであるにも関わらず、PHPを選択することで手間が増えてしまうのは本末転倒であります。 また、 公式ブログ によりコンテナイメージを使用する手段もありますが、DockerfileとBootstrapファイルの管理が必要となるため、やや手間が増えます。 そこで、本記事ではLambdaにてPHPのカスタムランタイム環境を簡単に構築できる、 bref を紹介します。 前提 anyenv(1.1.5) phpenv(v0.9.0-rc.1) PHP(8.1.0) AWS CLI(2.9.1) SAM CLI(1.65.0) bref(v1.7) 環境構築 空のディレクトリ上でComposerを使ってbrefをインストールします。 $ composer require bref/bref プロジェクトの初期化を行います。 $ vendor/bin/bref init 以下のメッセージが出力されます。brefでは、Webアプリケーション用またはイベントドリブン関数用のLambdaのどちらかを構築するか選択できるので、今回は 1 を選択します。 What kind of lambda do you want to create? ( you will be able to add more functions later by editing `serverless.yml` ) [ Web application ] : [ 0 ] Web application [ 1 ] Event-driven function ディレクトリ配下に以下のファイルが生成されました。 ├── sserverless.yml ├── vendor │ └── async-aws │ └── ... ├── composer.json ├── composer.lock └── index.php デフォルトで serverless.yml が生成されます。これはサーバレスのIacツールである Serverless Framework (以下、sls)で必要となるテンプレートファイルです。今回はIacツールにAWS SAM(以下、SAM)を使用するので、serverless.ymlを削除しSAM用のtemplete.yamlを作成します template.yamlのLayersパラメータに、 bref公式ドキュメント に紹介されているLambda Layerの最新ARNを指定します。今回は arn:aws:lambda:ap-northeast-1:209497400698:layer:php-81:34 を指定します。注意してほしいのが、Webアプリケーション用とイベントドリブン用で対象のARNが異なっており、Webアプリケーション用を選択した場合は php-81-fpm:34 用のARNを選択する必要があります Runtimeに provided.al2 を指定しておりますが、これはAWS が提供するパブリックの ECR に配備されている provided:al2 のイメージをベースイメージとして指定しています template.yaml AWSTemplateFormatVersion : '2010-09-09' Transform : AWS::Serverless-2016-10-31 Description : Sample SAM Template for handson-bref Globals : Function : Timeout : 3 Resources : HelloWorldFunction : Type : AWS::Serverless::Function Properties : FunctionName : hello-world-function CodeUri : . Runtime : provided.al2 Handler : index.php MemorySize : 1024 Timeout : 30 Layers : - arn:aws:lambda:ap-northeast-1:209497400698:layer:php-81:34 index.php <?php declare ( strict_types = 1 ) ; require __DIR__ . '/vendor/autoload.php' ; return function ( $ event ) { return 'Hello ' . ( $ event [ 'name' ] ?? 'world' ) ; } ; デプロイ BASEではサーバレスのIac管理をSAMで行うことが多いので、SAMを使用したデプロイを実施します。 本来なら sam build を使用してビルドを行いたいのですが、カスタムランタイムを設定している場合はmakefileを自身で用意する必要があり手間がかかってしまうので、Lambdaパッケージ用のS3バケットを用意します $ aws s3 mb s3://furukawa-bref-test --profile {任意} SAM CLIを使用してパッケージングを行います。 $ sam package \ --template-file template.yaml \ --output-template-file bref-output.yaml \ --s3-bucket furukawa-bref-test \ --profile { 任意 } SAM CLIを使用してデプロイを行います。 $ sam deploy --template-file bref-output.yaml \ --stack-name furukawa-bref-test --profile { 任意 } --capabilities CAPABILITY_IAM デプロイが完了したので、Lambdaコンソール上で試しにテストを行います。 問題なくLambda関数を実行できました。 おわりに brefを使用することで、DockerfileとBootstrapを使用せずに、LambdaランタイムをPHPに設定することができました。ただし、brefを使用する際に以下の点に注意が必要です。 brefで用意されているLambda Layerを使用すると、sam buildを使用したデプロイができない bref(v1.7)ではarm64に対応していない GitHub Issue によると、β版のbref(v2.0)ではarm64に対応するようなので、もうしばらく時間が必要 Lambda LayerのARNに、${bref:layer.php-81}を指定することでPHP8.1の最新のマイナーバージョンを取得してくれるが、 slsのみの対応 次回は、brefを使用してSlackbotを作ってみたいと思います。 明日は、kondo.hirotakaさん、yusakuさんの記事です。お楽しみに!!
アバター
Vue 2 でコンポーネントテストを書くために こんにちは。プログラミングをするパンダ( @Panda_Program )です。本記事は BASE アドベントカレンダー 2022 の4日目の記事です。 本記事では Vue 2 + TypeScript 環境に Testing Library を導入する方法をご紹介します。なお、Testing Library の使い方については本記事では触れていません。当該ツールの具体的な利用方法を知りたい方は 公式ドキュメント をご覧ください。 Testing Library とは何か Testing Library は Kent C. Dodds 氏が作成したコンポーネントテスト用のフレームワークです。 test-utils など他のテスト用フレームワークと異なり、コンポーネントの内部実装を意識する必要がなく、ユーザーがアプリケーションを使用する観点からテストを記述できる点に特徴があります。 以下で test-utils と Testing Library の公式ドキュメントからテスト記述例をピックアップして見ました。以下は完全に同等のテストではないですが、書き味や設計思想が異なる様子がテストコードから見て取れます。 vue test utils のテスト実装例 import { shallowMount } from '@vue/test-utils' import HelloWorld from '../HelloWorld.vue' describe ( 'HelloWorld.vue' , () => { test ( 'renders props.msg when passed' , () => { const msg = 'new message' const wrapper = shallowMount ( HelloWorld , { propsData: { msg } } ) expect ( wrapper.text ()) .toMatch ( msg ) } ) } ) Testing Library のテスト実装例 import { render , fireEvent } from '@testing-library/vue' import Component from './Component.vue' test ( 'increments value on click' , async () => { const { getByText } = render ( Component ) getByText ( 'Times clicked: 0' ) const button = getByText ( 'increment' ) await fireEvent.click ( button ) await fireEvent.click ( button ) getByText ( 'Times clicked: 2' ) } ) Vue でコンポーネントテストを記述するのであれば、少し前までは test-utils というツールを使うのが一般的でした。 しかし、Vue 3 の 公式ドキュメントが Testing Library を推奨していること 、2022年現在では React でテストを書くにあたり Testing Library がまず選ばれること、 Storybook を使ったインタラクションのテスト や Cypress を使ったE2Eテストの記述に Testing Library が使えること から、フロントエンドのテスト実装方法の主流を学べるのみならず、コンポーネントテスト以外にも応用できる知識が得られると考えてこちらを採用しました。 Testing Library は Vue 向けの Vue Testing Library を用意しており、これは test-utils を薄くラップして Testing Library 方式でテストの記述を可能にしたものです。Vue Testing Library は Vue 3 向けのみならず、最新ではないものの Vue 2 にも対応したバージョンが存在します。 ところが、次で説明するように Vue Testing Library をインストールしただけでは弊社の Vue 2 環境で Testing Library は動作しませんでした。そこで、以下では Vue 2 環境でテストを実行可能にするために実施した工夫を紹介します。 Testing Library を Vue 2 に導入する Testing Library Vue が使えなかった理由 Testing Library Vue が使えなかった理由を簡単に記します。 Testing Library の公式ドキュメントによると、Vue 2 では @testing-library/vue@5 を使うことを求められています。2022年12月時点で @testing-library/vue v5 の最新バージョンは v5.8.3(以下、5系と呼ぶ) です。 この Vue 2 対応の5系ではライブラリ側で Vuex に対する依存が明示的に存在しており、これが弊社の環境で5系の動作しない原因でした。 render.js 内の以下のコード store が必ず Vuex に限定される ため、テストを実行すると Vuex 関連のエラーが発生するのです。 // src/render.js export function render ( ... ) { /// ...省略 if ( store ) { const Vuex = require ( 'vuex' ) localVue.use ( Vuex ) vuexStore = store instanceof Vuex.Store ? store : new Vuex.Store ( store ) } /// ...省略 } BASE では、コンポーネントを跨ぐグローバルな状態管理に独自実装の Store を利用しています。これは弊社のプリンシパルテックリードである @ukyo が TypeScript で実装したものです。同期処理、非同期処理をシンプルに分けられて使いやすく型補完が効く上に、目まぐるしく変わるフロントエンドのトレンドや、「Vuex は辛い」「最近は Pinia(ピーニャ) がいいらしい」など Vue コミュニティの動向を気にすることなく、サービス開発に集中できる代物です(なお、現在は社内での利用のみを想定しており、OSSとして公開していません)。 つまり、弊社では Vuex を使用していません。ただし、ライブラリ側の制約から Store に Vuex を 利用しないと Store に接続するコンポーネントのテストが不可能です。 そこでチーム内で検討した結果、Testing Library Vue の render.js をコピーして一部書き換えることにより、Vuex への依存を不要としつつ Testing Library を利用したテストを書けるようにする対策を取ることを決めました。フォークしたりコードをコピーするデメリットよりも、コンポーネントのテストを書いてリグレッションに備えるというメリットが上回ると考えた結果の意思決定でした。 Vue 2 で Testing Library のテストを書くための対策 弊社のVue 2 環境で Vue Testing Library に依存せず Testing Library のテストを書くための具体的な対策は2つあります。 1つ目は render.js の Vuex 依存の箇所を削除することです。これはあまり特筆することはありません。まず Testing Library Vue の src 配下のファイル index.js fire-event.js render.js をプロジェクトにコピーし、前記の render.js の Vuex 依存箇所を削除しました。 そして、export された render 関数を使ってコンポーネントテストを記述したところ、Vuex 関連のエラーは解消されました。 しかし、グローバルなコンポーネントである社内向け共通コンポーネント集 BBQ を利用したコンポーネントに対するテストや、 Vue Router を使ったルーティングのテストを記述するにあたり、別のエラーに遭遇しました。これらを一つ一つ解消していき、最終的に出来上がった関数が2つ目の対策です。 それは、render.js をラップする関数 customRender を作ったことです。この関数の責務は、Store や Props を引数として render 関数に渡したり、グローバルなコンポーネントを登録する処理を実行することです。 // custom-render.ts import BBQ from '@baseinc/bbq' import { Store } from '@web/vue-store' import { createLocalVue } from '@vue/test-utils' import { I18n } from '@web/shopadmin' import Vue from 'vue' import VueRouter , { RouteConfig } from 'vue-router' import VeeValidate from 'vee-validate' import { render } from './render' import { addCustumValidators , getCustomOptions } from '../vee-validator-setup' type State = Parameters <typeof Store.create > [ 0 ] type Options = { initialState: State // Store の初期状態 props?: Record < string , any > // コンポーネントに渡す Props routes?: VueRouter | RouteConfig [] // ルーティングの定義 } type ConfigurationOption = { localVueOption?: ( localVue: Vue ) => void storeOption?: ( store: State ) => void routerOption?: ( router: VueRouter ) => void } export const customRender = < V extends Vue >( component: any , // this makes me sad :sob: // https://github.com/testing-library/vue-testing-library/blob/main/types/index.d.ts#L51 { initialState , props , routes } : Options , { localVueOption , storeOption , routerOption } : ConfigurationOption = {} ) => { const localVue = createLocalVue () // バリデーションライブラリの登録 addCustumValidators () localVue.use ( VeeValidate , getCustomOptions ()) // 社内共通コンポーネント集の登録 localVue.use ( BBQ , { prefix: 'bbq-' , } ) // Store の登録 localVue.use ( Store ) // i18n 対応 localVue.use ( I18n ) return render ( component , { localVue , store: Store.create ( initialState ), routes , props , } , // この第3引数は Testing Library Vue の v.5.8.3 の render 関数の第3引数に合わせている ( localVue: Vue , store: State , router: VueRouter ) => { localVueOption?. ( localVue ) storeOption?. ( store ) routerOption?. ( router ) } ) } customRender 関数を使ったテストの記述例を掲載します。これは名前の入力欄に空文字を書き込んだ時、バリデーションエラーとなりエラー文言が表示されるというテストです。input で入力した内容は直接 Store で管理しています。 import { customRender as render } from '../custom-render' test ( '名前が空欄の時、エラー文言を表示する' , async () => { render ( Component , { initialState: getInitialState () } ) const input = screen.getByLabelText ( /名前/i ) await fireEvent.update ( input , '' ) expect ( screen.queryByText ( '名前を入力してください' )) .toBeInTheDocument () } ) このテストが正常にパスすることにより、「BASE の独自 Store に接続したコンポーネントに対するテストが可能なこと」「グローバルなバリデーションライブラリのインタラクションがテストできること」「input は社内共通コンポーネント由来であるが、それに依存するコンポーネントのテストができること」の3点が確認できました。 なお、普段 React を書いている方向けに補足すると Vue 2の場合は input の入力値を直接 Store に書き込んでも、当該コンポーネント以外のコンポーネントが再レンダリングされることはありません。その結果、アプリケーションのパフォーマンスが劣化する懸念はないため、このような作りを許容しています。詳しくは Vue.js の公式ドキュメント「他のフレームワークとの比較」 のページをご覧ください。 以上で「Vue 2 で Testing Library の導入方法を紹介する」という本記事の目的は達成しました。ただ、せっかくのアドベントカレンダーなので、ここからは付録として Vue Router で画面遷移するテストの書き方の紹介と、Testing Library 導入がアクセシビリティ(a11y)の段階的な改善に繋がったというエピソードを紹介します。 付録 Vue Router で画面遷移をするテスト 本環境での Vue Router を使ったテストの書き方を紹介します。ページのアクセス時にログイン判定を行い、ログイン済みであれば /mypage に、非ログイン状態であれば /login に遷移するという架空の仕様を想定します。 この時、テストコードは以下のようになるでしょう。 import '@testing-library/jest-dom' import { waitFor } from '@testing-library/dom' import Component from '../settings-container.vue' import { render } from '../../testing-library' import { getInitialState } from '../../store' import { routes } from '../../routes' type Options = Partial < ReturnType <typeof getInitialState >> const setup = ( options?: Options ) => { const { route } = render ( // この render は上記で紹介した customRender Component , { initialState: { ...getInitialState (), ...options } , routes } , { routerOption: ( router ) => { router.push ( '/settings' ) } , } ) return { route: route () } } test ( 'ログイン済みのユーザーはマイページに遷移する' , async () => { const { route } = setup ( { isLoaded: true } ) await waitFor (() => expect ( route.path ) .toBe ( '/mypage' )) } ) test ( '非ログインユーザーはログインページに遷移する' , async () => { const { route } = setup ( { isLoaded: true , isCreated: true } ) await waitFor (() => expect ( route.path ) .toBe ( '/login' )) } ) render の返り値である route の中身は以下のような関数です。 // render.js export function render ( ... ) { // ... return { ... , route: () => wrapper.vm.$route } } なお、 Testing Library v5.8.3 での Vue Router のテストの書き方の例は公式の GitHub にも掲載されている ので、そちらも併せてご覧ください。また、もう少し上手く書けそうな気はしているものの現時点では十分に役割を果たしているため、今後さらに Vue Router を使ったテストケースが増えた段階でリファクタリングを実施しようと思います(インクリメンタルな設計)。 テスト駆動の a11y 改善 コンポーネントテストを書くことが、共通コンポーネントの a11y(アクセシビリティ)を改善するきっかけにもなりました。ここでは2つ事例を紹介します。 1つ目は、「ドラッグ&ドロップでファイルをアップロードできる」というコンポーネントの対応です。このコンポーネントを使って画像アップロードのテストを記述していた際、Testing Library で一般的なセレクタである getByLabelText() で input タグを取得できませんでした。 upload 原因は、 bbq-upload-box が Props に id 属性を受け取れなかったことです。 screen.getByLabelText('ファイルアップロード') と記述してテストを実行すると、ラベル「ファイルアップロード」に対応する form が見つからないというエラーが表示されます。 // セレクタで input を取得できない < div > < label for = "file-upload" > ファイルアップロード </ label > < bbq-upload-box label = "画像" icon = "image" accept = "image/png" @change= "uploadImage" ></ bbq-upload-box > </ div > そこで、 bbq-upload-box が Props に id 属性を受け取れるように改修し、テストの記述と実装を続けました。 // セレクタで input を取得できない < div > < label for = "file-upload" > ファイルアップロード </ label > < bbq-upload-box id = "file-upload" // この行を追加 label = "画像" icon = "image" accept = "image/png" @change= "uploadImage" ></ bbq-upload-box > </ div > 2つ目は、ボタンとしてクリックできるアイコンの対応です。アイコンボタンを含むコンポーネントのテストを書くにあたり、Testing Library のクエリを使ってこのボタンをクリックしようとしました。しかし、このままでは要素をうまく取得できませんでした。 icon buttons そこで、 bbq-icon-button コンポーネントに aria-label を渡せるように書き換え、利用側のコンポーネントからラベルを指定する変更を加えました。結果的にアイコンボタンをクリックした際に発火するコールバックのテストが書けたのみならず、Storybook の a11y プラグインの「Buttons must have descernible text」という指摘も無くなりました。 icon button a11y 対応の観点では、これらの改修内容はそれ自体ごく基本的なものだと思います。しかし、テストを書かなければ自分達でこの状態を発見することはなかったでしょう。もし気づいているのであれば既に誰かが修正しているはずです。対応内容の大小よりも、テストを書くことが a11y 改善のきっかけに繋がったことを喜ばしく思います。 ぜひ Testing Library を使ってフロントエンドでもテストを書いてみてください。 明日は @toshi-oliver さんの「AWS LambdaをPHPで使うためのベストな方法」です!ぜひご覧ください。
アバター
この記事は BASE Advent Calendar 2022 の3日目の記事です。 どうもこんにちは、ShopFrontチームの青木です。 主に ショップデザイン 関連機能の開発を担当しています。 今回は、チームのEMをしていた頃に、メンバーのGitHubやKibelaの活動など一箇所でまとめて見れるWebアプリを作成した話になります。 作成したWebアプリについて 自分のページのスクリーンショット 構成 ローカル環境で利用することを想定したもの 環境変数に各種PAT等設定をし、Asana API, GitHub GraphQL API, Slack APIを利用しています フレームワークにはNext.js, Bootstrap5を選定しました 主な機能 メンバーごとに、期間を指定して、以下のようなことができます。 Asanaのアサイン中のTask、GitHubの活動、Kibelaの活動、Uniposのリンク、Google カレンダーのiframe埋め込み、geekbotの投稿を表示 Asana、GitHub、Kibelaの活動をMarkdown形式でコピー どのように活用しているか 週次定例で共有する雛形に Asana、GitHub、Kibelaの活動をMarkdown形式でコピーできるので、それを週次定例で共有する雛形にして、定例資料作成にかかる時間を短くできるようになりました。 1on1での報告をスムーズに 週次で行っていたEMとメンバー間での1on1では、Asana、GitHub、Kibelaの活動に活動を見た上で、業務面での報告をコンパクトに出来るよう心がけていました。 内省に 期間を1ヶ月や3ヶ月、半年、1年などにすることで、自身がどういうことをやっていたのか見ることができます。 プロジェクトの振り返り会や、評価面談に向けての準備にも活用しています。 作ってみての感想 基本的に各サービスのAPI活用は読み出しのみなので、そこまで時間をかけずに実装できました 散らばっている公開情報を意味ある形にまとめるだけでも、活用できるシーンが生まれて、便利だなと思っています Asanaの項目やコピーする機能は実装当初なかったのですが、ありがたいことにチームのメンバーがほしいとPR作成してくれました!感謝! 最後に 明日は、 @Panda_Program さんによる「Vue 2 + TypeScript 環境に Testing Library を導入する」です!お楽しみに!
アバター
はじめに この記事は BASE Advent Calendar 2022 と Looker Advent Calender 2022 2日目の記事です。 こんにちは。BASE 株式会社 New Division BASE BANK Section にて、Engineering Program Manager (以下EPM) 1 をしている永野( @glassmonekey ) です。 私達のBASE BANK Section チーム (以下 BANK チーム) はBASEの中でも、新規事業の金融系のプロダクトにフォーカスしたチームになります。特に新規事業なので、日々の不確実性にどう向き合って行くかをテーマに日々仕事をしています。 不確実性に向き合うためには、日々リリースしていくしかありません。そしてそれを観測するための分析基盤も日々のリリースに耐えうる、つまりはアジリティを保つ必要があります。 今回はどのようにアジリティを保ってデータ基盤を日々運用しているかをテーマに導入と活用の紹介をいたします。 データ基盤をこれから導入しようとしている方や、データ基盤を社内に普及させようと尽力されている方々の参考になれば幸いです。 ちなみに、今回の内容は7月に開催された Lookerユーザー会 で発表した内容がベースになっているので、そちらももしよかったら御覧ください。 EPMの役割 最初に、私の役割であるEPMとはなにか?を簡単に説明させてください。 私は現在、 YELL BANK というプロダクトのEPMをしています。 EPMの役割としてはざっくりですが、以下のようなことを日々やっています。 * 開発のリードや開発プロセスの改善 * 設定したリリーススケジュールへの責任 * プロダクトのクオリティの担保 * プロジェクト、プロダクトの技術的な阻害要因の排除 * ステークホルダーとの適切なコミュニケーション * 意思決定やエスカレーションの実施 特に私としては、早期リリースとリリース頻度には重きを置いていています。 最初の企画では3ヶ月かかる想定のものでも、スコープの調整をすると1ヶ月になりそうか? もしかしたら、工夫すればもっと早くリリースできないか?を考えていく感じです。 なぜなら、我々のつくっているプロダクトは不確実性だらけなので、わからないことばかりです。 それらの不確実性と向き合っていくには少しでも早くリリースして学習を重ねていくしかありません。 では、リリースした結果を学んでいくにはどうしたらいいでしょうか? 特にチームメンバーは全員がエンジニアというわけではありません。それぞれの立場で見る景色が違うため、KPIのような一定の定量的な指標を北極星にして目線をそろえていく必要があります。 そこで、目線をそろえるために定量的な指標を管理していくためにデータ基盤が登場します。 データ基盤とEPM 一般的にデータ基盤というと、Data Ware House(DWH)やらデータレイクといった言葉がでてくるのではないでしょうか。 我々としても、データ基盤のDWHには BigQuery を使い、BIツールには Looker を使っています。 もともと Redash を使っていましたが、クエリ管理の課題などから最近はLookerへの移行が進んできました。 Lookerへの推進はデータプラットフォームチームが全社的にリードし、我々BANK チーム としてはそれに則った形です。 しかし、この状況だと日々のリリース変更の度に、データプラットフォームチームへ都度依頼してデータの変更を共有しないといけないので若干非効率です。 そこである程度アジリティを保ちつつ、人的リソースも有限なので下のような体制での運用を始めました。 全社的なデータ基盤はデータプラットフォームチームが責任を持つ。 BANKチームの細かな分析指標は、日々のリリースに責任を持つEPMが責任を持つ ETLからELT ではデータプラットフォームチームとEPMがどのように分業を進めていったを説明します。 一般的なデータ基盤において重要なプロセスに以下の3つのプロセスが挙げられます。 * Extract(抽出)... データをソースから抽出する。 * Transform(変換) ... データを必要に応じて加工する。 * Load(格納)... データ基盤に格納する。 一昔前では、Extract、Transform、Loadの順に行われたのでETLという略称で呼ばれていました。聞き馴染みのある方も多いのではないでしょうか。 この中で、一番変更可能性が高い箇所はTransformになります。分析したい点やプロダクトのリリースによるデータ変更による影響を受けやすいためです。 従来のETLプロセスでは、LoadがTtranformに依存している形だったので、Transformの変更の影響をLoadが受けやすいという構造でした。DWHを作るような規模のデータだとペタバイト級のデータを扱うことも珍しくなく、頻繁にLoad処理を変更することは現実的ではないでしょう。そのためTransformを変更することが難しいという状況を生んでしまい、アジリティを下げる要因だったのではないでしょうか。 一方で、最近ではETLの代わりにELTという流れが主流になってきました。つまりは以下ように変更可能性の高い、Transformの処理を最後に持ってくるわけです。 実際我々のチームでも、アプリケーションを変更した我々自身でデータ分析基盤の最終的な内容を変更するという体制を取りました。そのおかげで、リリースした内容の速報を翌日にはダッシュボードで作ったりといった柔軟な運用体制を敷くことができました。 つまり、Transformを我々EPMが責任を持ち、ExtractとTransformは全社的な部分はデータプラットフォームチームが、我々のチームが管理しているデータソースはEPMが整備するといった分業体制となりました。 特に、Lookerだと、 派生テーブル によって気軽にELTが実現されている点で重宝しています。 クエリを書いてTransformを書く役割と、日々のKPIを管理してダッシュボードを作る役割を分けられた点は、他のBIツールには難しかった点かなと感じています。 とはいえRedashなどの他のBIツールでもクエリが書けるなら、同じような運用はできると思われます。 プロダクトライフサイクルに組み込む 現在、我々のチームが管理しているDatabaseやlog情報をそれぞれ日毎または日時でBigQueryに同期する形を取っています。 基本的にはembulkで一部Python のスクリプトをECSのタスクスケジューリングで実現しています。 基本的に同期するデータは、ほぼ生データをそのままBigQueryに入れるようにしています。 分析しやすいデータの整形といった処理はBIツールといった分析者の近いところで実現する方式をとっています。前述したELTです。 この方式を採用した理由としては、データのロード部分はデータ量の関係から頻繁な変更は難しいと考えた点が大きいです。分析観点という変化のしやすい内容をロード部分に組み込むと分析基盤としてのアジリティが下がってしまうことを懸念したためです。 最近は、PMMといった日々の施策に責任を持つメンバーも増えてきたので、ダッシュボード作りはPMMに任せつつダッシュボードに必要な情報はEPMが責任を持つといった分業体制で基盤周りの活動をしています。 もともと、BANKチームのエンジニア組織はフルサイクルエンジニアを掲げて全てのプロダクトライフサイクルに関わることを大事にしています。その中に分析基盤へのフィードバックも組み込んだ形を取るようになったといえます。 フルサイクルエンジニアに関しては同僚の松雪( @applepine1125 )が、 このあとblogを書いてくれるはずなのでこうご期待!! 書いてくれました。 devblog.thebase.in あと、Lookerを活用して良かった点に日付の情報を以下のようにTimeframeとして定義しておくとシームレスに週次、月次、Quoaterといった形に拡張できる点は便利だなと感じています。 dimension_group: event_date { type: time time_frame: [ time, date, week, month, quarter, year ] } 具体的には、日々の振り返り用のデータをそのまま拡張して、役員会議の資料にも活用できる点です。このことにより、メンバーから経営層まで視点が合わせられるので、視座に関わらず同じ北極星を追いながら仕事ができていると感じます。 最後に 今後の展望として、データ基盤を作った体制ができたので、プロダクトへのフィードバックの仕組みづくりとそれを推進していくメンバーの採用を進めていきたいと考えています。 現在はembulkで雑にデータを転送していますが、今後プロダクトにフィードバックすることを考えるとGlueやSage Makerの活用も考えていけたらなと思っています。 そこで一緒にプロダクト作っていく仲間を絶賛募集中です。 もしちょっとでも興味あるとか、知見あるよという方ぜひ私までDMか、以下のフォームから応募お待ちしています!! 冷やかしでもいいよ!! open.talentio.com 明日はダーツが得意な @ao_kiken さんによる、「チームメンバーの活動を知る工夫」です。楽しみですね!! 参考文献 プロダクトのデリバリー、クオリティに責任を持つEngineering Program Managerという役割 ↩
アバター
この記事は 2022 BASEアドベントカレンダー 1日目の記事です。 こんにちは!開発担当役員のえふしんです。絶賛、来季の組織構造の設計中なので、何を考えながら組織図を検討しているか?ということを書いてみたいと思います! 組織検討の基本としては、サービスの拡大、組織の拡大に対して仕事のやりやすさ、スピード感を如何に実現しながら事業を伸ばしていくか?というテーマで組織を考えていきます。 組織構造は各社各様のものがあると思いますが、多くの会社さんではビジネスのトップラインを伸ばすチームを主体に、今ある課題をこなしていくチーム構成であることが多いことでしょう。 また、ビジネスの守りを支えるチームもあります。守りの裏返しは攻めと言いますか、何かを守り抜くことで、売上や利益の下支えを行う課題解決については経営課題として上がるため、それ専用のチームが作られたりします。 我々で言うと、決済機能の保守がそれに該当します。決済機能はただサーバを動かしているだけで済むものではありません。BASEの上で販売される商品をチェックするなどの加盟店管理責任を果たすことや、悪意のある決済からショップを守る不正決済管理などが該当します。 一方で、新機能開発を専念しているWebサービスの会社で、いつも悩みのタネになるのが、過去に作り込んでしまった不具合修正を誰がどうやって時間を割いて直していくかということではないでしょうか。つまりすでに動いているサービスのクオリティや安定性の維持の作業で、誰がどのようなプライオリティと責任で担っていくのかという課題です。 (事象がクリティカルではない)不具合修正に傾倒しすぎると新規機能開発が遅れている可能性が高くなり、新規機能開発を優先していると、いわゆる技術的負債が解消されないであるとか、プロダクトクオリティが低いままになるというリスクがあります。 こういう部分はなんらかしらの問題が起きない限りビジネスイシューには上がりにくく、マネジメントは開発チームに委ねられているのが実情だと思います。そして現場では大なり小なり問題が起きているため、このことに対するジャッジメントはCTOが担っているのではないでしょうか。 以前、個人のブログでも以下のような記事を書いたことがあります。 note.com ここで書いてあることをざっくり書きますと、足元のソフトウエア資産の維持(技術的負債の解消も含む)、セキュリティの維持などを行う保守作業等の必要性はエンジニアしかジャッジメントできない。一方でエンジニアの作業快適性の維持など、直接的に売上には貢献しないけど、放置も良くないという間接的なバリューへの貢献でもあったりするので、余計にその判断責任を担うためにもCTO職が必要であろうということが書いてあるわけです。 そして、不具合修正等の実作業ですが、昨今はCRE(Customer Reliability Engineering)などと言うチームを作って、顧客満足度を実現するための組織を作るところもあります。このチームはすごく意義、意味のある取り組みではありますが、言うて実態は社内の誰かが作りこんだ不具合修正を担う、若干のとばっちり感が否めないチームであるという部分で、感情的な難しさを醸し出したりもします。 そもそも、まず適切な不具合修正は難しい作業です。スキルが問われます。サービスのドメイン知識も必要なので、とりあえず人を雇ってきて、直してください〜で済む人はベテランエンジニアである確率が高いでしょう。 歴史的経緯を調べるためにステークホルダーを理解する人間関係も必要です。 そういうことができる人だったら新規開発もできる可能性が高いので、ビジネスプライオリティを優先する組織では、遅かれ早かれそういう人は新規開発に回るはずです。 そうではなく、もしベテランのエンジニアの人が教育者的な視点を持ち、守りの要を担ってもらえたら最高です。 でもWebサービス企業は歴史が浅いので、製造業などにいるリタイア間近のベテランの工員の人などはあまり存在しません。みんなもっと若いので、新規開発などを通じて自分のスキルを高めていきたいと思うのが人情というものでしょう。 なので、CREは、その会社の歴史的経緯に支えられた属人の活躍に委ねられている組織なんじゃないかな?と思ったりします。組織構造としては極めて維持が難しいチームではないかと考えたりするのですが、どうでしょうかね。理想論ではなくリアリティのあるCREチームの作り方を是非お聞きしてみたいです。 受託感という言葉について 話は変わりますが、Webサービス企業では「受託っぽい」と言う言葉が使われて忌み嫌われることがあります。多くは、よくない組織構造を起因として「上から案件が降ってくる」「スケジュールや仕様などでエンジニアサイドの決定権や拒否権がない」などの「やらされ仕事」という言葉に対して使われがちな表現です。いわゆる多重請負の下位レイヤーの人の話がたくさん出回っていた時期があったので、こういう言葉が使われがちです。 僕は以前、受託開発も担うWeb制作会社にいたので、一次請けであれば、もっと議論もできるし、そもそも見積もりでお断りもできるし、お客さんやSIerの「上に言っちゃったから今更断れない」にぶつかることはあっても、そのことを共有し、最大限助けてあげようという割と前向きなメンタリティで仕事ができていたので、受託開発も楽しかったです。 とはいえ後に、Webサービス企業に転職をするわけですが、次に何をしたかったかというと、自分がかけた時間を、すべてそのサービスの成長に結びつけたいということでした。 どうしても当時僕が関わっていた受託開発だと、契約条件としても納品するまでがビジネスで、そのシステムはお客様の資産ですし、それがどう運用されているかまで、当時は関われなかったので、どう成功した、どう失敗したのかまでは関われませんでした。また新機能提案なども、お客様にお金をいただかないと責任を持てないというオーバーヘッドが、当時の僕のスキルでは無駄だと思っていたし、何よりお客様が判断できることしかできません。 ギーク的なノリで「とりあえずやってみる」 自分たちの責任でチャレンジしてみるなどもできないわけです。 もちろん、これは当時の僕のスキルと環境がそうだっただけで、最近はDXの名の下で、アジャイルプロセスを使いながら運用を伴奏するサービス開発の受託ビジネスなども立ち上がってきて、レベニューシェアの仕組みが連動できれば面白いですし、DAO的な形で出資し合うビジネスもありでしょう。昔より面白くなってきたので、個人的にはそういう仕事にも興味があります。 ...それはともかくとして、Webサービスのエンジニアは、そのサービスの生殺与奪を握っているので、新規開発だけでなく、そのサービスの安定性維持、不具合修正、セキュリティ、サービス運用なども全部自分たちの責任の元でこなすべきだと思っています。それこそが主体的にWebサービスに携わるエンジニアの矜持だとも思っています。 でも、どうしても仕事として見ると、開発計画にあるプロジェクトを優先的にやらなきゃ!って思う人もいますし、たしかにそれは重要なのですが、かと言って、それだけに心を囚われてしまうのも、僕はむしろ「受託っぽいと言われやすい価値観かな」って思うところもあります。 もっとワガママに自分たちが守っているサービス全体を守るんだという意識の元で、新規プロジェクトもこなしつつ、サービスを維持するエンジニアリングスキルを高めていく方が絶対楽しいと僕は思っていますし、それができた人が、当社でもCTOであったりプリンシパルテックリードなどの重責についていることは、一応書いておきます(要するにその主体性、スピード感と技術力を兼ね備えた人材がわかりやすくエンジニアとして出世するノウハウということです) 攻めと守りを両立させる 我々のSlackには、#cs_qというお客様からの問い合わせの中で技術的な調査や回答を行うチャンネルがあります。 以前は組織も小さかったので、基本的に問い合わせに気がついた人が回答するという牧歌的なコミュニケーションチャンネルでしたが、いつしか問い合わせも増え、人も増えた流れで、CTOやプリンシパルテックリードが、まだその肩書がつく以前から問い合わせ対応に他の人達よりも爆速で対応し続けてしまい、かつ彼らが持っている開発プロジェクトもスケジュールに間に合うようにするために残業時間が爆増したため、その負荷分散のために、当番制で担当するように変わりました。 今は、マネージャ判断で#cs_q対応の当番リストから外さない限りは、原則としてプロジェクトに参加しているエンジニアも含めて、全員体制で対応をしています。 以前のアドベントカレンダーでもこういう記事を書いています。 devblog.thebase.in 我々は現在、こういう体制で対応をしていますが、一方で、どうしても現場目線では#cs_q対応はプロジェクトが遅れる要因として語られたりするため、ビジネス最適化という側面では、こういった守りの対応のための別チームを組成してみては?という話が出てくるわけです。 なので、組織構造をトップラインを伸ばす方向だけに最適化するとそういう話になってしまうわけです。ビジネスジャッジメントを優先しすぎてサービスの安定性、継続性、ソースコードの劣化に伴う開発生産性が損なわれていくようなことにはならないように、かつ、Webサービスのエンジニアとして、自分たちで作ったものは自分たちで直す、という当たり前のことをやり続けられる組織構造を維持しないといけないとは思っています。これはテックベンチャーとしてのこだわりポイントではないかと思っています。 もちろん管理者目線では両立できる組織構造でそれぞれが最速で動くような組織の方がきれいなので、将来はそうなったらいいなとは思いますが、エンジニア目線では「それでWebサービスに携わってると言えるのかなぁ、仕事楽しいのかなぁ」という気持ちを持ちながら、2つの価値観を考えているという感じですかね。 責務のドメイン分割が作業効率にだけ最適化されただけの状態が正解だとは思わないという話でして、答えの出ない話ではあるのですが、うっかりすると自分自身も効率性だけを考えて組織設計をしがちだったりするので、気をつけなきゃなと考えながら組織図案を書いてる今日このごろです。 アドベントカレンダー 2日目はBASE BANKチームの永野( @glassmonekey )さんによるデータ分析基盤の管理運用についての話です。お楽しみに! 2022年のアドベントカレンダーのスケジュール
アバター
こんにちは!BASE product blog編集部です。いつも弊ブログの記事をご覧いただきありがとうございます! あっというまに2022年も年の瀬。年の瀬といえばそう!アドベントカレンダー! 今年も恒例のBASEメンバーによるアドベントカレンダーを開催します! 過去の様子 2021年のアドベントカレンダー 2020年のアドベントカレンダー 2019年のアドベントカレンダー 2018年のアドベントカレンダー 今年も1日1記事に限定せずたくさんのバラエティ豊かな記事を公開する予定です。 公開され次第以下のカレンダーも随時更新していきますので、ぜひお楽しみに! 記事のカレンダー 日付 曜日 執筆者 テーマ・タイトル 12/1 木 @fshin2000 Webサービスの開発チームが担うべき攻めと守りという2つの役割 12/2 金 @glassmonekey アジリティを保ってデータ基盤を作る取り組み 12/3 土 @ao_kiken チームメンバーの活動を知る工夫 12/4 日 @Panda_Program Vue 2 + TypeScript 環境に Testing Library を導入する 12/5 月 @toshi_oliver AWS LambdaをPHPで使うためのベストな方法 12/6 火 @kon_engineer @yusaku スクラム開発で数スプリント先の未来の落とし穴を回避する ⁠ 年間500回1on1した結果わかった大事なこと 12/7 水 @satoshi_takemoto @tosh 入社して1ヶ月経ったのでBASEのオンボーディング体験を紹介します カートチームを振り返る 12/8 木 @takashi_matsuyuki @yuki_tsukita Real World Full Cycle Developers ユーザーリサーチとABテストで効果的なマーケティングが実現した話 12/9 金 @gatchan0807 @yuripi ⁠ プロダクトの小さな負を解消する有志活動の振り返り オンラインでのチームビルディングで意識をしていること 12/10 土 @gan.seki ファットな注文検索モデルをリファクタした話 12/11 日 @izuhara reportシステムの業務改善で経理業務が更に短縮した話 12/12 月 @naoki.munechika BASEに転職したUI/UXデザイナーが入社後1ヶ月で感じていること 12/13 火 @shibu_t @wakana UXライティングのレビュー工数をtextlintで90%削減した件 若手エンジニアの自分にとってBASEが最高の成長環境だった話 12/14 水 @matzz @yuni リモートワークで膨らみ続けた俺の自宅環境のすべてを詰め込む 知って欲しい!資金調達サービス「YELL BANK」の推しポイント 12/15 木 @funasaka チームの能力を数値化して今後の成長方針を立ててみた 12/16 金 @yanagawa リモートワークを乗りこなすために自己開示の仕組みを作っている話 12/17 土 @zan BASEに入社して感じたSpeak Openlyの良いところ3選 12/18 日 @chihirokym @cureseven @ngsw アドベントカレンダーでBASEデザイナーと2022年を振り返る Large-Scale Scrum(LeSS)体験記 SRE関連Issue、7年分を振り返る - BASEプロダクトチームブログ 12/19 月 @Shunsuke @ShoTakeuchi 型安全なPythonで堅牢なアプリケーション開発 BERTを利用した商品カテゴリの推論基盤を作りました 12/20 火 @yuzuy @ayako-hotehama BASEカードにおけるキャッシュバックの設計 BASEに入社して3ヶ月間で、ありがたいなと感じている3つのこと 12/21 水 @ogata2104 @tadamatu 上場3年目、改めてIT統制について考える BASE全体のインフラ知識底上げのため AWS JumpStart に参加してもらいました 12/22 木 @02 @tsubo BASE技術イベント登壇振り返り2022 新規事業のプロダクトマネージャーが入社6ヶ月で取り組んだこと 12/23 金 @FUJIIMichiro @Linda テキスト体験のデザインの先に見えたUXライティングメソッドの未来 経営戦略室と僕たちが信じる未来 12/24 土 @rerenote Pay IDの開発組織をマネージャーがパッション多めで紹介するよ 12/25 日 CTO gg BASEのCTOがエンジニアリングマネージャーに求めていること 9年間の社会人経験を経て、大切だと思った4つのこと
アバター
はじめに はじめまして、CSE (Corporate Solution Engineering 1 )の上野です。 今回は BASE Partners という事業で使用していた Google フォームを S3 + API gateway + Lambda (+ Aurora) を使用した Serverless 構成のフォームに移行するというプロジェクトについてお話します。 変更前の構成図と構築した構成図としては以下のようになります。 変更前 変更後 BASE Partners について BASE では新規のショップオーナー様を紹介・支援いただくオフィシャルパートナーを募集するパートナープログラムを運営しています。 それらの申請には初期的には Move fast に行うため、Google フォームと Google スプレッドシートが使用されていましたが、ありがたいことにパートナー様やご紹介いただいたショップ様は順調に増加し、それに比例して、誤申請とそれによるやり取りが増加するなど、業務負荷が高まっていました。 既存の仕組みが Google フォーム → Google スプレッドシート → DB へバッチ取り込み という流れのため、申請者がリアルタイムに申請した内容が正しいかどうかがわからないというのが誤申請の主な原因だったため、フォームから直接 DB を参照できるよう、フォームを移行することに決定しました。 技術選定 移行するにあたって、まずはどの技術を使用するかというところから検討をはじめました。今回はサーバーを管理・運用する人的リソースがない、などの理由から少しでも運用が軽減できるよう S3 の静的サイトホスティングと API gateway、Lambda を用いた Serverless の構成とするように決定しました。言語については、BASE では PHP がよく用いられていますが、Lambda では PHP は公式ではサポートされていないため、CSE で Lambda を使用する際によく用いる Python を選択しました。 また、Serverless アプリケーションの管理は、CSE で使用実績のあった Servewrless Application Model (SAM)を使用することにしました。 SAMによる実装 ネットワークの設定 Lambda から既存の DB にアクセスする必要があるため、VPC、Subnet、Security Group の設定が必要になります。 今回の実装では Terraform でネットワーク等を管理している別リポジトリがあるため、実装としてはありませんが、下記のような構成となるようにそれぞれ設定しました。 VPC、Subnet、Security Group の設定後、SAM の template.yaml の VpcConfig 設定で Lambda に Subnet と Security Group の設定を付与します。 Resources : SomeFunction : Type : AWS::Serverless::Function Properties : CodeUri : functions/ Handler : some_function.lambda_handler Runtime : python3.9 Architectures : - x86_64 VpcConfig : SecurityGroupIds : - <SecurityGroupId> # 構築したセキュリティグループIDを設定 SubnetIds : - <SubnetId> # 構築したサブネットIDを設定 CORS の設定 S3の静的サイトホスティングのドメインと、API gateway のドメインが異なるため、Cross-Origin Resource Sharing (CORS) の設定が必要になります。 API gateway と Lambda の両方に設定が必要になるため、以下のような形で実装をします。 template.yaml の実装 下記のサンプルでは1つの関数しかありませんが、実際には複数の関数が存在するため、Globals 内で api に Cors プロパティを設定します。 今回実装した API は POST メソッドがメインになりますが、CORS の設定をする場合は OPTIONS メソッドで preflight リクエストを送信するため、OPTIONS メソッドの設定も必要になります。 Globals : Api : Cors : AllowOrigin : "'<origin>'" AllowCredentials : false AllowMethods : "'POST,OPTIONS'" AllowHeaders : "'<headers>'" Resources : SomeFunction : Type : AWS::Serverless::Function Properties : CodeUri : functions/ Handler : some_function.lambda_handler Runtime : python3.9 Architectures : - x86_64 VpcConfig : SecurityGroupIds : - <SecurityGroupId> SubnetIds : - <SecurityGroupId> Events : SomeApi : Type : Api Properties : Path : /path Method : post Lambda の実装 API gateway の Lambda プロキシ統合の場合、バックエンドの Lambda 関数でも Access-Control-Allow-Origin および Access-Control-Allow-Headers を返す必要があるため、以下のように返り値の headers に記載します。 2 def lambda_handler (event, context): ... return { "statusCode" : 200 , "headers" : { 'Access-Control-Allow-Headers' : 'Content-Type' , 'Access-Control-Allow-Origin' : '<ORIGIN>' , 'Access-Control-Allow-Methods' : 'OPTIONS,POST' }, "body" : json.dumps({ ... }), } デプロイの自動化 続いてデプロイを自動化する仕組みについてです。 今回は大きなアプリケーションでは無いので GitHub Actions で SAM の deploy コマンドを実行する簡易的なものを使用しました。 また、デプロイに使用する認証は、初期的には GitHub Secrets を使用しようと思っていましたが、OpenID Connect であればクレデンシャル情報の漏洩などのリスクがなく、適切に使用すればよりセキュアであるということを知り、OpenID Connect を使用することにしました。 OpenID Connect の IAM Role 設定 ID プロバイダの追加 まずは以下の設定で GitHub を ID プロバイダとして AWS に追加します。 Provider URL : https://token.actions.githubusercontent.com Audience : sts.amazonaws.com IAM Role の作成 次に認証後に使用する IAM Role を作成します。 まず Policy で先程設定した GitHub の ID プロバイダを Principal に設定し、Action で sts:AssumeRoleWithWebIdentity を指定します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect " : " Allow ", " Principal " : { " Federated ": [ " arn:aws:iam::<account_id>:oidc-provider/token.actions.githubusercontent.com " ] } , " Action " : " sts:AssumeRoleWithWebIdentity ", " Condition " : { " StringLike ": { " token.actions.githubusercontent.com:sub ": [ " repo:<repository>:* " ] } } } ] } 今回は SAM のデプロイをするため、以下のポリシーをアタッチします。 - IAMFullAccess - AmazonS3FullAccess - AmazonAPIGatewayAdministrator - AWSCloudFormationFullAccess - AWSLambda_FullAcces GitHub Actions の実装 先に設定した IAM Role を使用して、自動デプロイを実行する GitHub Actions を実装します。 name : 'deploy' on : push : paths : - 'template.yaml' - '.github/workflows/deploy.yml' - 'functions/**' branches : - 'branch' env : AWS_ROLE_ARN : 'arn:aws:iam::<aws_account_id>:role/<OIDC_ROLE_NAME>' permissions : id-token : write contents : read jobs : deploy : runs-on : ubuntu-latest steps : - uses : actions/checkout@v2 - uses : actions/setup-python@v2 - uses : aws-actions/setup-sam@v1 - uses : aws-actions/configure-aws-credentials@v1 with : role-to-assume : ${{ env.AWS_ROLE_ARN }} - run : sam build --use-container - run : sam deploy --config-env <env> --no-confirm-changeset まとめ 本記事では、業務改善を目的とした API gateway と Lambda による Serverless なフォームの構築についてお話しました。 業務改善の観点としては、移行して間もないためどのくらいの改善ができたかという定量的な指標はないものの、最初に記載していた誤申請での問い合わせは削減することができました。 また、API gateway、Lambda での CORS の設定の記事や、OIDC についての記事など、各論の記事は多々あるものの、全体の実装をまとめたものが少ないと思ったので、参考になれば幸いです。 感想 AWS に触れるのは BASE に入社してからだったのですが、経験が少ない中でも挑戦させてもらえてとてもいい経験ができました。 以前公開された若菜の記事 にも 手を挙げることでやりたいことに積極的にチャレンジさせてもらえる という社風がある とありますが、たしかにそういった社風があるというのを体感することができました。 また、コーポレートエンジニアというとベンダーコントロールがメインというイメージや、レガシーなシステムの保守がメインだというイメージがある方もいらっしゃるかと思いますが、BASE では実装や技術選定など様々なフェーズやAWS、Serverless などモダンな技術も経験ができる、ということを知っていただければ幸いです。 最後に BASE では積極的にチャレンジがしたいエンジニアを募集しています。カジュアル面談も実施していますので、興味を持っていただけた方はぜひお気軽にご応募ください。 https://open.talentio.com/r/1/c/binc/homes/4380 CSE については こちらの記事 を参照ください ↩ AWS 公式ドキュメント ↩
アバター
基盤チームに所属している @okinaka です。 個人的には CakePHP とは長い付き合いで、もう14年以上になります。 BASE の事業においても10年間ずっと支えてくれている大変ありがたい Web フレームワークです。 以前から BASE の多くのコードはまだ古い CakePHP 2 (v2.10.24) 上で動作していることが課題になっています。 CakePHP 自身は順調に開発が継続されていますが、2系から3系へのバージョンアップはなかなか困難で二の足を踏んでいました。 そうこうしているうちに古いバージョンの2系は既に公式でのサポートは切れているうえに PHP 8.0 未サポートの状態です。 さすがにそのまま継続して利用するのは無理があるので、数年前から、より柔軟にシステムを構築するためのアーキテクチャ再設計をすすめています。 その成果は上がってきているのですが、単純なフレームワークのバージョンアップにとどまらず、根本的なアーキテクチャを再設計しているところなので、全てが刷新されるまでには、まだまだ既存の仕組みを利用することが続きそうです。(アーキテクチャの再設計に関しては、プラットフォームチームが別の機会で紹介することになると思います。) さぁ、大変。PHP 7.4 の EOL (2022-11-28) も迎えて、もう後のない状態、このまま先に進むには PHP 8.0 以上に上げる必要に迫られてきました。 PHP 8.0 でも意外と動くよ ありがたいことに、CakePHP 2 を公式のリポジトリからフォークして PHP 8.0 対応されている方がいることを CakePHP の Slack チャンネルで知りました。 https://github.com/kamilwylegala/cakephp2-php8 お試しでやってみたら、すんなり動くではありませんか!いける!と思ったのですが、一つだけ問題がありました。 それは自動テストは、未対応なのでした。 ということで、PHP 8.0 で動作するために必要な PHPUnit 9 対応を独自にやってみたらできたという報告です。 PHPUnit 9 に対応してみる 前置きが長くなってしまいましたが、本題に入ります。 PHPUnit 9 は PHP 7.3 以上に対応していますので、いきなり PHP 8.0 まで上げる必要はありません。 今回は公式の CakePHP 2.10.24 (& PHP 7.4) のままで PHPUnit 9 に対応してみます。 大きな要点は2点あります。 CakeTestCase や ControllerTest を継承しない (あるいは差し替える) cake test を使わずに直接 PHPUnit を実行する 他にも細かなポイントはたくさんあるのですが、さらに長くなりそうなので以上で留めておきます。 CakeTestCase や ControllerTest を継承しない (あるいは差し替える) とても残念なことですが、PHPUnit のバージョンアップ(phpunit>=8.0)に伴い後方互換性のない変更があったため、CakeTestCase や ControllerTest を継承してテストを書くことはできなくなりました。 TestCase の setUp() や tearDown() などのメソッドの戻り値に型宣言が追加されたためです。 こればかりは、フレークワーク側を修正するしかないので、応急措置として CakeTestCase をコピーしたクラスを用意しました。 名前空間がなく、独自のクラスローダーを利用しているメリットを活かし、 app/TestSuite/CakeTestCase.php を配置することで、 App::uses('CakeTestCase', 'TestSuite'); と書かいていると、オリジナルではなくコピーの方が読み込まれます。 対応例は こちら 。 直接 PHPUnit を実行する CakePHP 2 では、 cake test というコマンドが用意されていて、内部では PHPUnit を動かしています。 古いPHPUnit 向けのロジックが書かれているため、これを使うことを避けて PHPUnit を直接実行します。 弊社では、バージョンアップ以前から PhpStorm などの IDE の支援を受けるために PHPUnit を直接利用したいという理由で対応してました。 そのための対応方法は GitHub の issue に弊社の tenkoma さんが紹介していますので参考になります。 https://github.com/cakephp/cakephp/issues/12700#issuecomment-444150548 テストコマンド起動時のおまじない少々とフィクスチャ周りのロジックの追加が必要でした。 なお、CakePHP 2 には web 上からテストを実行する機能があるのですが、その機能は切り捨てることになります。(IDE や CI でテストする分には不要なのでなくても困らないと思います。) 対応例は こちら 。 サンプル 今回のブログのために、CakePHP 2 で PHPUnit 9 を動かすサンプルを作成してみました。 CakeTestCase のためのテストケース (CakeTestCaseTest) を用意しました。GitHub Actions の workflow を使って自動テストにも対応しています。 https://github.com/okinaka/cakephp2-phpunit9-sample おわりに 本来、フレークワークは常に最新バージョンを使うことが好ましいことなのですが、自動テストがあれば、古いバージョンを使っていても安心できると思います。 (あくまで移行完了までの中継ぎとしてですが) この記事で、CakePHP のバージョンアップができずに古いバージョンを延命しようかどうか迷っている方に参考になれば幸いです。
アバター
はじめに 2022/11/19(土)に開催される フロントエンドカンファレンス沖縄2022 にBASEに所属する4名のエンジニアが登壇します。 BASE ではこれまでいくつものフロントエンド に関連するテックブログ記事やイベントへの参加を行ってまいりました。 そして今回は、フロントエンドがテーマとしてありつつも、職種問わずWebに携わる方が楽しめるイベントということで協賛いたしました。 フロントエンドカンファレンス沖縄2022について フロントエンドカンファレンス沖縄2022とは、デザイナーもエンジニアも、職種問わずWebに携わる方が楽しめるイベントです。 BASEは、ゴールドスポンサーとして本カンファレンスに協賛しています。 <スポンサー紹介> BASE 様 BASEはどなたでも簡単にネットショップを作れるサービスです。「Payment to the People, Power to the People.」をミッションに、180万ショップを超えるショップ様に安定したサービスを提供しております。 #front_okinawa https://t.co/SQcoK6eTQK — フロントエンドカンファレンス沖縄 (@fec_okinawa) 2022年11月10日 セッション内容 mk0812(@mk0812__) mk0812 です。今回5分のLTに登壇します。タイトルは「 Figma TokensとStyle Dictionaryから始めるデザイナーとエンジニアが連携しやすい取り組みの話 」です。デザイナーとエンジニアの連携周りで5分では全ては語れないですがFigma Tokensなどを用いて効率化などできるような話をしていきたいです!是非ご覧ください! fortee.jp 千葉リリィ( @chiba_rry ) 千葉リリィ です!すこし前に「 Reject Conference - Vue Fes Japan Online 2022 」というイベントでも登壇しましたが、そこで少し触れた OpenAPI の API Client 自動生成についてお話しします。 とはいえ今回は5分の LT なので、全部は語りきれずエラーハンドリングにフォーカスした発表となります。 ちなみに資料進捗は0%です!先日 @mk0812 が早速レビュー依頼出してて速くてえらいなぁと思いました。これからがんばります。 fortee.jp プログラミングをするパンダ(@Panda_Program ) プログラミングをするパンダ(@Panda_Program ) です。今回は「フロントエンドエンジニアと個人開発の楽しみ」というタイトルで、個人開発について発表します。 今資料を作成しているのですが、30分という枠を貰っているものの素案の段階では多分30分に収まらないです(笑) 内容盛りだくさんなのでぜひご覧ください! fortee.jp aokiken (@ao_kiken) aokiken です。二年前のアドベントカレンダーで書いた「 Web開発を補助する目的でPuppeteerを使う 」の2022年版を5分のLTをします タイトルは「Web開発を補助する目的でPlaywrightを使っている話」になります ブラウザ操作自動化、E2Eテストに興味がある方ぜひご覧ください! devblog.thebase.in 最後に フロントエンドカンファレンス沖縄2022は、公式ページにて参加申込可能です。 今回我々はオンラインでの参加になりますが、SNSなどでわいわい盛り上がりましょう。 frontend-conf.okinawa.jp
アバター
はじめに フロントエンドエンジニアの @mk0812 です。自分は普段BackOfficeというチームで新規機能開発を担当しています。 この記事ではBASEのフロントエンド周りの事例として「 Monorepo 」を紹介します。 エンジニアの皆さんなら1度は聞いたことあるかもしれませんが、BASEではここ最近Monorepoにしていきました。 具体的にどこをMonorepoにしてるかというとBASEの管理画面にある基本機能とBASE Appsです(下図)。 前者はBASE管理画面で使用する機能を指しており、後者はショップにより充実した設定を追加したり、新しい機能が必要となる時に追加できる機能で、数多く提供されていて、その機能単位もしくはApp単位でMonorepo化されています。 基本機能とBASE Apps Monorepo とは 「Monorepo」とは単一のリポジトリである特定のプロジェクトコードを全て管理する方法を指します。下図はMonorepo構成であり、リポジトリに対して BASE App1 , BASE App2 ...とApp単位、機能単位でディレクトリがあり、その単位でビルド等ができるようになっています。 Monorepo とは Monorepoのメリットは下記になります。 依存管理がわかりやすくなる Monorepo単位で依存パッケージ(dependencies)が選択できる 関心ごとを分けられる Monorepoにした背景 元々フロントエンドの構成としてはマルチエントリーポイントであり全てのコードは1つに合わせて扱われます。個別のビルドはできず毎回全てをビルドする必要がありました。当たり前ではありますがコードが増えていくとビルド時間も増えていきます。 上記で説明したマルチエントリーポイントの構成は下図のようになります。BASE Appごとのディレクトリ(appディレクトリ)にそれぞれのAppや基本機能があります(図でいうApp1、App2、基本機能1)。そのディレクトリに必要な components , domain , store などのそれぞれの役割に応じたディレクトリにわけてエントリーポイントを配置しています。結果的にそれらをまとめてビルドしているので当然ビルド時間はファイル数の増加に伴い増えていきます。 Monorepo 前のBASE フロントエンドの構成 基本的に右に向かって依存していきます。図は一部の構成にすぎないもののどうしても良くない依存関係が発生したりします(例: domainなどのコードはその特定のAppのみで参照するべきで上図の構造だとどのAppでも参照可能である)。 この上図の状態に対して、整理を行い下図のようなMonorepo構成にしました。BASEで使用する基本機能およびappは frontend/shopadmin 配下に配置し、その機能およびapp単位で特有の components , domain , store を配置しました。また、app共通で使う components や api-client は共通ライブラリとして frontend/package に置きました。こうすることでMonorepo前の依存関係を整理し、マルチエントリーポイントを剥がして単体ビルドが可能となります。これらのことを踏まえてフロントエンドの構成をMonorepoへと移していきました。 Monorepo 後のBASE フロントエンドの構成 Monorepoツールの選定 次はMonorepoをする上でのツールのお話です。 様々なMonorepoツール がある中でBASEでは turborepo を採用しました。 turborepoを採用した理由は下記になります。 ローカルキャッシュ、リモートキャッシュの生成・利用 turborepoでは node_modules/.cache のキャッシュを見て、次のビルド時の復元をすることでキャッシュが利用され、不要なビルドがスキップされる(下図) ビルドパイプラインの実行 依存しているタスクを並列実行し、処理の最適化が可能 パッケージ間の依存関係を考慮した再ビルド パッケージごとに設定されたビルドコマンドを呼び出すことで個別のパッケージでビルドが可能 ⁠キャッシュなし ⁠キャッシュあり Monorepo化してみた 実際にMonorepo化の手順を以下に示します。 scaffdogでプロジェクトの雛形を作成 monorepo用のディレクトリを作成するのですが、 scaffdog を使用してプロジェクトの雛形を作成します。ここでは仮のAppとして「app-sample-a」を作ります。 ? Please select the output destination directory. shopadmin/ ? アプリの名前を設定してください。App管理画面では app- プレフィックスを使用してください。 app-sample-a 🐶 Generated files! ✔ shopadmin/app-sample-a/package.json ✔ shopadmin/app-sample-a/tsconfig.json ✔ shopadmin/app-sample-a/webpack.config.js ✔ shopadmin/app-sample-a/src/index.tsx ✔ shopadmin/app-sample-a/src/components/xxx.vue ✔ shopadmin/app-sample-a/src/store/index.ts これでMonorepoの雛形が完成しました。 引越し作業 「Monorepoにした背景」で記載した部分の対応です。下図はイメージ図でありBeforeにある components , domain , api-client を目的に沿ってmonorepo単体のパッケージ( app-sample-a )もしくは共通パッケージ( frontend/package )に配置して、Afterにあるような形にもっていきます。配置変換に応じてimport先の修正等もやります(後述するeslintでもimport先が間違っている場合はエラーとして検出されます)。 Monorepo 移行のイメージ図 足りない依存パッケージがないか検出する 上記の引越し作業を行うとビルドは可能となります。しかし、turborepoに依存関係を認識させるために使用しているパッケージを package.json に記述する必要があります。依存パッケージがないと判定されると本来依存してるパッケージより先にビルドが走ってしまい、ビルドが失敗します。 turborepoにあるeslintコマンド( turbo run eslint )を行うと、足りないパッケージを検出できます。その検出されたパッケージを追加することでエラー周りを解消していきます。(下記はエラーが出た時のイメージ) # app-sample-a配下でeslint $ turbo run eslint エラーが検出されると下記のように足りないパッケージを指摘します。 6:1 error 'date-fns' should be listed in the project's dependencies. Run 'npm i -S date-fns' to add it import/no-extraneous-dependencies 7:1 error 'lodash' should be listed in the project's dependencies. Run 'npm i -S lodash' to add it import/no-extraneous-dependencies 上記のエラーが解消されて、動作確認完了したらMonorepo移行は完了です。 結果はどうなったか 先日あった Vue Fes Japan Online 2022 にて弊社のスポンサーセッションとして「 BASEのフロントエンド組織の人数が2.5倍になって起きた変化 」という発表がありました。こちらを引用して結果的にどうなったかというと turborepoを導入したことによってローカル環境・デプロイサーバー共に 高速化 フルビルドの時間はそれなりにかかるが、フルキャッシュ、一部だけshopadminの変更があった時のビルド時にかかる時間を削減した。 フルビルド フルキャッシュ時 ⁠一部だけshopadminの変更があった時 567sec 12sec 85sec まとめ 以上、Monorepoの紹介をしました。Monorepoツールは様々あってBASEのフロントエンドはturborepoを採用しましたが、他ツールも検証しながらプロジェクトにあったMonorepoツールを採用してみてください。 またMonorepo化は対応しましたが、まだまだやることはたくさんあります。Monorepo化が完了したこの先としてはパッケージ単体での技術選定などが可能になったのでパッケージ毎で検証しやすくなったと思います。例えば特定のライブラリのアップデート(例: Vueのアップデート)もしくはそのライブラリの代役を探して検証したり(例: Vueのアップデートによって動かないライブラリがあるので代役ライブラリを探す)と、エンジニア目線で色々試行がしやすくなったのではないかと思います。 引き続きBASEではこれからのBASEを一緒に盛り上げて行ってくれる方を随時募集中です!カジュアル面談も実施しておりますので、お気軽にお問い合わせください。 BASE株式会社 の全ての求人一覧 以上、最後までお読みいただきありがとうございました。 ⁠参考 https://monorepo.tools/ https://turborepo.org/
アバター
この度、 2022/10/16(日)に開催された Vue Fes Japan Online 2022 に BASE から2名のエンジニアの登壇およびゴールドスポンサーとして協賛しました! 本記事では2名の登壇者のコメントと、参加したメンバーの感想・コメントをお届けします! Vue Fes Japan Online 2022 とは vuefes.jp Vue Fes Japan Online 2022 は Vue.js 日本ユーザーグループが主催する日本最大級の Vue.js カンファレンスです。 今回 BASE ではゴールドスポンサーとして当カンファレンスに協賛しました。 右下にBASEのサービスロゴ 今回の登壇者 Vue Fes Japan Online 2022 にはプロポーザル採択から 1 件、スポンサーセッションで 1 件登壇させていただきました。 発表内容の詳細と発表資料のリンクは以下をご参照ください。 共通コンポーネントのテスト実装方法にあえてVRTを選択した話 vuefes.jp Vue Fes Japan 2022 での発表スライドです。フロントエンドでの各種テスト実装方法を比較しているので、フロントでどんなテストをしようか悩んでいる方におすすめです! 「共通コンポーネントのテスト実装方法にあえてVRTを選択した話」 #vuefes https://t.co/UQcDNfrFdF — プログラミングをするパンダ (@Panda_Program) 2022年10月16日 BASEのフロントエンド組織の人数が2.5倍になって起きた変化 vuefes.jp ご視聴ありがとうございました〜! たくさんいただいた質問とか感想とか観測したので、質問・疑問は追って回答していきます👀👀 ちょっと早口で喋ってしまってたので、聞き逃した部分とかは↓の資料を見ていただけると嬉しいです! #vuefes #vuefes_cloudsign https://t.co/6pvoUFyPs3 — がっちゃん@えんじにゃ⛺ (@gatchan0807) 2022年10月16日 スタッフ参加 また、登壇・協賛だけでなく Vue Fes Japan Online 2022 運営スタッフとして @rry が参加しました。 上から4段目、左から3つ目にある @rry のアイコン 参加者のコメント プログラミングをするパンダ( @Panda_Program ) Vue Fes の存在はずっと知っていたのですが、BASE に入社してから Vue を触り始めたためいい機会だと思ってプロポーザルを提出しました。 「共通コンポーネントのテスト実装方法にあえてVRTを選択した話」ということで、フロントエンドの各種テスト方法を比較した後、VRTを採用したという話をしました。当日は「テストの導入を検討していたので、知りたかった情報がまとまっている」など嬉しいコメントを頂き、登壇してよかったなと思いました。 今年の Vue Fes はオンライン開催ながらお祭りのような雰囲気があり、Vue のクリエイターである Evan 氏に質問できたり、Vue のエコシステムで活躍されている方が取り組んでいるプロジェクトが紹介されたり(Peephole)、海外の方の登壇など、他のカンファレンスでは見ないようなプログラムがあって充実しているなと思いました。 登壇者の方の発表内容自体もレベルが高くとても勉強になりました。またイベント終了後、会社の同僚と Slack の Huddle をつないで Peephole で紹介された reTypewriter の最初の10コミットをコードリーディングしたり、Vue Fes に刺激を受けた別の同僚が BASE の共通コンポーネントライブラリ(BBQ)のバージョンを 2.7 に上げるプルリクエストを作成したり、会社としてもいい影響をたくさん受けました! 登壇者の皆様、運営の皆様、イベントを盛り上げてくださいありがとうございます。お疲れ様でした! @gatchan0807 初めてVue Fesに参加することができましたが、非常に楽しかった & 勉強になることがたくさんあって、参加してめちゃ良かったです! (個人的に、2019年度に当日スタッフとして参加予定だったものが残念ながら台風の影響で中止になってしまった思い出があります 😢) 今回、私は BASE のスポンサーセッションを担当して 「フロントエンド組織の拡大について」と「Vue.js アセットの運用・改善(Vue 2 から Vue 3 へのマイグレーションも含む)のアプローチ方法」について 話しをさせていただきました。 他の方々の発表を見ている中で、今回の Vue Fes の1つの大きなトピックとして「Vue 2 から Vue 3 へのマイグレーション」に関する話題が多く、 少数名でパワーでやりきるアプローチやチームで施策と並行して進めていくアプローチなどなど、様々なアプローチ方法を見てとれて BASE でのアプローチと比較しながらとても興味深い内容 だなと思って聴講していました。 また、Peepholeのセッションで紹介された reTypewriter は、プロダクトとしてとてもおもしろいと思いましたし、 Vue Fes 終了後に一緒に参加していたメンバーと中のコードを読みながらワイワイ学ぶことができるきっかけになって、とても楽しい時間を過ごすことができました! また来年も開催されればぜひ参加したいです! 最後に、運営の皆さま、登壇された皆さま、このような機会を提供してくださって本当にありがとうございました! @rry 今回コアスタッフとしてコンテンツの企画、託児サポートおよびハンズオンサポートなどいろいろなことをさせていただきました。 まずは無事に Vue Fes が終わったことについてほっとしています。 また来月11/2に Reject Conference も開催するのでぜひ参加してみてください! vuejs-meetup.connpass.com 謝辞 協賛・社員のスピーカー参加を通して Vue.js コミュニティの盛り上がりに貢献でき、弊社としても大変有意義な時間となりました。 スタッフの方々には業務でお忙しいにも関わらず、多くの時間をカンファレンス準備へ注いでいただいたかと思います。この場を借りて御礼申し上げます。 最後に 今回、BASE から 2 名登壇する機会をいただき、運営スタッフの皆様には改めてお礼を申し上げます。本当にありがとうございました。 来年の Vue Fes で、また皆様にお会いできることを弊社メンバー一同楽しみにしております。
アバター
2022/10/16(日)に開催される Vue Fes Japan Online 2022 で BASE に所属する2名のエンジニアが登壇します。 vuefes.jp BASE では 2018年頃から Vue.js を使ったプロダクト開発を行ってきており、これまでいくつか Vue.js に関連するテックブログ記事やイベントへの参加を行ってきました。 そして今回は Vue Fes Japan Online 2022 のスポンサーとなり、またひとつ Vue.js コミュニティへの貢献ができて大変嬉しく思います。 BASE はゴールドスポンサーとして当カンファレンスに協賛しています。 セッションの内容について 共通コンポーネントのテスト実装方法にあえてVRTを選択した話 2022/10/16 17:15〜 メドピアトラック セッション(20分) 所属:BASE株式会社 Product Dev Division シニアエンジニア vuefes.jp Vue を取り巻くエコシステム(ツール)とその運用の話です。 ■ 概要 社内のコンポーネントライブラリに対してStorybookとChromaticでビジュアルリグレッションテストを導入して、見た目のデグレを防止している話をします。 ■ 詳細 Chromaticとは、Storybookのメンテナーが作成しているStorybook用のツールです。 ストーリーごとのスクリーンショットを撮影し、差分を画像で比較してくれる機能を備えています。 以下の課題を解決することを目的にして Chromatic を導入しました。 既存のコンポーネントを改修した際に発生する DOM、CSS に起因する表示崩れを自動で検知できないこと 依存モジュールのバージョンアップに時間がかかること その結果、両方の課題を解決できた上に作業が楽になったという話をします。 また、なぜ他のテストではなくてビジュアルリグレッションテストを導入したのか、その意思決定の過程についても紹介します。 なお、本発表は以下の3記事の内容を合わせて再構成したものになる予定です。 「Storybook と Chromatic でビジュアルリグレッションテストを実施する」 「ビジュアルリグレッションテストのツールを導入するまでの意思決定プロセス」 「TypeScript Compiler API で40の Storybook コンポーネントを storiesOf から CSF(Component Story Format)に置換した」 Software Design 2022年3月号のTDD特集への寄稿なども行われた、 プログラミングをするパンダ さんによる発表です。 社内のコンポーネントライブラリに対してのテストがどのようなものが必要なのかを問い直してから、意思決定をした過程について発表予定とのことで、今から発表が楽しみですね! 社内コンポーネントライブラリに対してのテストに限らず、普段のコーディングの中で私はどこまでテストをすればいいんだろう?と悩まれている方にぜひご覧いただきたいです! BASEのフロントエンド組織の人数が2.5倍になって起きた変化 2022/10/16 12:45〜 クラウドサイントラック スポンサーセッション(10分) 所属:BASE株式会社 Product Dev Division エンジニア vuefes.jp 2020年9月に公開されたテックブログ 「BASEにおけるVue.jsのこれまでとこれから」 から2年経ち、当時から比べるとフロントエンド組織の人数が2.5倍に増加しました。 本セッションでは、記事公開から2年の間の組織の変遷と合わせて、人数増加に伴って拡大したBASEのVue.jsのコードベースのmonorepo化と、既存のVue資産の運用と今後の展望についてお話をします。 私、 @gatchan0807 による発表です。 これまでの2年間の BASE の変化をフロントエンド組織の拡大と、コードベースの拡大の両面からお話します。 また、近年話題に上がることも多い monorepo についても BASE の monorepo の特徴的な部分と合わせて、選択したツール・それらの運用における工夫についてお話します。 今後の BASE のフロントエンド組織・技術についても触れられればと思うので、 BASE に対して少しでもご興味をお持ちでしたらぜひご覧いただきたいです! 最後に Vue Fes Japan Online 2022 の事前登録チケットは下記よりお申し込みいただけます。 カンファレンスへの参加に事前登録は必須ではありませんが、事前登録をしていただくと、Vue Fes Japan の最新情報や当日の配信 URL、セッションに関する情報などをメールでお知らせされるので便利です! passmarket.yahoo.co.jp それでは、みなさまにお会いできることを楽しみにしております!
アバター
XP祭り 2022 XP 祭り 2022 は、XPJUG(日本 XP ユーザーグループ)主催のベントです。2002 年から毎年行われていて、今年 2022 年は、10 月 1 日(土)にオンラインで開催されました。 http://xpjug.com/xp2022/ 今回の XP 祭り 2022 に BASE・BASE BANK から 3 名が登壇しましたので、その参加レポートをお届けします。 セッション内容 エンジニアが新規事業に取り組むところから始めPdMとしてプロダクト開発に向き合う組織を作り続けるまで 2022/10/01 13:00 - 13:20 B Track 所属:BASE, Inc. / New Division / BASE BANK 今回はこれまでの自分の経験をお話させていただきました。なぜ事業責任者を志向するのか、どういう考えでチームをつくっているのか、どのようにプロダクト開発に向き合うのか。 そのようなことを自由に話させていただきました。 結論としてはプロダクト開発が大好きで、これからもやり続けていきたいというものになります。 プロダクト開発のプロとしてプロダクト開発力の向上に向き合う。チームが発展し続ける仕組みをつくって、困難なプロダクト開発に挑み続けようと改めて思いました。 キャリアを考える 1 つの参考になればと思い発表させていただきました。 個人的には発表をする利点は、人生のスナップショットが取れることだと持っているので、スナップショットのつもりでお話させていただきました。 ぜひスライドご確認ください。 confengine.com XP祭り2022 エンジニアが事業責任者になる.pdf from KeitaYanagawa 不確実性に向き合うために、チームのアジリティを高める開発タスクの切り方 2022/10/01 13:00 - 13:45 I Track 所属:BASE, Inc. / BASE Depertment / Product Dev Division この発表では、ユーザーストーリーで最低でも受け入れテストを含むようにタスクを切るとチームのアジリティを高めることができることを発表させていただきました。開発チームのアジリティを高めるための一助となれば幸いです。 当日はトラック数も多い中、想像していたよりも沢山のかたにお越しいただきました。また、発表中に多くのコメントをいただいたことも感謝しています。本当にありがとうございました。 confengine.com オーナーシップを持ち自己組織化するチームに必要な、Engineering Program Managerという役割 2022/10/01 14:25 - 14:45 B Track 所属:BASE, Inc. / New Division / BASE BANK アジリティの高い組織づくりのために、今回自分はロールという観点、それも Engineering Program Manager という比較的新しいロールを基にアジリティの高いチームづくりについて発表させていただきました。 日々要求やアウトプットも複雑化していくこのソフトウェアの世界において、高度な技術スキルと同じくらい、ステークホルダーを接続しなめらかにプロダクトアウトプットし続けることが重要だと考えています。 今回の発表や資料を見てくださった方々の日々の改善の一助となれば幸いです。 confengine.com 最後に 今回、BASE・BASE BANK から 3 名登壇する機会をいただき、運営スタッフの皆様には改めてお礼を申し上げます。本当にありがとうございました。 来年の XP 祭りで、また皆様にお会いできることを弊社メンバー一同楽しみにしております。
アバター
PHPカンファレンス2022 BASEスポンサーブースでの集合写真 こんにちは!さて、この度は、2022/09/24(土)~2022/09/25(日)にオンラインで開催された PHP カンファレンス 2022 にゴールドスポンサーとして協賛し、5名のメンバーが登壇しました。 今回は、登壇者 5 名からコメントと、会場でのスポンサーブースの様子をお届けします! PHP カンファレンス 2022 とは 2022/09/24(土)~2022/09/25(日)の 2 日間にわたって PHP カンファレンス 2022 がオンライン開催されました。BASE はこれまでにも開催されている PHP カンファレンスへの登壇並びにスポンサーをコミュニティ貢献活動として行って参りました。今回はゴールドスポンサーとして当カンファレンスに協賛しています。 右上にBASEのサービスロゴ 登壇者のコメント 松田 ( @tadamatu ) Platformグループ で Group Managerをしている 松田( @tadamatu ) です。 やっぱりオフラインのカンファレンス楽しいですね! さて、今回は 「10年後のBASEとリアーキテクチャ」 と題して、 「我々は、なぜリアーキテクチャしようとしているのか?」にフォーカス して、以下の3段がまえでスポンサーLTとして参加させていただきました。 リアーキテクチャ開始前にしたこと 取り組み中のリアーキテクチャ 10年後のBASE 「リアーキテクチャ自体」の記事や登壇はたくさんあると思うのですが、 「リアーキテクチャ開始前にしたこと」について の語りはあまり多くないと思うので、ご参考にしていただけると嬉しいです。 BASEでは、リアーキテクチャを私の所属するPlatformグループが主導して、現在進行形で 絶賛取組中 なので、興味のある方はDM・カジュアル面談依頼などいただければと思います! 川口 ( @dmnlk ) FLEXY様にお誘い頂いて「急成長3社サービスの開発ストーリー〜継続成長を支える技術と仕組みのお話〜」というパネルディスカッションに参加させていただきました。 Makuake様、Pixiv様とPHPのみというよりは組織戦略や課題などをざっくばらんにお話させていただきました。 時間が足りないくらいに盛り上がり、また別の機会にでもお話できたらいいなと思いました。 改めてご招待ありがとうございました。 glassmonkey ( @glassmonekey ) こんにちは ( @glassmonekey )こと永野です。 今回は体調不良もありオンラインで登壇しました。 2~3日前の急な要請にも関わらずオンラインの登壇でチャレンジできたことは。ひとえに運営の皆様の対応力の高さには度肝を抜かされました。改めてお礼を申し上げます。 ただ、オフライン参加の様子を見ていると羨ましい気持ちはあるので、次回はオフライン登壇のリベンジをしたいなと思っております。 登壇の内容自体は「PHPerから見たWebAssemblyってどうなんだろう?」というテーマで発表しました。 最近は wordpressをWebAssembly で動かすといったものもちょうど出てきてたりで、コミュニティとしては加速していきそうだなと思っています。 WASM使って、REPLとかも作れそうだなと思い近いうちに公開できたらなと思っています。乞うご期待!! プログラミングをするパンダ ( @Panda_Program ) プログラミングをするパンダ ( @Panda_Program ) です。今年、PHPerKaigi のアンカンファレンスに登壇した感想を以下のように書いていました。 発表中に自分のエンジンがかかり、発表後もすぐには熱が冷めなかったため、ライブコーディングに失敗しても、オフラインで参加するのがよかったかなと思いました。オンラインだと「カンファレンスの廊下」(立ち話)もできないですしね。 また参加する機会があれば、次はオフライン参加してみたいと思います! 「PHPerKaigi2022のアンカンファレンスでメンバーが登壇しました」 ということで、PHP カンファレンスでは「実践!ユニットテスト入門」というタイトルでオフラインで登壇してきました。資料は以下です。 25分で収まらなかった内容は最後に付録として掲載しています。 ブースの手伝いをしてBASEというサービスの認知度はまだまだ高くないので気を引き締めていかねばならないと感じたり、カンファレンスの廊下で初めましての方とも技術的な話をして刺激を受けたり、前職の同僚や上司が自分の発表を見にきてくれたので前職の経験が現職で活きているとお礼を伝えられたり、自分がまさにPHPコミュニティにいることを実感しました。 また、自分が尊敬するエンジニアの方の素晴らしい発表 「MVCとはなにか」 (2019年のペチコン)とたまたま自分の登壇する部屋が同じで嬉しくなったり、会社の他部署のメンバーと交流を深めたり、突発的な飲み会で他社のぶっちゃけた内情を聞けたり、オフラインだからこその経験をたくさんしました。 さらに、発表後は「PHPUnitによるテスト実装を切り口としたセッションだが、基礎や座学の部分と実践の部分とのバランスが非常によく取れていたと思う」( PHP Conference 2022 セッション紹介・感想 )というような嬉しい感想を頂けたり、スライドの view 数が SpeakerDeck で 3000 を超えたり、多くの方に見ていただいてとてもありがたいです。 準備の時間や登壇のプレッシャーなど費やしたこと以上に、得ることが多かったカンファレンスでした。来年も登壇ネタを探してプロポーザルを出してみたいと思います。 02 ( @cocoeyes02 ) こんにちは! 02 こと大津です。 今回はちょっと特殊な条件をつけたコードリーディングの話をしました! 今回の題材である assertObjectEquals の話は、 PHPでテスト駆動開発本の写経をした時 にPHPUnitのassert系メソッドに関するコードリーディングをして知ったものでした。OSSのコードリーディングやPull Requestのレビュー、業務コードの調査などテストコードを読むことが活かせる場面はたくさんあります。ぜひプロダクトコードだけでなく、テストコードも読む習慣をつけてみることをオススメします。 発表の最後に「コードリーディングに自信がないと思う人」と書いてありますが、これは私自身のことも指し示しています。コードリーディングに自信がないからこそ、様々な方法でコードリーディングの難易度を下げよう!という結論に行き着いたわけです。これからもコードリーディングに関する登壇は続けていきたいなと思っています! スポンサーブースの様子 初めての技術イベント参加となりました。 海野です。 ブースの様子は以下です。 PHPカンファレンス2022の2Fのスポンサーブースご紹介です! BASEさんのスポンサーブースは、カワイイPHP公式マスコットキャラクターを飾っています!🐘🐘🐘 楽しい BASE Apps クイズも開催しています! ぜひ、お立ち寄りください!😀 #phpcon2022 #phpcon pic.twitter.com/9meQx3WnF1 — PHPカンファレンス2022 (@phpcon) 2022年9月25日 初日は雨の中でしたがその中でも印象的だったのは BASEって知ってはいるけどメイン言語はPHPなんだ! と言われることが多かったことです。 実際にどのようなことをしたかというと、Appsで機能拡張ができることを更に知っていただくために、ご来場いただいた方に2日間はAppsクイズを使った会社紹介や登壇者が載ったチラシ配布などさせていただきました(私は最終日のみの参加)。 相手が技術者かそうでないか(お子さんもいらっしゃいました)、他社様のブースのクイズを経験しているかどうか、タイミング的にこちらの登壇者のスケジュール前か後か。 こういった要素によってどのような声かけや話題が好まれるのかが常に変わるため一筋縄ではいきませんでしたが、参加者の方にチラシを手に取っていただいた時はうれしかったです。 開催イベントごとに色が違うPHP公式マスコットキャラクターElePHPantぬいぐるみに助けられたりもしました。 紹介するにあたって瞬間的に興味を抱いていただくことも大切だなと感じました。どこまで可能かはわかりませんが次は何かしらノベルティ的なものも駆使できたらなと思います。 また、ブース作業についてではないのですが、一参加者として隙間時間には登壇者の方々の発表やLTを見ることができとても刺激になりました。まずはLTに出られるようにしたいなと密かに思っています。カンファレンス中や後に他部署の方々と交流できたこともよかったです。 PHP Conference Japanチャンネル では皆さんの発表を見ることができます。 謝辞 協賛・社員のスピーカー参加を通して PHP コミュニティの盛り上がりに貢献でき、弊社としても大変有意義な時間となりました。 スタッフの方々には業務でお忙しいにも関わらず、多くの時間をカンファレンス準備へ注いでいただいたかと思います。この場を借りて御礼申し上げます。 最後に 来年の PHP カンファレンスは 10/8(日)に開催される予定 だそうです。 PHPカンファレンス2023は2023/10/8開催予定です!! それでは、また来年の PHP カンファレンスでお会いしましょう!
アバター
はじめに みなさんはじめまして。BASEでエンジニアをしております田村 ( taiyou )です。 先日、BASEではショップオーナー向けのコミュニティサイト「BASE Street」にログインするための機能としてSSOログイン機能をリリースしました。 SSOログインを実現するための認証方式はいくつかあるのですが、弊社ではSAML認証方式を用いて実現しました。 そのため、この記事ではSAML認証機構のIdPとしてOSSを使わずにSAML認証機能を実装する方法を紹介します。 前回のテックブログで、このSSOログイン機能のフロント側を開発したPJメンバーの若菜が「 サーバーサイドエンジニアがフロントエンドに挑戦して最高の経験になった話 」を執筆したのでこちらも見てみてください! SAML認証機能を提供しているOSSには、 Keycloak などがありますが、BASEでは以下の理由により自前実装することにしました。 既に大量のユーザー情報を管理しており、Keycloakなどにユーザー情報の連携を行う必要がある 弊社で採用しているPHP, Goで実装された有名なOSSがないため、弊社エンジニアで管理・運用するハードルが高い SAML認証機能を有するライブラリ( lightSAML )があり、自前実装のコストが高くなかった 以上の理由により、OSSを使わずにIdPとしてSAML認証機能を開発しました。 対象読者 SAML認証についてこれから調べようと思っている方 IdPとしてkeycloakなどのOSSを使わずにSAML認証機能を開発するエンジニア SAML認証とは? SAML認証とは、シングルサインオン(SSO)を実現する一つの認証方式です。 シングルサインオン(SSO)とは? Single sign-on (SSO) is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems. True single sign-on allows the user to log in once and access services without re-entering authentication factors. Single sign-on - Wikipedia より引用 シングルサインオン(SSO)とは、ユーザーが1つのIDで複数のサービスにログインできるする認証方法です。 このシングルサインオンによりユーザーは一度ログインすれば、認証要素を再入力することなくサービスにアクセスすることができます。 SAML認証はシングルサインオン(SSO)を実現するための認証方式 上記のシングルサインオン(以下、SSOと呼称)を実現する認証方式は、 Single sign-on - Wikipedia で記載されている通りいくつかあります。SAML認証は、そのSSOを実現するための一つの認証方式です。 SAML認証はシングルサインオン(SSO)を実現するための認証方式 SAML認証方式以外の方法については、以下の文献を参照してください。 Single sign-on - Wikipedia#Common_configurations シングルサインオン(SSO)とは-概要・仕組み | GMOトラスト・ログイン(SSO/IDaaS) SAML認証のフロー それでは、SAML認証方式でSSOを行うためのフローを説明します。以降でSAML認証のフローについて説明する前に、サービスプロパイダー(SP)とアイデンティティプロパイダー(IdP)について説明します。 サービスプロバイダー(SP) 文字通り、ユーザーに対してアプリケーションサービスを提供するものです。ユーザーがサービスプロパイダー(以降、SPと呼称)にログインする際、後述するアイデンティティプロパイダーにユーザー認証を行ってもらいSPにログインします。 サービスプロバイダー(SP) アイデンティティプロパイダー(IdP) ユーザーの認証に必要な情報を管理しているのがアイデンティティプロバイダーです。アイデンティティプロバイダーでは、SPから送信された認証リクエストを処理し、ユーザー情報をSPに返却します。 アイデンティティプロバイダー(IdP) SAML認証フロー 詳しい内容は、 Security Assertion Markup Language - Wikipedia を参照してください。 Security Assertion Markup Language - Wikipedia より引用 1. SPのページへアクセスする まずユーザーはSPのページにアクセスするとします。このとき、ユーザーが既にSP側で認証済みの場合はSAML認証を行う必要がないため、ページが表示されます。認証されていない場合は、IdPへリダイレクトされます。 2. IdPへリダイレクトする ユーザーがまだ認証されていない場合は、IdPへリダイレクトされます。IdPへリダイレクトされる際に、クエリパラメーターに SAMLRequest パラメーターが付与されます。この SAMLRequest はIdPに認証の要求をする際に必要となる以下のようなxml形式の情報を圧縮した文字列になります。 < samlp : AuthnRequest xmlns : samlp = "urn:oasis:names:tc:SAML:2.0:protocol" xmlns : saml = "urn:oasis:names:tc:SAML:2.0:assertion" ID = "{認証要求ID}" Version = "2.0" ProviderName = "{サービスプロバイダー名}" IssueInstant = "{SAMLRequestの生成日時}" Destination = "{SAMLRequestの送信先IdPのURL}" ProtocolBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL = "{認証結果のPOST先SPのURL}" > < saml : Issuer> http://sp.example.com/hoge/metadata.php </ saml : Issuer> < samlp : NameIDPolicy Format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" AllowCreate = "true" /> < samlp : RequestedAuthnContext Comparison = "exact" > < saml : AuthnContextClassRef> urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport </ saml : AuthnContextClassRef> </ samlp : RequestedAuthnContext> </ samlp : AuthnRequest> 3. SAMLRequestを検証/ユーザー認証を行います IdPでは、まずユーザーのブラウザ(ユーザーエージェント)を経由してSPから送信された SAMLRequest の検証を行います。この検証では、電子署名付きの SAMLRequest が送信される場合では、電子署名を行います。 SAMLRequest の検証が終了したら、ユーザー認証のためにログインページを表示します。ユーザーはIdPに登録したメールアドレスやパスワードを入力します。 4. SAMLResponseを生成します ユーザー認証のための情報をフォームに入力して送信したら、IdP側でログイン処理を行います。そして、ログインに成功したら、SPに送信する SAMLResponse を生成します。この SAMLResponse はSP側でユーザー認証を行う際に利用する情報をxmlに格納します。 SAMLResponseの例 <? xml version = "1.0" encoding = "UTF-8" ?> < samlp : Response xmlns : samlp = "urn:oasis:names:tc:SAML:2.0:protocol" xmlns : saml = "urn:oasis:names:tc:SAML:2.0:assertion" Destination = "{SAMLResponseの送信先SPのURL}" ID = "{IdP側で発行するID}" InResponseTo = "{SPから送信されたSAMLRequestに含まれる認証要求ID}" IssueInstant = "{SAMLResponseを発行した日時}" Version = "2.0" > < saml : Issuer> {IdPのURL} </ saml : Issuer> < samlp : Status> < samlp : StatusCode Value = "urn:oasis:names:tc:SAML:2.0:status:Success" /> </ samlp : Status> < saml : Assertion xmlns = "urn:oasis:names:tc:SAML:2.0:assertion" ID = "{IdP側で発行するID}" IssueInstant = "{SAMLResponseを発行した日時}" Version = "2.0" > < saml : Issuer> {IdPのURL} </ saml : Issuer> < dsig : Signature xmlns : dsig = "http://www.w3.org/2000/09/xmldsig#" > < dsig : SignedInfo> < dsig : CanonicalizationMethod Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#" /> < dsig : SignatureMethod Algorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /> < dsig : Reference URI = "#ID_b93d4d7d-1937-474f-84df-2f3440025a3c" > < dsig : Transforms> < dsig : Transform Algorithm = "http://www.w3.org/2000/09/xmldsig#enveloped-signature" /> < dsig : Transform Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#" /> </ dsig : Transforms> < dsig : DigestMethod Algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" /> < dsig : DigestValue> {ハッシュ値} </ dsig : DigestValue> </ dsig : Reference> </ dsig : SignedInfo> < dsig : SignatureValue> {電子署名の値} </ dsig : SignatureValue> < dsig : KeyInfo> < dsig : X509Data> < dsig : X509Certificate> {IdP側で発行した証明書} </ dsig : X509Certificate> </ dsig : X509Data> </ dsig : KeyInfo> </ dsig : Signature> < saml : Subject> < saml : NameID Format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" > {ログイン対象となるユーザのメールアドレス} </ saml : NameID> < saml : SubjectConfirmation Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer" > < saml : SubjectConfirmationData InResponseTo = "{SPから送信されたSAMLRequestに含まれる認証要求ID}" NotOnOrAfter = "{SAMLResponseの有効期限}" Recipient = "{SAMLResponseの送信先SPのURL}" /> </ saml : SubjectConfirmation> </ saml : Subject> < saml : Conditions NotBefore = "{SAMLResponseの有効期限開始日時}" NotOnOrAfter = "{SAMLResponseの有効期限終了日時}" > < saml : AudienceRestriction> < saml : Audience> {SPのドメイン} </ saml : Audience> </ saml : AudienceRestriction> </ saml : Conditions> < saml : AuthnStatement AuthnInstant = "{SAMLResponseを発行した日時}" SessionIndex = "{IdP側で発行するID}" SessionNotOnOrAfter = "{IdP側のセッション有効期限}" > < saml : AuthnContext> < saml : AuthnContextClassRef> urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified </ saml : AuthnContextClassRef> </ saml : AuthnContext> </ saml : AuthnStatement> < saml : AttributeStatement> ... </ saml : AttributeStatement> </ saml : Assertion> </ samlp : Response> 5. SPへSAML認証情報をPOSTします IdP側で SAMLResponse の生成が完了したら、次のようなHTMLをレンダリングし、POSTします。 < form method = "post" action = "https://sp.example.com/saml2/sso/post" ...> < input type = "hidden" name = "SAMLResponse" value = "{XML形式のSAMLResponseをbase64エンコーディングした値}" /> ... < input type = "submit" value = "Submit" /> </ form > レンダリングした際、JavaScript側で画面が表示されたらsubmitボタンを自動で押下する処理を行うことでユーザーはSP画面へ自動遷移するようになります。 6. SPのページが表示される SP側でIdPから送られた SAMLResponse の検証が終了し、ログイン処理が終了したら、SAML認証は成功です。これで、ユーザー認証が必要なSPのページが表示されます。 SAML認証を実現する方法 上記で説明したSAML認証を実現するための方法として、次の2つが考えられます。 方法1. OSSを利用する SAML認証を実現するための代表的な方法として、OSSライブラリを利用する方法があります。現在、SAML認証機能を提供できるOSSとして以下のものがあります。 Keycloak : https://github.com/keycloak/keycloak これらのOSSを利用することで、ユーザーに対してSAML認証機能を提供することができます。 メリットとデメリットはそれぞれ以下の通りです。 メリット OSSをcloneし、SAML認証用サーバーを用意し、起動すればSAML認証機能を提供できること デメリット SAML認証用サーバーの保守が新たに必要になる BASEのように既にユーザーデータが管理されている場合は、BatchやAPI, MQなどを利用してSAML認証用サーバーにデータを連携する必要がある 方法2. 自前でSAML認証機能を開発する 別の方法として、既に提供しているサービスの1機能としてSAML認証機能を開発し、提供する方法があります。つまり、SAML認証用のエンドポイントとSAML認証用のログインフォームなどを開発することで提供する方法です。 メリット 稼働中のサービスの1機能として提供するので、新たにSAML認証用サーバーを用意する必要がなく、運用コストが抑えられる ユーザー認証に必要な情報をOSSなどに連携する必要がない デメリット SAML認証機能を開発する実装難易度が比較的高い BASEでは、次の2つの理由から方法2を採用しました。 SAML認証機能を利用するユーザーが限られているため、keycloakのメンテナンス・ランニングコストとユーザーへの費用対効果が釣り合わない BASEでは既に大量のユーザー情報を管理しているため、これをkeycloakに連携する必要がある 弊社で採用しているPHP, Goで実装された利用実績のある有名なOSSがないため、弊社エンジニアで管理・運用するハードルが高い IdPとしてBASEではどのような設計になったか? BASEのように既にサービスに登録しているユーザーのSAML認証を行う際に、BASEではどのような設計になったのか紹介していきます。 SAML認証のための必要な機能とページ 具体的な設計の紹介に入る前に、IdPとしてBASEでは、そもそもどのような機能が必要なのかを明らかにしてきます。SAML認証のための必要な機能は以下の通りです。 機能 説明 SAMLRequestの検証機能 SPからクエリパラメーターで送信されたSAMLRequestを検証する機能です。SP側で電子署名を行っている場合など検証が必要な場合に呼び出される機能です。 ログイン判定機能 SPからリダイレクトされたユーザーがBASEに既にログインしているか判定する機能です。もしログイン済みの場合は、ログイン処理をスキップするようにします。 ログイン機能 メールアドレス、パスワードなどユーザー情報を指定することでログイン処理を行う機能です。機能自体は通常のログインと差異はありません。 SAMLResponseの生成機能 メールアドレスやユーザーネームなどのユーザー情報とSPから送信されたSAMLRequestからSAMLResponseを生成する機能です。 また、必要なページは以下の通りです。 ページ 説明 SSOログインページ SSOログインのために必要なフォームを有するページです。 SPリダイレクトページ SAMLResponseをSPに送信するためのフォームページです。通常、このページではユーザー自身がフォームの送信ボタンを押すのではなく、ページがロードされたらjsで送信ボタンを押すように実装されます。 SPからBASEにリダイレクトされた際の画面遷移としては、SSOログインページでログインを行い、SAMLResponseをSPにフォームPOSTするSPリダイレクトページを表示するような画面遷移になります。 これらの機能とページがそれぞれどのようにやりとりするのか詳細の設計を紹介します。 処理の流れ 1. SPからBASEにリダイレクトされる SPからBASEにリダイレクトされたら、次の処理を行います。 SAMLRequestの検証処理 ログインの判定処理 既にBASEにログインしている場合は、「3. SAMLResponseを生成し、SPにリダイレクトする」へ進みます。 BASEにログインしていない場合は、ログインページを表示します。 2. BASEにログインする ログインページが表示されたら、ログイン処理に必要なメールアドレスとパスワードを入力し、submitします。今回BASEで開発したSAML認証機能ではすでにBASEサービスに登録しているユーザーにのみ提供する機能なので新規登録フォームは除外しています。 3. SAMLResponseを生成し、SPにリダイレクトする ログイン処理が正常に完了したら、SPへ返す認証情報であるSAMLResponseを生成します。SAMLResponseを生成したら、SPリダイレクトページを表示します。このSPリダイレクトページは、次のようなSPへPOSTするフォームページです。 < form method = "post" action = "https://sp.example.com/saml2/sso/post" ...> < input type = "hidden" name = "SAMLResponse" value = "{SAMLResponseをbase64エンコーディングした値}" /> ... < input type = "submit" value = "Submit" /> </ form > この場合、ユーザーが手動でsubmitボタンを押すことでSPへ遷移することも可能ですが、SAML認証機能を提供する多くのサービスはページがロードされたら自動でsubmitボタンを押下するjavascriptコードを実装することで自動遷移するようにしています。 おわりに この記事では、OSSを使わずにIdPとしてSAML認証機能を開発する方法について紹介しました。 IdPとしてSAML認証機能を提供する方法として、それぞれ ①OSSを利用する ②自前実装する 方法があります。今回、BASEでは②自前実装する方法を採用し、開発を行いました。その開発中に私が苦労した点として「 SAML認証についての資料が少なかった 」があげられます。そのため、この記事がみなさんの参考に少しでもなれれば幸いです。 参考文献 Security Assertion Markup Language - Wikipedia Using HTTP POST binding in OpenSAML 4 | SAMLSecurity How to turn your PHP website into a SAML Identity Provider in 30 minutes | by Duarte Garin | Prosple Engineering | Medium RFC 7522 - Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants シングルサインオン(SSO)とは-概要・仕組み | GMOトラスト・ログイン(SSO/IDaaS)
アバター
⛰ はじめに こんにちは。Owners Marketing所属の 若菜 です。 今回は、普段サーバーサイドエンジニアとしてプロダクト開発に従事している私が、BASEのフロントエンド開発に携わった経験をお話しさせていただきます。 結論、 付加価値がいくつもあった非常に良い経験であった と言えます。 BASEでの働き方や開発組織の雰囲気を少しでも伝えることができましたら幸いです! 🙋‍♂️そもそもなぜフロントエンド領域を担当することになったのか 私の所属するOwners Marketingでは、新規ショップオーナーの方によりよくBASEを使ってもらえるための機能改善や、 もっとたくさんの人にBASEを使ってもらえるようにするための機能提供に取り組んでいます。 先日、「オーナーズコミュニティ BASE Street へSSOログインできるようにする」というリリースを行いました。 BASE Streetは、ショップオーナー同士で意見を交わすことのできる場を、外部サービスを用いて提供しています。 そちらのサービスにBASEのアカウントでログインできるようにすることでより利便性を向上し、 ショップを開設して間もない方から既にBASEを使ってくださっているベテランの方までさまざまな利用者様の交流を通じ、 お悩み解決などに役立てていただきたいという目的があります。 BASEでは、従来であれば技術領域にあったエンジニアをアサインしてプロジェクトを進行しますが、 今回のプロジェクトにおいては 私自身がフロントエンドの開発に興味があった そこまで技術難易度の高い開発になる見込みはなく、スケジュールにも多少余裕があった 直近で似たような機能をほかプロジェクトでリリースしたので参考にしやすい 上記の理由をもとに、プロジェクトの人員配置を検討する際に相談をさせていただきました。 BASEには、 手を挙げることでやりたいことに積極的にチャレンジさせてもらえる という社風がある こともあり、今回の挑戦も歓迎していただけました。 BASEではフロントエンドフレームワークとしてVue.jsが採用されています。 私自身のフロントエンド経験はjs・jQueryで止まっていたため、Vue.jsを業務で学ぶことができる絶好の機会をいただくことができました。 🏃‍♀️ どのように進めたか 社内ドキュメントに沿って環境構築を行った 社内にはフロントエンド開発の手引きとなるドキュメントが用意されています。 こちらにはフロントエンド開発における前提知識からプロジェクトの作成の仕方、進め方までがハンズオン形式で解説されています。 こちらを参考にし、Vue.jsのインストールからプロジェクト用のディレクトリ作成、ルーティング設定などを行いました。 ディレクトリ構造などをざっくりと理解しつつ手を動かしてみた 他プロジェクトのコードや、社内で用意されているドキュメントに目を通して、 ディレクトリ構造や今回触っていくことになるファイルの役割、書き味を理解しました (エントリファイルとは、シングルファイルコンポーネントとは、というレベルから学びました) 周りのフロントエンドエンジニアたちに協力してもらいながら開発を進めた 自分だけではプロダクト品質をリリース水準まで満たすことができないので、周りの方にご協力いただき以下の体制で進めました。 フロントエンド開発の経験があるEMにデイリーMTGを設定してもらった 進め方がわからないところ、環境構築で詰まっているところなどを画面共有で教えてもらった レビューの際、他のフロントエンドエンジニアに入ってもらった プルリクエストを出しておくことで他チームの方もレビューしてくれた BASEではエンジニア全員参加のチャンネルでPRが流れてくる仕組みになっている 他チームのエンジニアがコメントをくれることがよくある 🖼 完成した画面 以下、作成した実際の画面および機能です。 ログイン画面の表示・装飾 入力値のバリデーション制御 ログインする ボタンを押下するとログインAPIをコール ログイン処理に成功したらローディング画面へ遷移 時間はかかりましたが、自分で1から関わった実装が形となって世に出ていく様は特別な嬉しさがありました。 🔥 苦労したところ 無事何事もなくリリースすることができた本プロジェクトですが、実施している中で主に以下の点に苦労しました。 アーキテクチャやディレクトリ構造、データフローの理解 BASEのフロントエンド領域のコードにまともに触れたことが初めてだったため、ディレクトリ構成やデータの流れ・処理の流れをコードを読みながら掴むところが初めの難関でした。 こちらは前述した社内ドキュメントにハンズオン形式で丁寧に解説されているため、これを読みながらドキュメント内のサンプルソースコードを手元に書き起こし、デバッグコードを仕込んだりして地道に体に浸透させていきました。 このアーキテクチャ構造が理解できた時、自分の行いたい処理をどこに書けばよいかがわかり、すごくコーディングしやすくなった感覚がありました。 Vue.jsとTypeScriptの記法をまとめて覚えようとしてしまったところ フロントエンド実装を行うにあたり、他プロジェクトでのソースコードなどをたくさんみて参考にしましたが、 Vue.jsとTypeScriptに触れることがほぼ初めてだったため、 実装の仕方や参考にすべきリファレンスがわからず、もどかしい思いをしました。 こちらはVue.jsとTypeScriptそれぞれの初学者向けのリファレンスを広く読んで自分なりに咀嚼していったことと、周囲のフロントエンドエンジニアの方にデイリーMTGやSlackでこまめに質問させていただき、解決することができました。 👍 こういうところが良かった・今後にこうやって活かしたい フロントエンド開発に1から携わり、上述した点などで苦労も多くありましたが、何より「挑戦してよかった」と思えることがいくつもありました。 中でも、個人の観点とチームの観点からそれぞれ抜粋させていただきます。 個人の観点では サーバーサイドからフロントエンドまでの処理・データの流れを体感できた 日頃の開発でBASEというプロダクトにサーバーサイド観点のみで触れてきた自分にとって、フロントエンドの処理は正直わかっていない点が多くありました。 今回、自分でVue.jsのコードを書くことでサーバーサイドからフロントエンドまで一つの流れとして追うことができ、自分の作った機能のすみずみまで自分で把握することができました。 これによって、何か問題があった時の対応を素早く検討することができたり、新たな改善を行う際にもサーバーサイドとフロントエンドの両面からアプローチを考えることができます。 Web開発・サービス提供に従事するものとして、こうでありたいと改めて再認識しました。 社内調査でカバーできる範囲が格段に増えた 社内の調査タスクにおいて、フロントエンド領域のコードを読めるようになったことでいい影響がありました。 具体的には、今まではフロントエンドエンジニアの方に聞かなければ調査ができないようなものも、まず自分で調査に着手することができ、わからないところだけ質問する方法をとることができるようになりました。 これは、読解し理解できる範囲の母数が単純に増えたこともありますが、Vue.jsのディレクトリ構成やフォルダの役割を知ることができたおかげで、 フロントエンドのコードを読む心理的障壁がなくなった と思っています。 jQueryで止まっていたFEキャリアを再開できた 新卒から3年ほどjQueryを扱っていましたが、それ以降モダンなフロントエンドフレームワークにほぼ触れてこなかったので、今後のキャリア形成にとてもいいチャンスをいただけました。 チーム・組織の観点では 後続も挑戦しやすい雰囲気作りに励みたい 今回自分が心置きなくフロントエンドの開発に挑むことができたのは、周りのエンジニアたちがサポートしてくれたからに他なりません。 そのため、これから同じようにサーバーサイドエンジニアだけどフロントエンドの開発に携わりたい!と思った人がスムーズに開発できるように、 社内ドキュメントの最適化やレビューに積極的に参加していきたいと思っています。 BASEの「チャレンジ大歓迎」な社風をより活発にしていきたい 前述の通り、BASEの開発組織では やりたいことに手を挙げることで積極的にチャレンジできる 社風があります。 私は、自らの担当領域にとらわれず、様々なドメイン領域・技術領域にチャレンジできるBASEの空気がとても好きです。 それらの体験は開発者体験を向上させ、組織の生産性向上にも寄与し、ひいてはサービス利用者の方々への高品質な価値提供に直結すると思っています。 この最高な好循環を自分で体感できたことで、今後のBASEの開発組織をもっともっとよくしていきたいと思いました。 📢 お知らせ 次回は、同じプロジェクトに携わった田村より SSOログインに関する記事を公開予定です! SAML認証によるSSOログインをどのように自前で実装したのか?を解説する、非常に充実した内容となっておりますのでご期待ください! また、BASEではこれからのBASEを一緒に盛り上げて行ってくれる方を随時募集中です! カジュアル面談も実施しておりますので、お気軽にお問い合わせください。 open.talentio.com
アバター
XP祭り 2022 XP 祭り 2022 は、XPJUG(日本 XP ユーザーグループ)主催のベントです。2002 年から毎年行われていて、今年 2022 年は、10 月 1 日にオンラインで開催されます。 xpjug.com 今回、BASE・BASE BANK から 3 名が登壇します。 セッション内容について エンジニアが新規事業に取り組むところから始めPdMとしてプロダクト開発に向き合う組織を作り続けるまで 2022/10/01 13:00 - 13:20 B Track 所属:BASE, Inc. / New Division / BASE BANK BASE BANK 所属の、柳川さんによるセッションです。新規事業を 1 から立ち上げ、現在グロースフェーズを迎えるまでの失敗と成功の経験を発表します。熱い話が聞けそうですね。 confengine.com 不確実性に向き合うために、チームのアジリティを高める開発タスクの切り方 2022/10/01 13:00 - 13:45 I Track 所属:BASE, Inc. / BASE Depertment / Product Dev Division BASE 所属の @tanden が発表させていただきます。不確実性に振り回されることなく、チームのアジリティを高めるために、開発タスクをどう切り出していくとよいのかについて発表します。 confengine.com オーナーシップを持ち自己組織化するチームに必要な、Engineering Program Managerという役割 2022/10/01 14:25 - 14:45 B Track 所属:BASE, Inc. / New Division / BASE BANK BASE BANK 所属の、 松雪さん によるセッションです。最近、Engineering Program Manager(EPM)というロールが注目を集めていますが、BASE BANK チームにおける EPM の役割とその働き方について発表します。自律的なチームをどう作っていくのかのヒントを聞けそうで楽しみです。 confengine.com 最後に XP 祭り 2022 への参加は下記よりお申し込みいただけます。 xpjug.connpass.com それでは、オンラインでの開催にはなりますが、みなさまにお会いできることを楽しみにしております。
アバター
2022/09/24(土) 〜 2022/09/25(日)の日程で開催される PHP Conference Japan 2022 で BASE に所属する4名のエンジニアが登壇します。 phpcon.php.gr.jp BASE はこれまでも PHP カンファレンスへの登壇並びに協賛をしています。 PHPカンファレンス2021に5名のメンバーが登壇・プラチナスポンサーとして協賛しました - BASEプロダクトチームブログ PHP Conference Japan 2020に4名のメンバーが登壇!プラチナスポンサーとして協賛しました! - BASEプロダクトチームブログ PHP Conference Japan 2019に3名のメンバーが登壇・プラチナスポンサーとして協賛しました - BASEプロダクトチームブログ 「PHP Conference 2018」にゴールドスポンサーとして参加しました - BASE Book(ベイスブック) PHPカンファレンス2015 - #phpcon2015 今年で通算6回目のスポンサーとなり、PHP コミュニティへの貢献を続けることができ大変嬉しく思います。 2022年はゴールドスポンサーとして当カンファレンスに協賛しています。 また、スポンサーブースでは BASE Apps クイズ の催しを行います。ぜひお越しください! セッションの内容について PHP で始める WebAssembly 入門 2022/09/24 14:40〜 Track1 レギュラーセッション(25分) 所属:BASE, Inc. / NEW Division / BASE BANK fortee.jp みなさんは WebAssembly はご存知でしょうか? ブラウザで利用するケースはもちろんのこと、最近ではサーバーレスアプリケーションで活用できるようになってきました。 このトークでは PHP と WebAssembly に関連するエコシステムの紹介と可能ならデモも紹介します。 Go Conference でも登壇実績のある glassmonkey さんによる発表です。 WebAssembly(WASM)は最近フロントエンドまわりでよく耳にする話題ですね。 PHP x WebAssembly の組み合わせではどういったものがあるのか気になります! 実践!ユニットテスト入門 2022/09/24 16:00〜 Track3 レギュラーセッション(25分) 所属:BASE, Inc. / Product Dev Div / Service Dev Sec / CRM fortee.jp テスト書いてますか? テストを書く理由と実際のテストコードを紹介する実践編に分け、TDD を3年間実践してきた経験に基づいてお話しします。 テストを書いたことのない方が、テストを書いてみたいと思ってもらえることを目指します。 サンプルコードは PHP + PHPUnit ですが、他言語でも通用する考え方を紹介します。 ■ 概要 ・なぜテストコードを書くのか ・レガシーコードとは、テストのないコード ・テストはコストが安いフィードバックループである ■ 実践編 ・テストケースは日本語で書こう ・いろんな assertion を知ろう ・arrange / act / assertion のテストコード実装パターン ・set up / tear down を使って前処理/後処理をする ・dataProvider でテストをまとめる(ただし早すぎる抽象化に気をつけよう) 登壇者のプログラミングをするパンダさんは以前 TDD の話題で Software Design に寄稿 したりしています。 テストを書くのにハードルを感じている方にもおすすめのセッションです! テストコードリーディングのみで PHPUnit の仕様を理解してみる 2022/09/25 13:55〜 Track4 レギュラーセッション(25分) 所属:BASE, Inc. / Product Dev Div / Service Dev Sec / Sales Promotion fortee.jp みなさんは「初めて見るコードの仕様を爆速で理解したい!」と思うときはありませんか? 例えばプロジェクトに初参画したときや、使用している OSS ライブラリの調査をするときなど・・・ そんな時には既存のテストコードを活用すると便利です。 見通しの良いテストコードやテストケース管理は、テスト対象となるコードの仕様理解を手助けします。 今回のセッションでは、PHPUnit の実装コードや公式ドキュメントサイトを一切見ずに、 PHPUnit 内で書かれているテストコードを読みながら PHPUnit の仕様を理解していきます。 当セッションはこんな方におすすめです! ・自分以外の人が実装したテストコードをあまり読まない人 ・PHPUnit のコードを読んだことがない人 PHP カンファレンスではおなじみの 02 さんによる発表です。 去年は Composer2.0 についての発表 でしたが今年は PHPUnit についてです!テストについての関心が高まります。 10年後の BASE とリアーキテクチャ 2022/09/25 16:00〜 Track1 小展示ホール ゴールドスポンサートーク(5分) 所属:BASE, Inc. / Product Dev Div / Service Dev Sec / Platform fortee.jp BASE では現在進行形でリアーキテクチャに取り組んでいます。 何時間にも渡り議論を繰り返し、どのような経緯でモジュラモノリス採用に至ったのか。 また、BASE のどんな将来像を見据えてリアーキテクチャに取り組んでいるかといったことを話します。 Platform グループでマネージャーをしている tadamatu さんの発表です。最近弊社 Tech Blog にて OpenAPI 関連の記事 を書いていただきました。 サービスリリースから長い年月を経て、今どのようにしてリアーキテクチャへ取り組んでいるのかについてです。 同じような規模のサービスを開発しているエンジニアには気になる話題ではないでしょうか? 最後に PHP Conference Japan 2022 の当日のチケットは下記よりお申し込みいただけます。 phpcon.connpass.com それでは、みなさまにお会いできることを楽しみにしております!
アバター
こんにちは。Pay ID Devの大木 ( @roothybrid7 )です。 今回外部スクリプトとして読み込み利用する外部SDKを、Reactに組み込むためのラッパーライブラリを作ったので、その開発事例を紹介します。 今回、 SWC(Speedy Web Compiler) や Release Please を利用して開発したので、主にそれらをどう使ったのかを紹介いたします。 背景 去年12/16に開催しましたオンラインイベント 「BASE Tech Talk #1 〜Next.jsを使ったカート大規模リプレイスPJの裏側〜」 の通り、BASEカートシステムのFrontendアプリケーションは、Next.jsで動作してます。 さて、アプリケーションでは、Amazon PayやPayPalなど様々な外部の決済サービスを利用しており、それらのJavaScript SDKをいくつか利用しています。 これらのSDKは、幅広いWebサイトで動作するように作られており、ドキュメントも用意され、各種APIを簡単に利用できるようになっています。 利用方法に関しては決められており、バンドルしたり自分自身でホスティングして利用することはできず、 <script> タグを使って直接読み込む必要があります。 また、利用登録をして、API Keyといったものも必要になったりします。 SDKの機能利用の方法は、サービスによって様々あり、スクリプト読み込み後のグローバルオブジェクトのメソッドを使うだけのものであったり、一度しか作れないインスタンスを取得しそれによって始めて機能を使うことができるものもあります。 Reactに組み込むのは、 他のライブラリとのインテグレーション で述べられているように確かに可能です。 そこでは、他ライブラリのスクリプトが組み込まれた前提の話で勧められていますが、 <script> で直接読み込むSDKの場合、以下の点を考慮する必要があると思っています。 バンドルして利用することはできないため、スクリプトが読み込まれているかや重複して読み込まないか、SDKの初期化方法の確認 SDKのI/Fを使って、Reactのライフサイクルに応じた処理の実行、SDKから取得した値やインスタンスを失わないように保持 小規模なアプリケーションなら、グローバル変数やSDKを利用するReactコンポーネントで保持すれば問題ないのかもしれません。 ただし、規模が大きくなれば、Reactコンポーネントツリーの末端でしかも複数の離れた箇所で利用することもあるため、組み込むのは難しくなります。 問題に関しては、 react-paypal-js に記載されている通りで、他のSDKでも同じような問題が起こります。 The Problem Developers integrating with PayPal are expected to add the JS SDK <script> to a website and then render components like the PayPal Buttons after the script loads. This architecture works great for simple websites but can be challenging when building single page apps. React developers think in terms of components and not about loading external scripts from an index.html file. It's easy to end up with a React PayPal integration that's sub-optimal and hurts the buyer's user experience. For example, abstracting away all the implementation details of the PayPal Buttons into a single React component is an anti-pattern because it tightly couples script loading with rendering. It's also problematic when you need to render multiple different PayPal components that share the same global script parameters. それらの本質的でない複雑な処理は、アプリケーションに実装するよりライブラリにまとめてしまった方が、アプリケーション実装は簡単になるので、検討に値すると思います。あとは、新しいツールを試す絶好の機会にもなります。 また、実装には、以下のライブラリを参考にしました。 react-paypal-js react-google-recaptcha-v3 react-stripe-js プロジェクト構成 さて、実装したライブラリは、外部SDKをロードして利用するラッパーであり、ReactのI/Fに準拠しているが単体では動作しないプラグインと呼ぶようなものです。 以下に package.json の内容を抜粋します。 // package.json { " name ": " @baseinc/react-payid-js ", " version ": " 0.1.0 ", " main ": " dist/index.js ", " types ": " dist/types/index.d.ts ", " publishConfig ": { " registry ": " https://npm.pkg.github.com/ " } , " files ": [ " dist ", " README.md ", " CHANGELOG.md " ] , " scripts ": { " bundle ": " spack ", " build ": " yarn bundle ", " test ": " jest ", " type:check ": " tsc --noEmit ", " type:declarations ": " tsc --emitDeclarationOnly --outDir dist/types ", " validate ": " concurrently yarn:format:check yarn:type:check yarn:lint yarn:test yarn:build ", " sync ": " concurrently yarn:build yarn:type:declarations ", " prepack ": " yarn clean && yarn sync ", " clean ": " rimraf dist " } , " devDependencies ": { " @swc/cli ": " ^0.1.57 ", " @swc/core ": " ^1.2.203 ", " @swc/jest ": " ^0.2.21 ", " @testing-library/react ": " ^13.3.0 ", " @types/react ": " ^18.0.14 ", " @types/react-dom ": " ^18.0.5 ",, " concurrently ": " ^7.2.2 ", " jest ": " ^28.1.1 ", " jest-environment-jsdom ": " ^28.1.1 ", " jest-mock-extended ": " ^2.0.6 ", " react ": " ^18.2.0 ", " react-dom ": " ^18.2.0 ", " rimraf ": " ^3.0.2 ", " typescript ": " ^4.7.3 " } , " peerDependencies ": { " react ": " >=17.0.0 ", " react-dom ": " >=17.0.0 " } } peerDependencies で、ホストとなるアプリケーションなどが利用を想定されるReactの最低バージョンを指定 開発時やテスト実行時にも Reactが必要となるので、 devDependencies でも明示的に指定 TypeScriptのコンパイルとbundleは、SWCで行う TypeScriptの型チェックと型定義ファイルの出力は、 tsc で行う GitHub Packagesで、privateな npm packageとして利用できるように SWCについて SWCは、最新機能を使ったJavaScript、TypeScriptファイルをブラウザ互換のJavaScriptに変換することができます。 Next.js や Deno でも使われています。 設定 は、 .swcrc に記述します。 Babelの置き換え も目標としているようなところもあり、設定にはそれと同等の設定も見られます。 また、 spack(swcpack) という複数のJavaScript、TypeScriptファイルを一つにbundleできる機能があります。 rollup.js や webpack を利用したことがあるという人は、それらと似たようなツールだと考えてもらえればいいです。 bundleの実行 bundleツールの設定 は、 spack.config.js というファイルに、 .swcrc に追加するような設定とあわせて記述できますが、開発途中だからなのか、コンパイル周りの設定のいくつかは、 .swcrc ファイルに追加しなければならないという罠があります。 設定ファイルは以下の通りです。 // spack.config.js const { config } = require( '@swc/core/spack' ); module.exports = config( { entry: { web: __dirname + '/src/index.ts' , } , output: { path: __dirname + '/dist' , name: 'index.js' , } , module: {} , externalModules: [ 'react' , 'react-dom' ] , // [...snip...] } ); react や react-dom は、ライブラリを利用するホスト側のアプリケーションなどで解決するため、Bundleに含める必要がないので、 externalModules で除外します。 rollup.jsのexternal と同じような指定方法ですね。 spack.config.js のあるディレクトリで、 spack コマンドを実行すると、 output に指定したディレクトリにbundleファイルが出力されます。 $ spack Bundling done: 0s 138.030983ms Done: 0s 0.351072ms ✨ Done in 0.49s. Bundleツール利用時のコンパイル設定 さてこの spack は、 .swcrc に追加するようなコンパイル周りの設定も options に追加することができます。 これによりJavaScriptへの変換やminify、難読化も行うことができます。 // spack.config.js const { config } = require( '@swc/core/spack' ); module.exports = config( { // [...snip...] options: { minify: true , jsc: { target: 'es5' , minify: { compress: true , mangle: { keepFnNames: true , } , } , parser: { syntax: 'typescript' , tsx: true , decorators: true , dynamicImport: true , } , transform: { legacyDecorator: true , decoratorMetadata: true , react: { runtime: 'automatic' , useBuiltins: true , } , } , } , } , } ); しかし、未実装な箇所があるのか一部の設定は追加しても動きません。そこで、 .swcrc を併用するわけなのですが、デフォルトだと暗黙的にこれを利用してくれます。 利用してくれるのであれば、 .swcrc に全て定義すればいいのでは? と思うかもしれませんが、JSON形式で設定を扱うと記述ミスが怖いので、できるだけJavaScript形式の設定ファイルを使いたいです。 以下に、 .swcrc の設定内容を載せます。 // .swcrc { " exclude ": [ " .*.test.tsx? ", " .*.js$ " ] , " module ": { " type ": " commonjs ", " lazy ": true } , " jsc ": { " transform ": { " optimizer ": { " globals ": { " vars ": { " ENTRYPOINT_FUNCTION_NAME ": " '__payid%example%v1__' " , } } } } } } exclude は、適用しないファイルのリストを設定します。ソースコードはTypeScriptなので、JavaScriptの設定ファイルやテストコードのファイルを除外しています。 コンパイル時にコードを置き換える jsc.transform.optimizer.globals も、 spack.config.js に指定しても動かない設定の一つで、コンパイル時に指定した変数を置き換えることが可能です。 WebpackのDefinePlugin と同じようなことができます。 // .swcrc { // [...snip...] " jsc ": { " transform ": { " optimizer ": { " globals ": { " vars ": { " ENTRYPOINT_FUNCTION_NAME ": " '__payid%example%v1__' " , } } } } } } 今回利用するSDKは、使うのに用意されたグローバル関数を実行するのですが、今後後方互換性のないアップデートなどが発生した場合、関数名が変更されうるなど、名前が可変なため、コンパイル時に指定の名前に置き換えるという処理に使いました。 ソースコードで以下の記述があったとして、 declare const ENTRYPOINT_FUNCTION_NAME: string ; // [...snip...] return window [ ENTRYPOINT_FUNCTION_NAME ] ; swcでコンパイルすると、以下のように変換されます。 return window [ '__payid%example%v1__' ] ; コンパイル、Bundle設定の調整 また、Bundleファイルが動作するかは、リポジトリに別ディレクトリを切って、最小限のサンプルアプリケーションを作って確認しました。 今回の想定環境は、Next.jsアプリケーションで、Reactのバージョンがv17以上なので、以下のようなプロジェクトの設定を用意しました。 { " name ": " next ", " version ": " 0.1.0 ", " private ": true , " scripts ": { " dev ": " next dev ", " build ": " next build ", " start ": " next start ", " lint ": " next lint ", " dev:sync ": " cd ../../; yarn sync " } , " dependencies ": { " @baseinc/react-payid-js ": " latest ", " jose ": " ^4.8.1 ", " next ": " 12.1.6 ", " react ": " 17.0.2 ", " react-dom ": " 17.0.2 " } , " devDependencies ": { " @types/node ": " 18.0.0 ", " @types/react ": " ^17.0.0 ", " @types/react-dom ": " ^17.0.0 ", " eslint ": " 8.18.0 ", " eslint-config-next ": " 12.1.6 ", " typescript ": " 4.7.4 " } } commonjs形式にして難読化などもするため、これで、Bundleファイルで提供するライブラリが正しく動作するかを確認して、コンパイル設定を試行錯誤していきました。 型定義ファイルの出力 TypeScriptでコーディングするアプリケーションに組み込んでもらうのに、コード補完や型チェックに利用する型定義ファイル(.d.ts)を用意した方が便利です。 それには、 tsc コマンドを使って出力します。 $ tsc --emitDeclarationOnly --outDir dist/types ✨ Done in 3.65s. テスト 外部SDKをロードして利用するラッパーなので、スクリプトのロード管理まわりの動作確認は、自動化しておきたいところです。 テスティングフレームワークとして、 Jest を使うことにしました。 また、ソースコードのコンパイルとバンドルに swc を利用しているため、TypeScriptで書いたテストコードの変換に @swc/jest を利用することにしました。 Jest transformerとして、 @swc/jest を指定するのですが、swcの設定を jest.config.js に記述することができます。 bundleツールの spack(swcpack) と異なり、設定は全てJestの設定にまとめることが可能です。 また、 .swcrc がある場合、暗黙的に利用するため、使わないように設定すると、テスト用のswc設定をまとめられます。 /** @type {import('@jest/types').Config.InitialOptions} */ module.exports = { testEnvironment: 'jest-environment-jsdom' , transform: { '^.+ \\ .(t|j)sx?$' : [ '@swc/jest' , { swcrc: false , sourceMaps: true , jsc: { parser: { syntax: 'typescript' , tsx: true , } , transform: { hidden: { jest: true , } , react: { runtime: 'automatic' , } , optimizer: { globals: { vars: { ENTRYPOINT_FUNCTION_NAME: "'__payid%js%test__'" , } , } , } , } , } , } , ] , } , } ; パッケージ公開・管理 作ったライブラリを簡単に導入するためには、npm標準のパッケージ管理システムの仕組みで扱えると便利です。ソースコード管理にGitHubリポジトリを利用しているため、簡単に統合できる GitHub Packages を利用することにしました。 というのも、GitHub Packagesへの公開は、GitHub Tokenを利用したり、リポジトリの権限と可視性(public/private)を継承できるし、コミットのプッシュやデフォルトブランチのマージをトリガーに、GitHub Actionsを使って自動化が簡単にできます。 パッケージの公開の準備やリリースのような定型作業の自動化というのは、さまざまなツールが公開されています。 今回は、CHANGELOG、GitHubリリースの作成、バージョン番号の更新を自動化できる Release Please とそのGitHub Actionである Release Please Action を利用しました。 なお、パッケージの公開自体は、このツールで直接行うという訳ではなく、ツールで作成されたGitHubリリース作成をトリガーとして利用します。 Release Pleaseについて Release Please を使うと、以下の作業を自動化できます。 CHANGELOGの生成 GitHubリリースの作成(とそれに紐づくコミットへのGit Tagの作成) プロジェクトのバージョン番号更新 これらの作業をリリースPRと呼ばれるPull Requestを作成し、それをマージすることによってリリースが実行されます。 デフォルトブランチにマージされたら自動リリースするのと異なり、リリースPRを挟むことによって、事前に変更内容やリリースノートを確認でき、自分のタイミングでリリースすることができます。 また、PRにラベルをつけ、そのリリースがどの状態かを記録しています。 リリースPRは、 fix: 、 feat: といった Conventional Commits 対応したメッセージが含まれるコミットが、マージされると作成されます。 feat: エラーコードの追加 のようなメッセージで、コミットしマージすると、CHANGELOGへの変更内容の追記とバージョン番号の更新がされます。CHANGELOGの追記内容は、コミットメッセージから自動生成されます。また、バージョン番号は、たとえば、 feat: というプレフィックスを含むと、セマンティックバージョンのマイナーバージョンが、インクリメントされるという仕組みです。プレフィックスによって、どこがインクリメントされるかは異なります。 package.json { "name": "@baseinc/react-payid-js", - "version": "0.4.0", + "version": "0.5.0", "main": "dist/index.js", そして、PRをマージすると、GitHubリリースが作られるという仕組みです。 Release Please Actionを使ったパッケージ公開 前述のRelease Pleaseを用いてリリース作業を自動化するためのActionが、 Release Please Action です。 これを使うと、リリースPRとGitHubリリース作成を行う本来の処理の他に、GitHubリリース作成をトリガーにGitHub Packagesへの公開を行うことができます。 それには、Action実行後のoutputsを利用します。 release_created という変数にGitHubリリースを作成したかがboolean値で格納されています。 release_created=true であれば、GitHub Actionのjobで、コマンドを使って公開を行います。 name : release on : push : branches : - main jobs : release-please : runs-on : ubuntu-latest steps : - uses : google-github-actions/release-please-action@v3 id : release with : release-type : node - uses : actions/checkout@v2 if : ${{ steps.release.outputs.release_created }} - uses : actions/setup-node@v1 with : node-version : "16.x" if : ${{ steps.release.outputs.release_created }} - name : Resolve dependencies run : yarn install --frozen-lockfile if : ${{ steps.release.outputs.release_created }} - name : Configure git user run : | git config --global user.email ${{ github.actor }}@users.noreply.github.com git config --global user.name ${{ github.actor }} if : ${{ steps.release.outputs.release_created }} - name : Set GitHub packages env : GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} run : | npm config set //npm.pkg.github.com/:_authToken=$GITHUB_TOKEN if : ${{ steps.release.outputs.release_created }} - run : yarn publish env : NODE_AUTH_TOKEN : ${{ secrets.GITHUB_TOKEN }} if : ${{ steps.release.outputs.release_created }} 最後に、パッケージには、bundleファイルや型定義ファイルを含めたいため、 publish時に動作するnpm script を使って、bundleファイルと型定義ファイルの出力を実行します。 prepack というスクリプトで実行します。 // package.json { " name ": " @baseinc/react-payid-js ", " version ": " 0.1.0 ", " main ": " dist/index.js ", " types ": " dist/types/index.d.ts ", " publishConfig ": { " registry ": " https://npm.pkg.github.com/ " } , " files ": [ " dist ", " README.md ", " CHANGELOG.md " ] , " scripts ": { " bundle ": " spack ", " build ": " yarn bundle ", " type:declarations ": " tsc --emitDeclarationOnly --outDir dist/types ", " sync ": " concurrently yarn:build yarn:type:declarations ", " prepack ": " yarn clean && yarn sync ", " clean ": " rimraf dist " } , // [...snip...] } Actionにて、GitHub Packagesに公開されたパッケージは、リポジトリから参照することができます。 おわりに SWCとRelease Pleaseを利用したReact TypeScriptライブラリ開発と公開の事例を紹介しました。 アプリケーション上で新しい技術を導入するのに躊躇われるケースでも、特定の機能を別途ライブラリ化することによって、小さく始められるので、何かアプリケーションから分離できそうな機能がある場合は、検討してみてはいかかでしょうか?
アバター
Platformグループ でマネージャーをしている 松田( @tadamatu ) です。 この記事に書いてあること GitHub Actions を利用し 「OpenAPI の自動バージョニング」から「API Clientのnpmパッケージ生成」までを完全自動化 したのですが、その際に ハマったこと、工夫したこと が結構あったので、シェアしておきたいと思い書かせていただいた記事になります。 具体的には以下のような内容について書いてあります。 Branch protection rulesを維持した状態で、workflowからだけはcommitをさせたい(bypass機能を利用) → 文中の(3-2) 別ブランチの GitHub packages に npm publish したい(通常は何もしなければGitHub Actionsからは同じリポジトリのGitHub packagesにしか npm publish できない)→ 文中の(2) Branch protection rulesを維持した状態で、PRが生成されたときに Auto Merge されるようにしたい → 文中の(4) 特にbypass機能に関しては、 GitHub Community で出されていたリクエストが今年の5月に利用できるようになったようで、早速利用してみたものになります。 github.com github.blog そもそも何を解決したかったのか(目的) PlatformグループではBASE全体のリアーキテクチャを進めているのですが、その課題の1つに BackendとFrontendの分離 というものがあります。 現在はOpenAPIを採用しているのですが、 WebAPIスキーマの管理とClientの生成フロー を適切にしたいと言うのが目的でした。 ※これまではBackendとFrontendが同じリポジトリ内にあったため、リポジトリ内部でよしなに生成し融通を利かせて開発を行っていたのですが、物理的にリポジトリが別れるとそれが難しくなるためです。 最終的には以下のような構造とし、期待値は以下のようなものです。 スキーマ定義ファイル openapi.yaml はBackend側のリポジトリにて管理をする WebAPIスキーマ自体はFrontend・Backend開発者の合意で決定されるが、Backendの開発者が素案を作る文化が強いため、それに合わせてチューニングした仕組みとした (もちろんモックサーバをたててFrontend開発したりする場合もある) openapi.yaml を修正すると、GitHub Actionsを通してClientコードを生成しnpmパッケージとして自動リリースする フロントエンド開発者は npm install で簡単にClientコードを取得できる ほとんどはmasterへのpushトリガーで処理されるが、Clientコードが生成されるまでフロントエンド開発者が開発することができないため、手動で開発用パッケージを生成できるようにして解決している 今回はバージョンもワークフロー内で自動採番しており作業衝突が回避できる 「 openapi.yaml のバージョン番号」と 「 npm package のバージョン番号」を一致させる これによりBackendとFrontend が同じスキーマを利用していることが明確になり、Frontendもpackage.jsonを見るだけで判断できるようになる 全体フロー 全体フローはこのような感じになっています。 のマークの付いているところが、 マニュアル操作 の部分になります。 それ以外の部分は ワークフロー(GitHub Actions) により処理が行われます。 【開発中フェーズ】 (1) スキーマ定義ファイル openapi.yaml を実装して開発ブランチ へ push (2) 開発用のnpmパッケージ出力 → (a) Generate Client for Dev openapi-generetor により openapi-client (typescript-fetch) 生成 TypeScriptトランスパイル 開発用パッケージ公開 開発中はこの開発用パッケージを利用してFrontend側の開発を行います 【本番リリース】 本番リリースのため masterブランチへの merge(push) をトリガーにGithub Actionsが実行されたあとは、以下のような処理が全て自動で処理されます。 (3) 本番リリース(merge) → (b) Update Version (3-1) 新しいバージョン番号の発行( major / minor / patch のコントロール含む) (3-2) backendリポジトリ(selfリポジトリ) の openapi.yaml を新しいバージョン番号に更新しコミット (3-3) openapi-clientリポジトリ へPR生成 package.json を新しいバージョン番号へ更新 openapi-generetor により openapi-client (typescript-fetch) 生成 (4) (c) Auto merge (PR自動マージ) 生成されたPRの自動マージ (5) (d) Build & Publish package (正式版パッケージ公開処理) TypeScriptトランスパイル 正式版パッケージ公開 新しいパッケージが生成されたことをslackへ通知 以降に各処理を詳しく書いていきます。 注意) ・以降に出てくるコードは、ブログ用にコメントを追加し、適時編集したものです。ご了承ください。 ・BASEでは適切な単位で openapi.yaml が複数存在するのですが、以降はcartをサンプルにしたものですので、適時読み替えてください。 事前準備 GitHub Actions では通常、実行開始時に GITHUB_TOKEN が発行され、 ${{ secrets.GITHUB_TOKEN }} に保存されたTokenを利用して基本的な操作をすることが可能です。 自動トークン認証 - GitHub Docs しかし、リポジトリを跨いでActionしたり、今回のようにbypass機能を利用したい場合は、 GITHUB_TOKEN ではActionできません。 リポジトリを跨いだActionなどは PAT(Personal Access Token) でも可能です。 個人用アクセス トークンを管理する - GitHub Docs しかし、PATは個人に依存してしまいますので、組織配下で作成した GitHub Apps を利用し生成したToken を利用した方が良いでしょう。 また、今回のbypass機能もGitHub Appsでないと機能しません。 GitHub Appsを作成 では、GitHub Appsを作成して、インストールしていきます。 (a) GitHub Appsを作成 Organizationページ から [Settings] - [Developer settings] - [GitHub Apps] - [New GitHub Apps] と進み... 以下のように入力し、 [Create GitHub App] クリックで GitHub Apps を作成します。 # 今回の対応で変更が必要な部分だけ記載 GitHub App name : BASE openapi client generated App Homepage URL : https://binc.jp/ (適当で大丈夫) Webhook-Active : 不要なのでoff Contents=Access : Read and Write Pull requests=Access : Read and Write (b) 作成したAppページで以下の情報を拾う 作成したAppページに表示されている 【App ID】 [Generate a private key] 押下し private key を生成 その時にダウンロードされる 【pemファイル】 (c) 必要なリポジトリにインストール 作成したAppページの左メニュー [Install App] から、今回利用するリポジトリ backendリポジトリ , openapi-clientリポジトリ で有効になるようにインストールする (d) 各リポジトリページで Sercretsを入力 [Settings] - [Sercrets] - [Actions] と移動し、(b)で取得した 【App ID】 と 【pemファイル】の内容 を設定します。 この記事では以下のように設定し、workflowから利用しています。 OPENAPI_CLIENT_GENERATED_APP_ID : 【App ID】 OPENAPI_CLIENT_GENERATED_PRIVATE_KEY : 【pemファイル】の内容 (1) スキーマ定義ファイルを実装して開発ブランチへ push この章では赤枠の部分を説明しています まずはスキーマ定義ファイル openapi.yaml をローカルで実装します。 ここは言わずもがなだと思いますので省略しますが、API仕様に合わせて以下のようなyamlファイルを修正しGitHubへpushします。 openapi-generator/samples/yaml/pet.yml at master · OpenAPITools/openapi-generator · GitHub 1つ注意が必要なのは、 このタイミングで openapi.yaml のversionを実装時に変更しない ということです。 同時に別の開発者によってスキーマ修正が行われている可能性もありますし、hotfixで急遽変更になる場合もあります。 あくまで 本番デプロイ時にバージョンが自動的に割り当てする ことで、バージョン番号の調整にいちいちコミュニケーションを必要としなくてもよい、というメリットが得られます。 今回のフローでは (3-1) でバージョン番号が発行されます。 (2) 開発用のnpmパッケージ出力 この章では赤枠の部分を説明しています (1)でpushされた openapi.yaml をもとにFrontendで開発するためには、 pushされたスキーマ定義のWebAPIクライアントパッケージをFrontendにインストールする 必要があります。 今回の場合、パッケージを生成するためには、pushされたブランチを指定し 「 (a) Generate Client for Dev」 をマニュアル実行することで 開発用のnpmパッケージファイル が生成されます。 このときバージョン番号は更新されません。 npmパッケージにはbeta版やrc版といった 任意のプレリリース識別子 をつけることができます。 また、同じプレリリース識別子でも build番号 をその後ろにつけることができます。 例) ex. 1.2.0-rc.8 (識別子= rc , build番号= 8 ) config | npm Docs 今回はdev版として「識別子= commit short_sha ,build番号= 日時 」として、いつのどの時点のpublishしたものかを理解しやすくしました。 # 例: <version>-<short_sha>.<yyyymmddhhmm> 1.2.3-de0b012.202209050312 また、公開のコマンドでは npm publish --tag dev とし、latestバージョンが移動しないようにもしています。 workflowの中で、トランスパイルやパッケージ公開をしていますが、 package.json tsconfig.json の設定に対して特殊な設定はしていないので、今回は省略します。 #### (a)Generate-Client-for-Dev #### name : "OpenAPI: Generate Client For Dev" on : workflow_dispatch : jobs : generate-openapi-client : runs-on : ubuntu-latest steps : # selfリポジトリ以外の取得に利用するため GitHub Apps の Token を取得 - name : Generate apps token id : generate_token uses : tibdex/github-app-token@v1 with : app_id : ${{ secrets.OPENAPI_CLIENT_GENERATED_APP_ID }} private_key : ${{ secrets.OPENAPI_CLIENT_GENERATED_PRIVATE_KEY }} # backendリポジトリ(selfリポジトリ) のチェックアウト (openapi.yamlのありか) - name : Checkout Current Repogitory uses : actions/checkout@v2 # openapi-client リポジトリのチェックアウト (パッケージをpublishするリポジトリ) - name : Checkout Client Repogitory uses : actions/checkout@v2 with : token : ${{ steps.generate_token.outputs.token }} repository : baseinc/openapi-client path : client # nodeセットアップ - name : Setup node uses : actions/setup-node@v1 with : node-version : 16.x registry-url : https://npm.pkg.github.com # yqセットアップ (openapi.yamlの分析に利用) - name : Setup yq uses : chrisdickinson/setup-yq@latest # 開発用パッケージバージョン作成のための情報を取得 - name : Get vars id : vars run : | echo "::set-output name=api_version::`yq r './openapi.yaml' 'info.version'`" echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" echo "::set-output name=dev_version::`date +" %Y%m%d%I%M"`" # https://openapi-generator.tech/docs/usage/#generate - name : Generate OpenAPI Client for typescript-fetch uses : openapi-generators/openapitools-generator-action@v1 with : generator : typescript-fetch generator-tag : latest command-args : | -i=openapi.yaml \ -o=client/packages/typescript-fetch/cart/src/ \ --generate-alias-as-model \ --enable-post-process-file # npm install と TypeScriptトランスパイル - name : install node_modules & build run : | cd client/packages/typescript-fetch/cart npm install npx tsc # NODE_AUTH_TOKEN には backend リポジトリの GITHUB_TOKEN を使用している # backend から openapi-client リポジトリの package へ `npm publish` することになるが、 # これをするには package setting より Manage Actions access の設定に backend を設定する必要がある - name : Publish npm package for DEV run : | VERSION=${{ steps.vars.outputs.api_version }} cd client/packages/typescript-fetch/${{ github.event.inputs.target }} npm version ${VERSION}-${{ steps.vars.outputs.sha_short }}.${{ steps.vars.outputs.dev_version }} npm publish --tag dev env : NODE_AUTH_TOKEN : ${{ secrets.GITHUB_TOKEN }} ここでのはまりポイントとしては、BASEではpackageの管理をGitHub pakages で行っているのですが、 実行されるworkflowがbakendリポジトリ なのに対し、 publish先が openapi-clientリポジトリ となっており、通常は単純にpublishはできません。(これはTokenに PAT や GitHub Apps を利用してもダメでした) これをするためには、 GitHub pakages のリポジトリ (今回の場合はopenapi-clientリポジトリ)に、 Manage Actions access という設定がありますので、そこで backendリポジトリ からの書き込みを許可します。 こうすることで、 backendリポジトリの GITHUB_TOKEN で publishすることが可能 になります。 (3) 本番リリース この章では赤枠の部分を説明しています openapi.yaml が確定し、開発が完了すると本番にリリース(masterブランチにmerge)をします。 merge(pushトリガー)により 「 (b) Update Version」 が実行されて (3-1)(3-2)(3-3) が処理されます。 (3-1) 新しいバージョン番号の発行 この章では赤枠の部分を説明しています ここでは 新しいバージョン番号の発行 を行います。 特別な処理がなければ、 通常はpatchバージョンを更新 します。 minor, major バージョンを更新 するためには、 merge対象のcommit comment のprefix に [openapi/major] [openapi/minor] を入れておく と該当のバージョンを更新してくれます。 複数ある場合は、一番直近のprefixが有効になります。 #### (b)Update-Version-<1> #### name : "OpenAPI: [Cart] Update Version" on : push : branches : - master paths : - "openapi.yaml" # 複数回連続実行された場合、直列に実行されて、最後のワークフローが有効になるようにconcurrencyを設定 concurrency : ${{ github.workflow }} jobs : update-openapi-version-cart : runs-on : ubuntu-latest # selfリポジトリ に対してコミットを追加するため、対象のコミットコメントの場合、workflowは無視する if : ${{ !startsWith(github.event.head_commit.message , 'chore(release):' ) }} steps : # backendリポジトリをチェックアウト - name : Checkout Current Repogitory uses : actions/checkout@v2 # nodeセットアップ - name : Setup node uses : actions/setup-node@v1 with : node-version : 16.x registry-url : https://npm.pkg.github.com # yqセットアップ (openapi.yamlの分析に利用) - name : Setup yq uses : chrisdickinson/setup-yq@latest # 現在のバージョン番号を取得(例. current_version = 1.2.3) - name : Get vars id : vars run : | echo "::set-output name=current_version::`yq r openapi.yaml 'info.version'`" # 基本はpatchバージョンが更新されます # commit comment を遡り、prefix に特定の文字列があれば minor や major バージョンを更新 - name : Get version type uses : actions/github-script@v6 id : version-type with : script : | const commits = ${{ toJSON(github.event.commits) }} for (const commit of commits.reverse()) { if (commit.message.startsWith(' [ openapi/major ] ')) return ' major' if (commit.message.startsWith(' [ openapi/minor ] ')) return ' minor' if (commit.message.startsWith(' [ openapi/patch ] ')) return ' patch' } return 'patch' result-encoding : string # patchバージョンを更新(例. 1.2.3 → 1.2.4) - name : Version Up (patch) if : steps.version-type.outputs.result == 'patch' run : | VERSION=${{ steps.vars.outputs.current_version }} && a=( ${VERSION//./ } ) && a[ 2 ]=$((a[ 2 ] + 1)) echo "new_version=${a[0]}.${a[1]}.${a[2]}" >> $GITHUB_ENV # minorバージョンを更新(例. 1.2.3 → 1.3.0) - name : Version Up (minor) if : steps.version-type.outputs.result == 'minor' run : | VERSION=${{ steps.vars.outputs.current_version }} && a=( ${VERSION//./ } ) && a[ 1 ]=$((a[ 1 ] + 1)) echo "new_version=${a[0]}.${a[1]}.0" >> $GITHUB_ENV # majorバージョンを更新(例. 1.2.3 → 2.0.0) - name : Version Up (major) if : steps.version-type.outputs.result == 'major' run : | VERSION=${{ steps.vars.outputs.current_version }} && a=( ${VERSION//./ } ) && a[ 0 ]=$((a[ 0 ] + 1)) echo "new_version=${a[0]}.0.0" >> $GITHUB_ENV # (b)Update-Version-<2> に続く ==> (3-2) backendブランチ(selfブランチ)の openapi.yaml を新しいバージョン番号に更新しコミット(bypass機能を利用) この章では赤枠の部分を説明しています (3-1)で発行されたバージョン番号を、 backendブランチ(selfブランチ)の openapi.yaml へ更新する(書き戻す) 必要があります。 しかし、 ここで問題になるのは Branch protection です。「masterブランチへの直接push禁止」「1人以上Approveしていないとマージできない」などといったあれです。 workflowで何も気にせずにコミットをしても、通常はこのBranch protectionに弾かれてエラーになってしまいます。 しかし bypass機能 を利用することで、 特定のGitHub Apps からの命令をbypassして実行させる ことができます。(つまり、Branch protectionをスルーしてコミットができるようになります) 設定は簡単で、GitHub Apps を設定してあれば、bypassさせたいリポジトリ(今回の場合はbackendリポジトリ)に設定するだけです。 [Settings] - [Branchs] - [Branch protection rules] と進み、 Protect matching branches 内の Allow specified actors to bypass required pull requests をONにし、該当する GitHub Apps を追加します。 これにより、 人による誤操作はBranch protectionによって守りつつ、該当する GitHub Apps のワークフローでだけ実行したいアクションができる ようになりました。 以下のワークフローで実際に利用しています。 #### (b)Update-Version-<2> #### # ==> (b)Update-Version-<1> の続き # openapi.yaml のバージョンを上書きする - name : OpenAPI Versioning run : | sed -i -e "s/version: ${{ steps.vars.outputs.current_version }}/version: ${{ env.new_version }}/" openapi.yaml # GitHub Apps は branch protection を bypass する設定することで、commit を master に直pushすることができる # まずは GitHub Apps のtoken を取得 - name : Generate apps token id : generate_token uses : tibdex/github-app-token@v1 with : app_id : ${{ secrets.OPENAPI_CLIENT_GENERATED_APP_ID }} private_key : ${{ secrets.OPENAPI_CLIENT_GENERATED_PRIVATE_KEY }} # 次に backendリポジトリ(selfリポジトリ) へ commit - name : Commit New Version to baseinc/backend repo uses : stefanzweifel/git-auto-commit-action@v4 with : commit_message : "chore(release): openapi.yaml update version from ${{ steps.vars.outputs.current_version }} to ${{ env.new_version }}" github-token : ${{ steps.generate_token.outputs.token }} # (b)Update-Version-<3> に続く ==> (3-3) openapi-clientリポジトリへPR自動生成 この章では赤枠の部分を説明しています 以下のワークフローでは、 openapi-clientリポジトリに対し、新しいスキーマ定義のクライアントのPRを自動生成 します。 同時に、(3-1)で発行されたバージョン番号を、packageにも反映させています。 #### (b)Update-Version-<3> #### # ==> (b)Update-Version-<2> の続き # PR生成のために、openapi-client をチェックアウト - name : Checkout Client Repogitory uses : actions/checkout@v2 with : token : ${{ steps.generate_token.outputs.token }} repository : baseinc/openapi-client path : client # https://openapi-generator.tech/docs/usage/#generate - name : OpenAPI Generate typescript-fetch uses : openapi-generators/openapitools-generator-action@v1 with : generator : typescript-fetch generator-tag : latest command-args : | -i=openapi.yaml \ -o=client/packages/typescript-fetch/cart/src/ \ --generate-alias-as-model \ --enable-post-process-file # openapi.yaml で更新したバージョン番号と同じものを、package.json に対して適用する - name : Update release version run : | cd client/packages/typescript-fetch/cart npm version ${{ env.new_version }} # PRを生成する # [automerge] というprefixをcommit commentにつけることで、次の工程(4)で自動マージが発火する - name : Create pull request to baseinc/openapi-client repo uses : peter-evans/create-pull-request@v4 with : token : ${{ steps.generate_token.outputs.token }} path : client delete-branch : true commit-message : "chore(release): npm package update version from ${{ steps.vars.outputs.current_version }} to ${{ env.new_version }}" title : "[automerge] chore(release): npm package update version from ${{ steps.vars.outputs.current_version }} to ${{ env.new_version }}" body : | openapi-typescript-fetch-cart : ${{ env.new_version }} branch : chore/openapi-client-cart branch-suffix : short-commit-hash # 同じプルリクは作らない (4) PRの作成によりAuto Mergeされる この章では赤枠の部分を説明しています openapi-clientリポジトリ側でPRが生成されたら、以下のworkflowが発火します。 このworkflowでは [automerge] というprefix がついているときだけ、自動マージが行われるようになっています。 AutoMerge には、GitHubのAutoMerge機能を利用しています。 AutoMerge機能 は [Settings] - [General] の Allow auto-merge をONにすることで、利用ができます。 これを利用する利点は、全てのcheckが通った時にだけ発火することです。たとえば万が一CIでエラーになれば、MergeされずにPRは残ることになります。 Branch protection には「マージするには1人以上のApproveが必要」が設定してあり、workflow内でApproveをするようにしてあります。 これにより、 人による誤操作はBranch protectionによって守りつつ、workflowによる自動マージだけが動作する ようにしてあります。 #### (c)Auto-Merge #### name : Auto Merge on : pull_request : types : - opened branches : - main jobs : release : name : Auto Merge runs-on : ubuntu-latest # `[automerge]` というprefixがある場合にだけこの workflow が発動する if : ${{ startsWith(github.event.pull_request.title, '[automerge]' ) }} steps : # GitHub Apps により該当PRの GitHub AutoMerge を ON にする - name : Generate apps token id : generate_token uses : tibdex/github-app-token@v1 with : app_id : ${{ secrets.OPENAPI_CLIENT_GENERATED_APP_ID }} private_key : ${{ secrets.OPENAPI_CLIENT_GENERATED_PRIVATE_KEY }} - name : Enable Pull Request Automerge uses : peter-evans/enable-pull-request-automerge@v2 with : token : ${{ steps.generate_token.outputs.token }} pull-request-number : ${{ github.event.number }} # AutoMerge 条件(つまり branch protection)は「1人以上のApproveが必要」であるため、 # Approve する → これにより GitHub Auto Merge が発動 - name : Approve uses : hmarr/auto-approve-action@v2 with : pull-request-number : ${{ github.event.number }} (5) openapi-clientを生成し、npmパッケージを生成 この章では赤枠の部分を説明しています 最後の工程として、(4)で自動マージされると、以下のworkflowがpushトリガーにより発火します。 ここでは、 TypeScriptトランスパイルをした後、パッケージとして公開する といった作業をしているだけになります。 #### (d)Build-&-Publish-package #### name : Publish packages on : push : branches : - main jobs : release : name : Build and Publish package runs-on : ubuntu-latest steps : # openapi-clientリポジトリ をチェックアウト - name : checkout uses : actions/checkout@v2 # nodeセットアップ - name : setup node uses : actions/setup-node@v1 with : node-version : 16.x registry-url : https://npm.pkg.github.com # npm install と TypeScriptトランスパイルをした後、パッケージとして公開 - name : build & publish package (cart) id : publish_package_cart run : | cd packages/typescript-fetch/cart npm install npx tsc npm publish ./ echo ::set-output name=version::$(npm version --json | jq '."@baseinc/openapi-typescript-fetch-cart"' ) env : NODE_AUTH_TOKEN : ${{ secrets.GITHUB_TOKEN }} # 生成完了をslackに通知 - name : Success Slack Notification if : contains(steps.changed-files.outputs.all_changed_files, 'packages/typescript-fetch/cart/package.json' ) uses : rtCamp/action-slack-notify@v2 env : SLACK_MESSAGE : "新しい openapi-typescript-fetch-cart: ${{ steps.publish_package_cart.outputs.version }} が生成されました" SLACK_TITLE : "openapi-client 生成通知" SLACK_USERNAME : "openapi-client-generated-bot" SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK_CART }} まとめ 今回は、アーキテクチャ変更に合わせて、 OpenAPI Client 生成の自動化した話 を、ワークフローを中心に書いてきました。 GitHub Actions は本当に良くできている と思います。 こんなことできないかなと Marketplace を検索してみると、大体のものは見つかったりします。 これからも 作業効率を上げるため に、また 人による誤操作をできるだけ減らす 意味も含め、GitHub Actions を使い倒していきたいと考えています。 長文を最後までお読みいただき、ありがとうございました。 最後に 私が所属する Platformグループ は、今年の7月に新設されたばかりのグループで、 仲間を絶賛募集中です! Platformグループでは、 「オーナーやカスタマーの良い体験、エンジニアの開発体験をプラットフォームで守る」というビジョン のもと、リアーキテクチャをメインに、作業効率化やパフォーマンス改善、組織との親和性について考えるといった作業まで、様々な改善を行っています。 もしご興味あれば、カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください。 open.talentio.com open.talentio.com
アバター