はじめに
STORES 予約 で Web エンジニアをしている osd です。
今回は STORES 予約 で店舗一括登録を実装した際の設計、実装についてお話しします。
前提
なぜ必要とされているのか
STORES 予約 では、事業者が店舗を追加する毎に開設手続きを行う必要があります。具体的にはその際に
- 店舗情報の登録
- 予約での店舗情報(業種/id 設定など)
の操作を店舗数分行う必要があります。大規模な事業者になると 3 桁店舗数の作成が必要になり、開設工数の増加やミスが発生しやすい状況になります。
また、店舗ごとに予約ページ設定やスタッフ登録などの設定が必要になるため、店舗登録以外の一括設定機能も要望されており、その前段として店舗一括登録機能が必要とされていました。
予約のストア構造
現在 STORES 予約 では事業者 / 店舗が作成でき、事業者には複数の店舗が紐づく構造になっています。
事業者 / 店舗情報は 組織管理基盤システム(組織管理基盤システム についての記事)上で管理されており、STORES 予約 では 組織管理基盤システム 上での変更を subscribe してキャッシュテーブルに反映することで予約独自の組織情報と統合して組織情報を管理しています。
erDiagram "組織管理基盤:ORGANIZATION" { int id PK string name } "組織管理基盤:STORE" { int id PK string name string location int organization_id FK } "組織管理基盤:ORGANIZATION" ||--o{ "組織管理基盤:STORE" : "" "予約:ORGANIZATION" { int id PK string name } "予約:STORE" { int id PK string name string location int organization_id FK } "予約:ORGANIZATION_CACHE" { int id PK int organization_id FK } "予約:STORE_CACHE" { int id PK int store_id FK } "予約:ORGANIZATION" ||--o{ "予約:STORE" : "" "予約:ORGANIZATION" ||--|| "予約:ORGANIZATION_CACHE" : "" "予約:STORE" ||--|| "予約:STORE_CACHE" : ""
設計
機能として用意しなかった理由
店舗一括登録機能を実装する上で、
- 利用者側から一括登録の操作ができる UI を提供する
- 依頼があった際は都度アドホックに店舗登録作業をエンジニアが行う
という 2 つの選択肢がありました。
初期段階では、前者の UI を提供することで利用者が自身で店舗登録を行える仕様を検討していましたが、以下の理由からアドホックに倒すことを選択しました。
機能追加時の改修コスト
一括登録機能はすでに UI として提供されている機能のインタフェースの形を変えた機能となるので、新たな機能や field を対象機能に追加した際にはメンテナンス対象となるコードが増えることになります。利用者がいれば価値のあるコードですが今後のコストとのトレードオフを考える必要がありました。
データ構造の複雑化
ネストしたデータ構造を取り込む場合にはデータ登録の仕様が複雑になりオペレーションコストが高くなる傾向があります。 例として、複数スタッフ登録を持つ予約ページを表現した簡易的なデータ構造を以下に示します。
{ "reservation_page": { "name": "平日予約受付", "price": 8000, "menu": [ { "id": 1, "name": "カット", "price": 3000 }, { "id": 2, "name": "カラー", "price": 5000 }, { "id": 3, "name": "パーマ", "price": 6000 } ], "option": [ { "id": 1, "name": "トリートメント", "price": 2000 }, { "id": 2, "name": "ヘッドスパ", "price": 3000 } ], "staffs": [ { "name": "staff1", "acceptable_menu": [ { "id": 1, "acceptable_nomination": true }, { "id": 2, "acceptable_nomination": true } ], "acceptable_option": [ { "id": 1 } ] }, { "name": "staff2", "acceptable_menu": [ { "id": 1, "acceptable_nomination": true }, { "id": 2, "acceptable_nomination": false } ], "acceptable_option_id": [ { "id": 1 } ] } ] } }
頭が痛くなりますね…。実際に存在するデータ構造はさらに複雑で外部システムと連携している部分もあります。 そして、ユーザーの操作性のため csv に落とし込むとさらに冗長な形式となり、構造の理解を強制してしまいます。
これら 2 つの理由から、
- メンテナンスコストを抑えることができる
- 作成 / 変更されるデータ構造を理解している前提でデータ登録ができる
というメリットを取り、必要な際にエンジニアがアドホックに対応する形を取ることにしました。
オペレーションの設計
アドホックに対応する際には、業務ロジックの設計が重要になります。 今回の店舗一括登録機能では、以下のようなオペレーションを設計しました。
sequenceDiagram actor エンジニア actor カスタマーサポート actor 利用者 カスタマーサポート ->> エンジニア: 一括編集 依頼 エンジニア ->> エンジニア: 依頼内容の一括編集の可否を検討【*1】 alt 不可能な場合 エンジニア ->> カスタマーサポート: 対応不可能 連絡 else 可能な場合 エンジニア ->> エンジニア: 変更に必要なcsvのフォーマットを準備 エンジニア ->> カスタマーサポート: csvフォーマットの連携 カスタマーサポート ->> 利用者: csvフォーマットを埋めてもらうよう依頼【*2】 loop データ登録完了まで 利用者 ->> カスタマーサポート: データ提供 カスタマーサポート ->> エンジニア: データ提供 エンジニア ->> エンジニア: データインポート alt インポート成功 エンジニア ->> カスタマーサポート:完了連絡 カスタマーサポート ->> 利用者:完了連絡 else インポート失敗 エンジニア ->> カスタマーサポート: 修正依頼内容の連携 カスタマーサポート ->> 利用者: エラー内容の連絡 end end end
*1: 依頼内容の一括編集の可否を検討
アドホックな対応の利点として、依頼内容の可否検討の段階を設けています。 対応可能な工数であれば一括登録機能の項目を拡充したりなど要望ベースで柔軟に対応できます。
*2: csv フォーマットの連携
こちらから提供するフォーマットに沿ってデータを提供してもらうことで、エンジニアがデータの整形などの作業をする必要がなくなります。データの整形は工数の問題もありますが、利用者から提供されたデータに手を加えず一次情報として扱うことでデータの信頼性を保つことができます。
実装
組織管理基盤 との連携
前述した組織管理基盤が組織情報を管理している以上、店舗一括登録を行う際には予約のシステム内で完結せず 組織管理基盤 との連携が必要になります。
そこで、店舗一括登録機能では以下のようなシーケンスで処理を行っています。
sequenceDiagram box 予約 participant DB participant Sidekiq end participant 組織管理基盤 loop each_row activate Sidekiq Sidekiq ->> Sidekiq:【*1】 Sidekiq ->> DB: Store.find_by(public_id:) DB -->> Sidekiq: store alt store exists【*2】 Sidekiq ->> Sidekiq: 行エラー else Sidekiq ->> 組織管理基盤: createShop 組織管理基盤 -->> Sidekiq: response Sidekiq ->> 組織管理基盤: assignServiceToOrganization 組織管理基盤 -->> Sidekiq: response alt success Sidekiq ->> DB: insert store DB -->> Sidekiq: response else Sidekiq ->> 組織管理基盤: deleteShop【*3】 組織管理基盤 -->> Sidekiq: response Sidekiq ->> Sidekiq: 行エラー end end deactivate Sidekiq end
処理として考慮したポイントは主に 3 点です。
*1: 外部システムへの負荷軽減
組織管理基盤に対して店舗作成のリクエストを送る際に、一定のフロー制御をかけています。
組織管理基盤においても店舗作成時に複数の外部システムとの連携が発生するため余裕を持ってリクエストを送ることで、外部システムに負荷をかけないようにしています。
*2: 再実行性の確保
エンジニア作業での店舗一括登録を行う際にはデータの確認は行うものの、想定外のエラーが発生することが想定されます。その際に登録済の店舗があっても対象の登録をスキップすることで再実行時に一時情報となる登録元 csv を編集することなく処理の再実行が可能になります。
*3: ロールバック処理
想定外のエラーが発生したり、外部システム上でのエラーが発生する可能性があるため予約内での処理は transaction で rollback されますが、組織管理基盤に対してデータを操作するリクエストを実行している場合には対象処理のロールバックと見做せる deleteShop
のリクエストを送ることで外部システム上でのデータの整合性を保っています。
さいごに
今回は STORES 予約 で店舗一括登録を実装した際の設計、実装についてお話しました。業務ロジック設計やアドホックな対応を選択する際の判断基準の一例として参考にしていただければ幸いです。