🌻

制約と共に登るコンパウンドスタートアップのアーキテクチャ | Resilire Tech Blog

2024/12/20に公開

はじめに

レジリアでアーキテクトをしている奥村@arashigaoka3 です。
今日はサプライチェーン領域でマルチプロダクト戦略をとる弊社のアーキテクチャの過去と未来についてお伝えしたいと思います。

アーキテクトのしごと

私はアーキテクトとして、アーキテクチャや技術選定を担当していますが、実態としては以下のようなことを行っています。

  • 週一で実施しているBackend Syncと呼ばれる技術相談会のファシリテーション。レジリアには優秀なエンジニアが集まっているので、一人一人の意見を整理して、集合知を一番いい形でアウトプットする。
  • 設計、PRをレビューし、アーキテクチャ通り実装されていることを確認する。

もちろん決定に必要な事業的インプット、技術的インプットは継続して行っていますが、知らないことやわからないことも多いので、積極的にメンバを頼りながらアーキテクチャに関する議論を進めています。

制約と共に歩んだプロダクトの過去、そして未来

レジリアの進めているプロダクトやドメインは、似たようなプロダクトやシステムが少なく、試行錯誤しながらアーキテクチャのアップデートを続けています。
僕の好きな言葉で、「ソフトウェアアーキテクチャはトレードオフがすべてだ」という書籍「ソフトウェアアーキテクチャの基礎」にある言葉があります。
スタートアップは特に制約が大きくなる傾向があり、その制約を前提にどのトレードオフを選択するかが求められます。過去の決定もその時点では最善だったケースも多いため、それを尊重しながらも「いま」の制約とともにアーキテクチャを進化させることが大事だと考えています。

そんな今までの進化の歴史を紐解くと、下記のような流れとなっています。

  • 旧プロダクト
  • SCRM(Supply Chain Risk Management)
  • SCRM 2.0 ←イマココ
  • SCRM 3.0 ←未来

旧プロダクト時代

レジリアは現在、サプライチェーンリスク管理クラウドサービスを提供しています。詳しくは、Company Deckをご覧いただけると嬉しいのですが、システム的な観点では以下のような仕様を実現したものと言えます。

  • 社内のユーザ、取引先のユーザはサプライチェーンデータ(拠点や品目など)を入力できる
  • システムは、リスクデータ(地震や大雨など)を自動的に取得する
  • システムは、サプライチェーンデータとリスクデータを照らし合わせ、リスク発生時の通知やリスクデータの可視化などを行う

当初レジリアはエンジニアが採用できていないなか、需要を検証するため、オフショア開発にてAltairと呼ばれるプロダクトを開発しました。
これはMVPとして機能し、クライアントの導入を進めることができた一方で、以下のような課題も存在していました。

  • オフショア開発でコミュニケーションコストが高く、エンジニアが高い解像度でプロダクトを開発できていなかった結果、コード品質が低い。
  • サプライチェーンデータがjsonで格納されており、再利用可能な形で保存されていない。

レジリアはサプライチェーンデータのプラットフォームになるというビジョンを掲げており、サプライチェーンデータの再利用性は極めて重要です。このデータ構造の見直しを、低いコード品質のプロダクトをメンテナンスしながら実施することは難しい状況でした。
その頃、一人目エンジニアの@teruhikyが入社したこともあり、レジリアは新しいプロダクトであるSCRMの開発へと門出を切りました。

SCRM時代

SCRMへと門出を切ったタイミングで、以下のようなアーキテクチャを採用しました。

SCRM1.0のアーキテクチャー図

以下、ポイントについて何点か説明します。

BFFとBackendの分離

弊社ではADRを導入しているので、そこから参照しながらご説明します。
BFFとBEを分離したのは、将来的なMicroServiceをにらんでいたためです。具体的には、下記のような責務に分解しました。

BFFの責務:

  • (フロントエンドとのインターフェース)
  • Role の確認
  • User の認証状態の確認
  • View Model の定義

BEの責務:

  • ドメインのサイズを決定する(ドメインモデルの見える範囲)
  • ドメインモデルの定義
  • (DB とのインターフェース)

ここで一点特徴的なのは、複数のMicroServiceから利用できるよう、BEを再利用可能な形で作成したいと構想した点です。そのため、SCRMの仕様を実現するためのビジネスロジックはBFFにて実装されていました。

BackendとBatchの分離

弊社のプロダクトは、災害が発生した際にメールを送付するなど、非同期的に行いたい処理が多数存在し、これをBatchとして実装しています。
当初、Batchは別のコードベース(monorepo内の別directory)に分離されており、DBとのI/Oを行いたい場合はBackendへのgRPC callを通じて行う設計となっていました。

SCRM 2.0

プロダクトのリプレイスは本当に大変でしたが、開発チーム一丸の努力の末、今年(2024年)の4月にはSCRMを本番リリースできました。5月にはエンジニア社員が6名にまで増えるなど、体制的にも少し余裕が出てきました。
クライアントも着実に増加する中、運用も安定してこなしつつ、新たな機能開発も進めることができるようになってきました。
一方で、運用や機能開発のプロセスを進める中で、以下のような課題が見えてきました。

BFFとBackendの役割の曖昧化

元々BackendはシンプルにDBアクセスする薄いレイヤとして捉えていたのですが、実際にプロダクトの開発を進めていくと、シンプルなSQLだけでは十分なパフォーマンスが得られないケースが多いことがわかりました。
具体例を2つ挙げます。

  1. マップ
    SCRMでは、「いつ」「どこで」「どのような規模の」災害が発生したかを見ることができ、かつその災害がユーザのどの拠点に影響したかをマップに表示できます。
    これを実現するためには、拠点(サプライチェーン)の情報と、災害の情報を組み合わせる必要があります。
    その一方で、マッチングするべきキーのオーダーが10 ** 3を超えることが十分想定される画面仕様となっており、これを実現するためにはBackendでJOINを使用した適切なクエリを構築することが必要となります。

  2. サプライチェーンデータ
    サプライチェーンとは、下記の図のようなツリー構造であり、これは現状、nodesテーブルの各レコードに parent_node_id というカラムを持つことで表現しています。
    これは隣接リストモデルと呼ばれる方式で、SQLアンチパターンの1つとされてきましたが、PostgreSQLにおいては再帰クエリを利用可能なので、これを活用できます。
    ただし、サプライチェーンデータは個別に権限を設定できるなど、単なる再帰クエリではなくシーンに応じてカスタマイズされたクエリを発行する必要があります。

これ以外にも、BFFでビジネスロジックを書ききれないケースが多くあり、結果としてBEにもビジネスロジックが分散して実装されることとなってしまっていました。
そのため、BEでもserviceレイヤが登場することとなり、多すぎるレイヤによる開発生産性や品質の低下、ロジックの重複や分散が生じてしまっていました。

また、外部APIの呼び出しも、BFFに集約されていましたが、その影響でBEのトランザクションと整合性を取れないシーンがありました。

BatchとDBの間にBackendを入れることによるデメリットの顕在化

BatchとBackendはコードベースが分離されており、アプリケーションとしてもプロセスが別で、Batchはその処理の中でDBの値を参照・更新したい場合はBackendにgRPCでリクエストする必要がありました。これはDBを更新する際の諸々の制約を記述する場所を一箇所に集約するためです。

しかし、以下のような課題がありました。

  • コードベースが分離されているため、パフォーマンスチューニング等で処理を分割・統合するなどして見直す際に手間がかかる
  • アプリケーション間のコネクションはDBとのコネクションに比べて不安定で考慮すべき点が多い
  • gRPC毎にDBのトランザクションが別になるのでデータの整合性や冪等性の担保が大変で、一部のケースは運用で対応せざるをえない

SCRM 2.0のアーキテクチャ

上記の課題を受け、以下のようにBFFとBackendの役割を見直し、BatchとBackendのコードベースを統合しました。

SCRM2.0のアーキテクチャー図

BFFとBackendの役割の見直し

今後もパフォーマンス起因でBackendに一定複雑なロジックを実装するのは避けられないと判断し、結論として今後はBFFにはビジネスロジックを書かず、基本的にgRPC(Protobuf)からJSONへのデータ変換のみを行うことにしました。

BatchとBackendのコードベースの統合

前述したデメリットを解消するため、BatchとBackendのコードベースを統合しました。その上で、従来のBackend処理を行うgRPC Serverモードと、従来のBatch処理とその処理の中で呼んでいたBackendの処理をおこなうBatch Serverモードのどちらかを指定して起動できるようにしました。

gRPC ServerモードはusecaseがgRPC callで呼ばれ、Batch ServerモードはusecaseがPubSubのSubscribeで呼ばれるような構造になり、どちらのusecaseについてもmodelやrepositoryは共有できるような構造にしました。

SCRM 3.0

そのような状況の中、レジリアでは来年にSAQと呼ばれる2つ目のプロダクトをリリースすることを決定し、マルチプロダクトになることを見据えて改めてアーキテクチャを考え直す機会を得ました。
そこで改めて考えたところ、SCRM 2.0においてもまだまだ構造的な課題があるように感じました。具体的には以下のようなポイントです。

  • 1つの処理がBFFとBackendをまたいで実装されており、BFFやBackendの中でもレイヤが複数存在している。そのため、モデルの変換処理が煩雑で不具合を誘発しやすく、既存実装の調査にも大きな工数がかかっている。
  • 1つの画面に必要なデータが複数テーブルにまたがっていること、加えて前述のノードやマップに関する課題も考慮すると、どうしてもクエリがある程度複雑になってしまう。

上記のような課題を踏まえ、今後マルチプロダクトを目指す上でどのようなアーキテクチャがあり得るのか、そして現在のアーキテクチャからどのようなstepで進めていくのかを議論しました。

MicroService or Monolith

まず、マルチプロダクトを踏まえた上で、MicroSeriviceを採用した場合、どのようなアーキテクチャとなるのかを検討しました。

MicroServiceのアーキテクチャー図

この案で進める場合、懸念がいくつかあるように感じました。
具体的には、

  • データ量を鑑みると、それぞれのMicroServiceで管理するテーブル間でjoinが必要になるケースが想定される。MicroServiceの場合、共有データベースは原則アンチパターンとみなされるため、これを実現するためにはQuery用のDBやElasticSearchなどを用意する必要がある。
  • MicroServiceの大きな目的として、Service間を疎結合にすることで各チームのコミュニケーションパスを限定し、生産性を高めることが挙げられる。一方で、現状の開発リソースでは、複数チームを組成する見通しが立たない。
  • 現状のプロダクトは一定のPMFを得たものの、まだまだ試行錯誤の段階であり、適切な設計の形が必ずしも見えていない。

一方で、Monolithを採用した場合は以下のようなアーキテクチャが考えられます。

Monolithのアーキテクチャー図

まず、試行錯誤の過程においては、DBのjoinはできる状態で作りたいと考えました。その一方で、複数プロダクトが同じテーブルをwriteしていると、巨大なグローバル変数になってしまい、秩序を保てなくなることが懸念になります。そこで、各テーブルに対して、writeできるcomponentを限定することで、これを防ぐこととしました。
また、レイヤ間の変換の問題やアプリケーションロジック内のループの問題については、CQRSの考え方をとることによって改善できるのではないかと考えました。すなわち、Queryについてはドメインロジックを実現するようなレイヤは不要であり、単にDBをクエリした内容を返すことに集中する設計としました。データ量の問題が発生するのは、ほとんどの場合Queryのケースなので、これによりパフォーマンスの問題も解決できるのではないかと考えました。

結論として、現状の事業フェーズや開発リソースを鑑みると、Monolithの方向が理想だろうと考えました。

山の登り方

Monolithの方向で考えるのが良いとわかった上で、現状の事業フェーズでは機能開発を止めるリアーキテクチャはできないため、どのように歩みを進めていくかも合わせて考える必要があります。
結論としては、以下のように現状のBFFにDBのRead権限を与え、クエリについてはBFFが処理できる形にするところに着手したいと思っています。DBアクセスを可能とするタイミングで、BFFからFacadeのような別の名称(いい名前募集中です)に変えることで、単純なBackendのラッパー層ではないことも合わせて明示したいと思っています。

SCRM 3.0のアーキテクチャー図

これにより、現在最も課題となっているQueryの生産性や品質向上を実現できる一方で、APIレベルでのテストでリグレッションテストを用意することで段階的な移行も可能となります。
もちろん少しいびつな部分があることは否めないのですが、現状の制約をもとに考えるとメリットが上回るように思っているので、このようなアーキテクチャでトライしてみたいと思っています。

まとめ

アーキテクチャを見直す中で色々な方とお話しさせていただきましたが、アーキテクチャというものはいろんな会社のいろんな事情を反映したものになるなという当たり前のことに改めて気付かされました。特にスタートアップにおいては、「事情」の変化が激しいと思っているので、継続的にアーキテクチャを進化させていきたいと思っています。

終わりに

今回は、アーキテクチャの進化について概要を駆け足でまとめました。詳細を省略した点や、Risk Componentの話やORMの話、テストの話など、書ききれなかった部分が多数あるので、それらについてもまたお伝えしたいと思います。

来年はマルチプロダクトの運用が始まるとともに、データモデリングの見直しを含む大きなアップデートを予定しており、これをどれくらい良いものにできるかによって、事業がどれくらいスケールできるか変わってくると思っています。
一方で、あらゆる方面でエンジニアが足りておらず、手が回っていないところばかりです。もしこの記事を読んでご興味持たれた方がいらっしゃいましたら、ぜひ一度カジュアルにお話しさせてください!

カジュアル面談はこちら

採用情報
https://recruit.resilire.jp/

Discussion