Upgrade to Pro — share decks privately, control downloads, hide ads and more …

カートリプレースPJの全体像と技術選定

Satoshi Ohki
December 16, 2021

 カートリプレースPJの全体像と技術選定

BASE Tech Talk #1 〜Next.jsを使ったカート大規模リプレイスPJの裏側〜

Satoshi Ohki

December 16, 2021
Tweet

More Decks by Satoshi Ohki

Other Decks in Programming

Transcript

  1. © 2012-2021 BASE, Inc. 2 自己紹介 2 大木 聡 :@roothybrid7

    • BASE, Inc. Product Dev Division > Tech Lead • BASE入社直後は、iOSアプリ開発に従事しており、 今はこのカートPJのフロントエンド開発などに従事
  2. © 2012-2021 BASE, Inc. 3 今回の話 • 購入フローの短縮 • 追加購入導線の設置

    • 購入商品のSNSシェアを促す https://baseu.jp/22660 先日、カートをリニューアルしました。 3
  3. © 2012-2021 BASE, Inc. 6 従来のアーキテクチャ(モノリシック) • コードベースの肥大化 • 見えない依存関係がいくつも生まれる

    • 段々アプリケーションが複雑化 最初は、素早くリリースできて良かった が。。。どんどん機能が詰め込まれていった。 • 市場価値の検証 • 利益を生み出せる最小限のもの カート機能を含む1つのアプリケーション 6 CakePHP 2 UI Business Logic Data Access Cart Author/Copyright holder: Henrik Kniberg. Copyright terms and licence: All rights reserved
  4. © 2012-2021 BASE, Inc. 7 モノリスから機能を剥がす 7 UI CakePHP 2

    Data Access Business Logic CakePHP 4 Next カート機能を切り離して、新システムを構築 UI Business Logic Data Access Cart Frontend Backend OpenAPI
  5. © 2012-2021 BASE, Inc. 8 モノリスから機能を剥がす 8 1 2 3

    旧システムへは機能追加停止 新システムへ切り離す フロントエンドも分離
  6. © 2012-2021 BASE, Inc. 10 モノリスから機能を剥がす UI CakePHP 2 Data

    Access Business Logic CakePHP 4 Next カート機能を切り離して、新システムを構築 UI Business Logic Data Access Cart Frontend Backend OpenAPI 新システムへ切り離す 機能を別システムに切り離すことにより、コンテキストが明確になり、実装のしがらみが減 り、変更が用意になる。 変更や問題特定の容易さ > 実装難易度、実装量増大
  7. © 2012-2021 BASE, Inc. 11 モノリスから機能を剥がす フロントエンドアプリケーションとして、独立 してビルド、デプロイをすることが可能に。 バックエンドからプレゼンテーション層を分離 UI

    Data Access Business Logic CakePHP 4 Next Frontend Backend OpenAPI APIを介してのみデータの参照・更新が可 能 -> UIの制御にBusiness Logicの混入、不 用意なデータアクセスがなくなる
  8. © 2012-2021 BASE, Inc. 13 フロントエンドCI・デプロイ周り Developer Push GitHub Actions

    Unit Testing/ Linting cart locales Push GitHub Actions POEditor Editor 翻訳テキスト編集 Pull& Push Merge Publish npm GitHub packages 開発時のフロー
  9. © 2012-2021 BASE, Inc. 14 フロントエンドCI・デプロイ周り Deployer 手動実行 GitHub Actions

    cart GitHub packages デプロイフロー ECS Deploy Build 最新のLocale パッケージ取得 最新のLocale パッケージイン ストール
  10. © 2012-2021 BASE, Inc. 15 フロントエンド構成 UIの描画と振る舞いの定義、バックエンドとのリソースのやりとり + ページの配信(SSR) Backend

    Server Backend for Frontend(BFF) Browser フロントエンドでのみ利用する データを保存 API API SSR/API
  11. © 2012-2021 BASE, Inc. 16 Next.js採用してよかった点 1 2 3 全部JavaScript/TypeScriptで実装でき、ページの配信も簡単

    > PHP/JSでViewが分断とかPHPの変数をJSへ埋め込むとかがない ビルドやBundleのための環境構築を一から作らなくても良い > 難しいWebpackの設定やJS Bundleの作成・埋め込みを自分でやらなくて良い (Great defaults for production) VercelやGoogleの研究成果をいち早く利用できる > Web技術の叡智が詰まったReactコンポーネントのリリース (Building on powerful React abstractions) ※Great defaults for productionやBuilding on powerful React abstractionsは、Next Conf 2021 Keynoteでの言葉
  12. © 2012-2021 BASE, Inc. 17 Next.js採用してよかった点 3 VercelやGoogleの研究成果をいち早く利用できる > Web技術の叡智が詰まったReactコンポーネントのリリース

    (Building on powerful React abstractions) HTML Tag React Component 内容 <a /> <Link /> prefetchやClient-sideでの遷移 <img /> <Image /> 自動リサイズ、ローディング中のblur表示 <script /> <Script /> ローディング戦略設定やパフォーマンス改善
  13. © 2012-2021 BASE, Inc. 18 Next.js Server Side Rendering •

    閲覧メインではなく、更新処理が多くあり、購入ごとに内容が異なる ◦ Static Site Generationで生成できるものがほとんどない • API Tokenなどの保存・復元は、ブラウザ側ではなくサーバ側で行う ◦ JavaScriptで、Cookieに保存し、 document.cookie経由で取得してほしくない • 販売不可状態やメンテナンス状態を事前に検出 ◦ 販売不可な場合に、レンダリングするのは無駄なので別ページへリダイレクトするなど
  14. © 2012-2021 BASE, Inc. 19 Next.js その他 • パフォーマンス改善などバージョンアップ時の恩恵を受けられるように対応 a.

    Nextの機能をなるべく利用 b. データレイヤーなど、狭義のWeb UI以外のロジックを他のテクノロジーで管理し、 React Componentsを薄く保ち、a. を行えるようにする ▪ Open API, XState, Recoil
  15. © 2012-2021 BASE, Inc. 20 Next.js その他 とはいえ、ReactコンポーネントがWebアプリ ケーション制御の中心になっていることが多 い。

    そのため、Reactとその他ライブラリの間を繋 ぐGlueコードやReact Bindingsライブラリが 必要なこともある。 • Recoilの状態同期 • React NativeのNative API実行コンポーネ ント > Reactコンカレントモードサポート以降どう なるか Recoilの状態同期の処理コード
  16. © 2012-2021 BASE, Inc. 21 Next.js その他 • 国際化対応 ◦

    i18n Routing ▪ v10からサポート ▪ 言語検出とURLハンドリング ◦ i18n Translation ▪ 翻訳テキストの取得と適用 ▪ 他のライブラリで賄う(next-translateなど) https://shop.thebase.in/shops/shop/checkout/edit https://shop.thebase.in/en/shops/shop/checkout/edit
  17. © 2012-2021 BASE, Inc. 22 CSS • styled-jsxを採用 ◦ Reactコンポーネント毎にCSSを記述できれば十分

    ◦ 共通のスタイルはCSS Modulesを併用 • PostCSSも併用 ◦ 必要な機能を選んで組み合わせられる ◦ CSSの学習コストのみでいい CSS Moduleでimport PostCSS 必要な機能を選ぶ
  18. © 2012-2021 BASE, Inc. 23 CSS設計 • RSCSSを採用 • 3つの基本概念がありルールが少ない

    ◦ Components ◦ Elements ◦ Variants • ComponentsはReactコンポーネントと1:1に対応づけられる • ElementsはComponents内部の要素のこと • Variantsはクラス名をハイフンで始め、ComponentsやElementsのバリエーションを規 定する
  19. © 2012-2021 BASE, Inc. 25 フォーム周り • 注文内容を更新するのに中心的な要素 ◦ 複雑なバリデーションロジックを制御する必要がある

    ▪ ✋禁則文字、全角カナ • 制御するの難しい ◦ input要素などの値は、文字情報として取得されてしまう(数値ならnumberで扱いたい) ▪ 型付けされていない値 ◦ 組み込みの機能は、ブラウザによって使える使えないがある ◦ 大きなフォームのバリデーション実行制御難しい ▪ いつの時点で実行する? 個別要素の制御は? 入力変更時? blur時? submit時?
  20. © 2012-2021 BASE, Inc. 26 フォーム周り • 要素の監視、値の検証と取得が簡単にできる • いつから検証するか(change,

    blur, submit) の制御が可能 > 検証の実行タイミングの設定がどうあれ、 submit時にバリデーションを実行した時の ロジックだけを書けば良い • バリデーションエラーの表示も簡単 • 基本的なバリデーションルールも設定できる が、別の高度なバリデーションライブラリを 組み合わせることも可能 React Fook Form(フォームバリデーションライブラリ)
  21. © 2012-2021 BASE, Inc. 27 フォーム周り • ValidationSchemaを定義 • React

    Hook Formと組み合わせて利用 • 単独でも使用できる ◦ 起動時に環境変数で渡した設定値の検証 ◦ Express.jsで立てたAPIサーバのリクエス トパラメーターの検証 Superstruct
  22. © 2012-2021 BASE, Inc. 28 フォーム周り • 検証した値のデフォルト値を埋めたり、型の 変換ができる ◦

    input要素などは、基本文字列として値を 返すと思うが、要素の都合は全く興味ない ため、この機能を使って型変換している Superstructの機能(coercion)
  23. © 2012-2021 BASE, Inc. 29 フォーム周り • name属性を構造化することによって、 React Hook

    Formは、その構造通りの JavaScript Objectでフォームデータを管理 する • input要素のブラウザ毎の差異などには立ち 入りたくないので、React Hook Formが管 理するデータに対してバリデーションをかけ たい • React Hook Formの管理するデータ構造を もつValidationSchemaをSuperstructで定 義して利用 Superstruct フォームに対応する ValidationSchemaを定義
  24. © 2012-2021 BASE, Inc. 30 フォームバリデーションの仕組み submit時に構造化されたJavaScript Objectとして取得 フィールド登録と 監視、値の反映と

    取得 他のValidationライブラリで値の検証をするためのresolver 検証結果を React-hook-for mで扱える形式 へと変換 値の検証と データ変換 フィールド値と Schemaを渡す フィールド値と Schemaを受け取る 検証結果を返す React Hook Form Superstruct
  25. © 2012-2021 BASE, Inc. 31 フォームバリデーションの仕組み • SuperstructはResolverが用意されていた が、細かい部分は握り潰されていた。 ◦

    Superstructのカスタムルール未対応 • React Hook Formのエラーメッセージは、 国際化対応できない Validation Resolverのカスタマイズ
  26. © 2012-2021 BASE, Inc. 33 注文するには、他のシステムと協力が必要 ドメインモデリングして発見したモデルを元 に、フロントエンドへ公開するデータをリソー スとして定義。 注文セッションリソースがメインリソースで、

    別のリソースはこれに紐づく。 バックエンドがリソースを管理しているが、 APIにアクセスするためのTokenと各リソース のIDをフロントエンドで保存しておく必要があ る。 OpenAPIを使ってAPIエンドポイントやリソースを定義 カートリソース 商品を管理 注文内容リソース 注文するための情 報を管理 注文セッションリソース カートに商品を追加して から注文完了するまでを 追跡するリソース Token APIにアクセス するための Token文字列。 注文セッション を生成した時に 一緒に返される
  27. © 2012-2021 BASE, Inc. 34 注文するには、他のシステムと協力が必要 商品追加は、既存システムから転送 ショップ(既存システム) 商品ページ Cart

    Frontend カートページ Hyperlink カートに追加する 商品情報を転送 Cart Backend Cart API JSON API 商品情報の送信 作成したリソース やTokenの返却 新規追加 ショップ(既存システム) 商品ページ Cart Frontend カートページ カートに追加する 商品情報を転送 Cart Backend Cart API 商品情報の送信 更新リソースの返却 既存追加 既存リソースの取得と返却
  28. © 2012-2021 BASE, Inc. 35 注文するには、他のシステムと協力が必要 作成した注文データは他システムでも利用可能なように互換性を保つ Cart Frontend カートページ

    Cart Backend Cart API 注文実行 各種リソースの更新 Business Logic/ Data Access 注文データ保存 ショップ管理(既存システム) 注文管理ページ 社内管理(既存システム) 管理ページ ショップオーナーが 注文を処理 カスタマーサポートが 注文内容を確認
  29. © 2012-2021 BASE, Inc. 37 OpenAPI Spec/Generator OpenAPI定義から、仕様を参照した実装の強制や自動コード生成可能 バックエンドが別(Cart Backend)

    仕様書作成(OpenAPI Spec) API Client自動生成 バックエンドが同一(Cart Frontend) OpenAPI Generator CLI 仕様書作成(OpenAPI Spec) API Client自動生成 OpenAPI Generator CLI API Server手動実装 express-openapi 仕様自体の機能拡張 仕様を参照した機能の追加&実装の強制 import
  30. © 2012-2021 BASE, Inc. 38 OpenAPI Generator • コード生成にgenerateコマンドは使っておらず、batchコマンドを使っている ◦

    複数のAPI仕様書が存在するため ◦ コマンドライン引数を、yamlかjsonファイルで設定ファイルとして定義可能 ◦ 設定ファイルは他の設定ファイルをinclude可能 ◦ 設定ファイルをワイルドカードで指定すると、複数のAPI Clientがまとめて生成可能 # 生成コマンド openapi-generator-cli batch *.yaml
  31. © 2012-2021 BASE, Inc. 39 OpenAPI Client エラーレスポンス • 正常レスポンス時の挙動と異なる

    • エラーレスポンスが発生した場合、呼び出し側に丸投げされる(throw!!!) • カートの場合、エラーレスポンスボディに、エラーコードをセットしているため困る 400 Bad Request { “errors”: [ { “code”: “shop_can_not_sell”, “sub_code”: “shop_is_not_public” } ] } エラーレスポンス例
  32. © 2012-2021 BASE, Inc. 41 OpenAPI Generatorで生成したコードを拡張 • Middlewareの処理でResponseオブジェクトを拡張 (Proxy

    API, Object.defineProperties) ◦ メタプログラミングな様相だが仕方ない ◦ RecoilのLoadableを真似たI/Fで、レスポンスボ ティを取得 ◦ Responseオブジェクト自体にレスポンスボディを 保存し、後から取り出せるようにする
  33. © 2012-2021 BASE, Inc. 42 OpenAPI Generatorで生成したコードを拡張 • API呼び出しを関数でラップ ◦

    throwされたエラーレスポンスからレスポンスボ ディの取得 ◦ カスタムエラーでラップ ▪ カスタムエラーからエラー詳細情報を取り出せる ▪ エラーオブジェクトの名前は、RFC 7807 - Problem Details for HTTP APIs から命名
  34. © 2012-2021 BASE, Inc. 44 リソースのレンダリングと更新で要件が異なる 複数リソースを レンダリング • 複数の情報から総合的に判断する必要がある

    ◦ カートに配送不要商品が入っている >配送先入力なし ◦ テイクアウト商品が入っている >テイクアウトの受取日時を指定 • 古くなった内容では適切な判断ができないので、情報 を適度に最新にする必要がある >時間経過やリソース更新失敗時にエラーコードを見 て情報を最新にする • APIから取得した内容をそのまま描画するわけではな く、UIの表現に合わせて変換が必要 依存関係がある
  35. © 2012-2021 BASE, Inc. 45 APIリソースの定期的な取得 • データフェッチのためのReact Hooksライブラリ •

    面倒臭い非同期処理のハンドリングを隠蔽でき、 コードをシンプルに保てる • propsやstateを参照するような使用感なので、宣 言的なViewであるReactと相性がいい • optionsによって、取得タイミングを制御でき、定 期的にデータ取得可能(これもまた宣言的) ◦ interval ◦ ブラウザタブフォーカス時 ◦ コンポーネントマウント時 SWR
  36. © 2012-2021 BASE, Inc. 46 • fetcherにOpenAPI Clientを使った呼び出 し関数を指定することで、Promise制御を勝 手にやってくれる

    • keyをnullにすることで、データフェッチを 一時停止ことができる >必須のリクエストパラメーターが取得でき た場合のみリクエスト実行させることが可能 SWR const { data, error } = useSWR(key, fetcher) const { data } = useSWR( shouldFetch ? '/api/data' : null, fetcher) 条件付きフェッチ APIリソースの定期的な取得
  37. © 2012-2021 BASE, Inc. 47 • 取得したAPIリソースのデータ構造そのままレンダリングすることは少ない ◦ 何らかの変換を行うことがある ◦

    他のリソースを組み合わせ表示することもある >合計送料=商品数 x 基本送料 • リソースの更新で、リクエストパラメータとして利用することもある >配送方法の更新バージョン(取得時点のスナップショット) SWRは簡単にAPIリソースを取得できて便利だが、リソースはユースケースに合わせて表現 を変える必要がある >別の解決策を模索する必要がある リソースはユースケース毎に表現を変える必要がある
  38. © 2012-2021 BASE, Inc. 48 Recoil 状態管理ライブラリ • 状態を共有でき、React Hooks(useStateな

    ど)と同じような使用感で利用可能 • 余計なボイラープレートコードが不要 • atomという単位で状態を分割管理 • selector(純粋関数)によって、atomの内容 を受け取り変換させたり、atomの内容を更 新したりできる ◦ 入力にはatomだけではなくselectorや他 のデータソース(DBやAPIなど)も接続可能 ◦ 非同期関数にもできる • atom effectsが強力 リソースはユースケース毎に表現を変える必要がある
  39. © 2012-2021 BASE, Inc. 51 Recoil 非同期selector • 非同期関数で定義できるため、APIリクエス トの結果をselectorの評価結果として返すこ

    とが可能 • ただし、評価結果はkey毎にキャッシュさ れ、冪等性を担保する必要があるため、リク エストの度、変更されうるリソースを扱うの は向かない • 例は、滅多に変わることのない郵便番号と住 所のマッピングをAPIで取得しキャッシュす る処理 Key=postalAddressQuery${code} で結果をキャッシュ リソースはユースケース毎に表現を変える必要がある
  40. © 2012-2021 BASE, Inc. 52 Recoil atom effects • atomの初期化や、副作用処理が書ける機能

    • DBやlocalStorageなど、外部のデータソー スを使って、atomの初期化や、状態の永続 化、同期が行える • Pay IDに登録する際のパスワード管理に利用 するatomとatom effectsの例 リソースはユースケース毎に表現を変える必要がある
  41. © 2012-2021 BASE, Inc. 53 SWRとRecoilを組み合わせる/Xstateから特定通知を受け取った場合も再検証 リソースはユースケース毎に表現を変える必要がある 53 React Component

    useSWR 強制再検証(mutate) リソース取得 useEffect(xstate.subscription) OpenAPI Client useSetRecoilState(recoil.selector) 通知受け取り登録 リソース更新通知 (カートリソース更新 など) Recoil リソース反映 リソース反映 選択項目表示 送料合計計算に提供 選択項目の詳細 情報を提供 リソース更新のため、 FormDataを送信 強制再検証
  42. © 2012-2021 BASE, Inc. 54 リソースの更新は一度に一つだけだが。。。 • あるリソースが更新されると、依存している他のリ ソースに、新たな制約が生まれることがある AmazonPay決済情報を反映する(一つのワークフロー)

    1. AmazonPay決済情報(配送先、支払い情報)取得 2. 注文セッションリソースにAmazonPay決済情報を 反映(AmazonPayの配送先を変えてはならないとい う制約が生まれる) 3. 2.を通じて、1.が注文内容リソースへ反映可能かを 検証 4. 1.を注文内容リソースへ反映 次のステップへ進める前に新たな制約が生まれる リソース更新は、 一度に一つだけ
  43. © 2012-2021 BASE, Inc. 55 XState StateMachine/StateCharts • StateMachine及びStateChart •

    StateChartは、抽象化及び階層化した状態 遷移モデルを定義する描画方式及び視覚言語 • “こうあるべき”というアプリケーションの動 作を、宣言的に記述できる • XStateは、定義した状態遷移モデルを、イ ンスタンス化して、アプリケーションに組み 込んで利用することができる • Recoilとの状態の考え方の違い ◦ Recoil: atomやselectorで評価された値 ◦ XState: 定義された概念(画像参照) リソース更新のためのワークフローを管理 https://stately.ai で作成したXState Machine
  44. © 2012-2021 BASE, Inc. 56 状態遷移とイベント、それをトリガーに実行されるアクションやサービス リソース更新のためのワークフローを管理 状態: asleep, awake

    イベント: wakes up, falls asleep @xstate/inspect という開発ツールで 描画されたMachine • 状態遷移やイベントなどあらゆる事象をトリガーにアクション関数を実行可能。 ◦ ENTRY/〜, DO/〜 ◦ XState外の世界ともやりとりができる(Next/React) • 複雑なサービスロジック(Promise,Callback,Machine etc.)を組み込み実行可能。 ◦ INVOKE/〜 リソース更新に利用
  45. © 2012-2021 BASE, Inc. 57 AmazonPay 決済利用 ワークフロー XStateでワークフローを定義 idle

    SUBMIT_AMAZON_PAY_LINKING submitting.amazonPayLinking INVOKE / postAmazonPayLinking routingError ENTRY / dispatchRoutingError needsToRefreshForm draft defaultSetting INVOKE / setCheckoutDefaults done: postAmazonPayLinking [shouldBeSetDefaults] error: postAmazonPayLinking done: postAmazonPayLinking [hasDraft] done: postAmazonPayLinking [shouldBeSetFormData] DISPATCH_ERROR 以降省略 AmazonPayの紐付け 注文内容に住所コピー 注文内容の足りない 項目埋める 住所の項目不足(電話 番号など) 注文内容のその他項目 の入力必須 AmazonPayの紐付け失敗 得られたAmazonPayの SessionIdを送信 XStateの状態遷移やイベント、アクションを駆使して、アプリケーションを動作させるのは、React/Nextの役割 以降省略
  46. © 2012-2021 BASE, Inc. 59 まとめ Great defaults for production!!

    Next.jsの枠組みの中で、ビルドやページ配信などWeb技術のあらゆることを 全部まかなってくれ、アプリケーション実装に集中できた。