こんにちは。ソウゾウのSoftware Engineerのktazoeです。連載:メルカリShops 開発の裏側 Vol.2の16日目を担当させていただきます。
元々メルペイでmicroserviceの開発を担当しており、10月にソウゾウへ異動してきました。メルペイでは、multirepoでkubernetesをインフラとして利用して開発を行なっていましたが、ソウゾウでは「メルカリShopsの技術スタックと、その選定理由」にもあるように、monorepoでCloud Runを利用してmicroserviceを開発しています。同じGo言語を利用したmicroservice開発ですが、tech stackが違うことで開発体験が違う部分も多いと感じています。
この記事では、直近で取り組んでいた売上履歴サービスの開発の一部を紹介することで、ソウゾウにおける開発体験についてご紹介します。
売上履歴サービス
今回開発を行った売上履歴サービスについて説明します。売上履歴とは、出店者さまに取引が完了した注文の一覧を提供する機能で、以下のような要件があります。
- 期間を指定して、完了した取引をCSVにまとめてダウンロードする機能。将来、CSV以外のAPIなどの形での情報提供も視野に入る
- 将来的には、キャンペーンなど注文サービス以外のデータも売上履歴に入ってくる可能性がある
- メルカリShopsは、裏側でメルペイの決済系APIを利用しており、メルペイの決済履歴と整合性の取れた売上履歴を作成する必要がある
アーキテクチャの検討
Design Docsを作成し、全体の方針を整理してアーキテクチャを検討します。Design Docsのreviewは新規サービスの開発の際に行われることが文化になっています(*1 メルカリShopsでのDesign Docs運用について)。今回の場合では、以下のような方針が検討されました
- a. 売上履歴サービスが、注文サービスをPubSubなどを利用してsubscribeし、決済完了やキャンセルのタイミングで必要な情報を収集してデータを作成し、履歴向けのviewを継続的に作成する案
- b. 売上明細のダウンロードリクエストが来た際に、batch的に関連サービスを呼び出し、必要な情報を収集し、CSVを作成する案
- c. データウェアハウスとして利用しているBigQueryにqueryして複数のmicroserviceの情報を組み合わせた明細を作成する案
- d. 注文サービスに参照系の機能として履歴機能を実装する案
b案では、売上明細のダウンロードリクエストが来た際に、batch的に関連サービスに呼び出しが飛ぶため、参照系の負荷が注文の実行系の処理に影響を与えてしまう懸念があります。また、売上明細のための検索要件を盛り込んだAPIを関連サービスに準備する必要が生じたり、依存先のサービスが増えた場合に売上履歴サービスのロジック自体が複雑化してしまう懸念もあります。
c案では、データウェアハウスを経由しているとはいえ他のサービスのDB schemaに依存したqueryを売上明細サービスが管理することになるため、他のサービスのDB schemaの変更が売上明細に与える影響を考慮しなくてはならないという懸念があります。
また、b案でもc案でも、歴史的経緯などでデータの読み方が複数存在するようになってしまった際に、売上明細サービスがそれらのパターンを考慮してロジックやqueryを管理していかなくてはならなくなるという問題があり、ロジックの正当性を検証・維持することが徐々に難しくなるという懸念があります。
d案では、注文サービス自体の複雑化や性能に懸念が生じます。
a案では、独立したDBをもつmicroserviceの開発が必要になるため整合性の観点も含めた開発やmigrationのコストはかかる一方で、将来的な性能面やロジックの複雑化の懸念は少ないと考えました。
これらの理由から、今回はa案を採用し、注文serviceをPubSub経由でsubscribeし、履歴情報を保持する形で売上明細サービスを開発することにしました。
開発における課題と解決
今回の開発で課題となっていた下記3点について、どのように解決したかを順に説明していきます。
- (a) 複数のmicroserviceからデータを収集して履歴データを保存するため、システム全体として整合性が取れているかを検証する必要がある。この検証作業には、決済APIとして利用しているメルペイのシステムも含む
- (b) 明細に必要な情報が分散しており、それらの取得のために複数のmicroserviceのapiに修正を加える必要がある
- (c) 過去の注文の履歴を売上履歴サービスにmigrationする必要がある
(a) 複数のmicroservice間での整合性の検証
複数のmicroserviceからデータを収集して履歴データを保存するため、各microserviceから必要な情報を収集し、データの不整合がないことを確認してから、viewを保存するという実装を採用しました。非同期処理によるステータスのずれなどがあり、一時的な不整合が検出された場合は、retry用のtableにPubSubのeventを保存し、非同期的にretry処理を実施します。
定期的にcloud schedulerがretry用のRPCを呼び出し、CloudTaskQueueを経由してrate limitをかけながら処理をretryします。
メルカリShopsでは、microserviceのgrpcをhttpに変換するgrpc-proxy(envoy)が存在するため、httpを前提にしているGCPの技術資産を各microserviceが利用することができます。kubernetesを利用していれば、cronjobとして実装・管理していくようなretryなどの機構もCloud SchedulerとCloud Taskを組み合わせることで、シンプルなRPCとして実現することができます。
(b) 複数のmicroserviceに修正を加える
一般的に複数のmicroserviceに修正を加えなければ実現できない機能の見積もりや開発は高コストになります。メルカリShopsでも、既に50近くのmicroserviceが存在していますが、このように多くのmicroserviceがある中で全てのmicroserviceのコードを把握することは容易ではないからです。
しかしながら、ソウゾウでは、monorepoの採用やDesign Docのreview・タスク分解や見積もりにチーム全体で取り組むことによってそのコストを低減できています(*2 メルカリShopsにおける開発の進め方)。現段階では、調整や手戻り少なく開発が行えており、大きな問題にはなっていません。
今回の場合は、特にmonorepoによるmicroservice開発の利点を強く感じました。修正が複数のmicroserviceにまたがっていても、一つのPRでPoCの作成やリリースを済ませてしまうことが可能だからです。
(c) 過去データのmigration
メルカリShopsは、サービス開始からまだそれほど時間が経っておらず、過去データの量も数百万程度とそれほど大きくありませんでした。そこで、過去データのmigrationは、(a)で開発したPubSubのendpointを流用し、Cloud Tasks経由でrate limitをかけつつ実施することにしました。
まとめ
ソウゾウでは、以下のように工夫をしてmicroservice開発に取り組んでいます。
- DesignDocのreviewやタスク分解や見積もりに全体で取り組むことで、規模の大きい開発の確度を上げつつ、よりよい設計の議論ができる
- monorepoを採用していることで、複数のmicroserviceにまたがった機能のPoCやリリースをスピード感持って行うことができる
- Cloud RunやCloud PubSub、Cloud Task、Cloud SchedulerなどのGCPのフルマネージドな機能を組み合わせることで、効率的に開発を行うことができる
microserviceという選択肢を取りつつも、事業の立ち上げ期におけるスピード感を出すための工夫として現時点ではうまく機能していると感じています。仕組みや体制を工夫しているので、学び多く開発できる環境が整っているのではないかと思います!
明日は、@_ha1f さんの「iOSエンジニアから転向して在庫補充通知機能をリリースしました!」です。在庫補充通知機能のアーキテクチャ事例が紹介されます。そちらもぜひお楽しみに!
ソウゾウではメンバーを大募集中です。メルカリShopsの開発やソウゾウに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。
- Software Engineer
- Software Engineer, Site Reliability
- Software Engineer, Machine Learning
- Software Engineer, QA Test
- Software Engineer (Internship) – Mercari Group (※新卒採用に応募するにはまずインターンへの参加をお願いしています。)
またカジュアルに話だけ聞いてみたい、といった方も大歓迎です。こちらの申し込みフォームよりぜひご連絡ください。