FBZにおけるドメイン駆動設計(DDD)とサーバーレスアーキテクチャを組み合わせた設計戦術

f:id:vasilyjp:20201118132647j:plain

はじめに

BtoB開発部の木目沢です。Fulfillment by ZOZO(以下FBZ)で提供しているAPIの開発に携わっております。

FBZに関しては以前、物流支援サービスを支えるAWSサーバーレスアーキテクチャ戦略で、AWSサーバーレスアーキテクチャ関連のサービスをどのように活用しているかをご説明しました。 techblog.zozo.com

今回は、サーバーレスアーキテクチャの特徴、設計するうえで課題だった点、そしてそれら課題をどのように克服していったかご紹介します。

イベント駆動と分散処理

FBZではAWSサーバーレスアーキテクチャ関連のサービスを採用しています。その大きな特徴は「イベント駆動」であるという点です。

f:id:vasilyjp:20201117171940p:plain
Lambdaはイベント駆動のサービス

例えば、API GatewayへのリクエストやDynamoDB、S3などへのデータのプッシュイベントをトリガーにLambdaを起動します。起動されたLambdaは関数として処理を実行、そのアウトプットをトリガーに別のLambdaが動いていくという仕組みです。

Lambdaのトリガーとして利用できるAWSのサービスはこちらを見ていただくとわかるように、現時点で20サービス以上あります。

このように、多くのインプット元やアウトプット先があれば、例えば下図のように分散環境を活かしたサービスが自由に構築可能になります。

f:id:vasilyjp:20201117172028p:plain
Lambdaを使った分散環境の例

例では、CloudWatch イベントから起動したLambdaが全商品を取得しています。取得後、商品の更新を行うLambdaが各商品ごとに起動され処理していきます。その結果をSQSに追加すると、また別のLambdaが起動し通知処理を行うという流れです。

分散処理を行うことで全体の処理時間の短縮やバグ発生時の原因切り分けがしやすくなるなど、多くの利点を得ることができます。

設計面での課題

一方、このようなイベント駆動や分散処理のアーキテクチャはソースコードの可読性を下げる要素が2つあります。

  • 多くのAWSサービスへのアクセス

  • 1つの処理で複数Lambdaの実行

多くのAWSサービスへのアクセス

多くのAWSサービスを利用するため、各サービスへのアクセスロジックとビジネスロジックが混在してしまうため、処理が複雑になりソースコードが読みづらくなります。

1つの処理で複数Lambdaの実行

1つの処理で複数のLambdaが実行されるため、Lambdaの処理からビジネスロジックを確認しづらくなります。例えば、商品の更新処理という1つの処理の中で、取得・更新・通知と3つの機能が実行されます。そのため「商品の更新処理」の流れについて3つのLambdaを追う必要があります。

AWSサービス処理とビジネスロジックの徹底分離

この課題を解決するため、以下の2点の考え方が必要でした。

  1. 各AWSサービスへのアクセスロジックとビジネスロジックを分離すること
  2. 各Lambdaから共通して参照できるようにビジネスロジックを集中した場所に定義すること

そこで私達が当初の設計から取り入れたのがドメイン駆動設計(DDD)です。

ドメイン駆動設計ではドメインモデルをAWSの他のサービスと分離するために、いくつかのアーキテクチャが提案されています。私達が採用したのはレイヤアーキテクチャです。

f:id:vasilyjp:20201117172059p:plain
レイヤアーキテクチャ

ヘキサゴナルアーキテクチャやクリーンアーキテクチャなど他のアーキテクチャも提案されています。しかし、私達はドメインモデルを分離することだけに集中し、最もわかりやすいレイヤアーキテクチャを採用しました。

分離したモデル層はモジュール化し、他の層を参照できないようにしました。これにより、FBZのモデル層に各AWSサービスへのアクセスロジックが入り込めない設計にできました。

f:id:vasilyjp:20201117172151p:plain
FBZのフォルダ構成例

この設計により、アウトプット先のストレージを変えたい、またはSQSではなくSNSを使いたいなどの要望もinfrastructures層だけを変更すればよくなりました。そして、商品の更新ルールを変えたいなどビジネスロジックを変更したい場合はmodels層の商品モデルだけを修正すればよくなりました。

このようにイベント駆動、分散処理に対する問題点をドメイン駆動設計を採用することで解決してきました。

新しい問題点

サービスの成長に伴い、新しい問題も出てきました。一部ではありますが、2点ご紹介します。

  1. モデルのモノリス化
  2. モデル以外にビジネスロジックが書かれてしまう

モデルのモノリス化

私達は各Lambda関数が共通のモデルを見ることでソースコードが追いにくいという問題を解決してきました。しかし、FBZが成長するに連れLambda関数も増えていき、現在では数百単位の関数になっています。このレベルまでLambda関数の数が増えると、今度はモデルのモノリス化が深刻になってきました。

例えば、注文の業務では注文モデルを見ます。同じモデルを発送や返品・交換でも見ることになると、それぞれのロジックが注文モデルに集まってくる状態になります。その結果、複数のLambda関数から同じモデルを見ることで容易に理解できるというメリットから、モデルが大きすぎて逆に理解しづらくなるという新しい問題が発生しています。

大きくなりすぎたモデルを理解しやすい単位に分割することが必要になってきています。

モデル以外にビジネスロジックが書かれてしまう

メンバーの増減や至急の要件などをこなしていくうちに、ビジネスロジックがモデルではなくアプリケーション層やハンドラーに書かれていってしまう問題も発生しています。

Lambda関数によってはモデルを一度も使わずに複雑なビジネスロジックを実装しており、さらにそこが何度も修正が入るような重要なロジックであることも少なくありません。修正が入るタイミング、リファクタリングできるタイミングでこういった箇所をモデルに移行していくということも意識していかなくてはいけません。

複雑なビジネス要件だからこそ活きる設計

今回はサーバーレスアーキテクチャを用いたサービス開発の中で生じた課題と、その課題に対しドメイン駆動設計を用いて対応してきた内容をご紹介しました。苦労している点は現在もありますが、約50に及ぶECサイトで利用されるAPIサービスをチームで開発してきたという意味で一定の成果を上げています。サーバーレスアーキテクチャを採用する事例も最近では増えてきたと思いますのでぜひ参考にしていただければ幸いです。

最後に、今回の経験をもとにドメイン駆動設計を採用する上で重要だと感じた点を共有します。

ドメイン駆動設計ではモデルを分離したその先、モデル自体をどのように構築していくかが重要になってきます。サーバーレスアーキテクチャはそれ自体複雑なものですが、もっと複雑で理解が難しいのはユーザーの活動やビジネスに関するロジックです。

FBZも単に在庫を連携するだけのサービスではありません。商品はもちろん、在庫が変動する要素である注文や配送の管理も必要となるため、ビジネスロジックは非常に複雑です。さらにサービスの成長に伴い、このビジネスロジックに多くの変更が入ります。複雑なビジネスロジックをどのようにモデルに表現するかで、修正や追加の難易度が変わります。それがさらなるサービスの成長に影響していきます。

そのため、モデルを改善し続けていくことがサービスを成長させていく上でなにより大切になってきます。私達は引き続き改善を続けていくことで、今後もFBZを成長させていきたいと考えています。

BtoB開発部では、サーバーレスアーキテクチャやドメイン駆動設計などテクノロジーを活用しサービスを成長させたい仲間を募集中です。ご興味ある方はこちらからぜひご応募ください!

tech.zozo.com

カテゴリー