ZOZOSUITからZOZOMATへ - CQRSによる解決アプローチ

f:id:cozima0210:20200602145647j:plain

はじめに

こんにちは、計測プラットフォーム部バックエンドチーム、テックリードの児島(@cozima0210)です。この記事では、ZOZOSUITとZOZOMATの違いにより生じたバックエンド開発における課題と、その解決のためにCQRSアーキテクチャを採用した経緯、そしてその実践について紹介します。

ZOZOSUITとは

ZOZOSUITは、2017年に発表した全身の計測を目的としたツールです。現在も計測機能は提供されていますが、新規の販売は終了しています。現在、ZOZOSUITの計測データは、マルチサイズ商品の開発に活かされています。

ZOZOMATとは

ZOZOMATは、2019年に発表した足の計測を目的としたツールです。足の計測データから、足型診断や推奨サイズの提案に活用されています。今年の2月にリリースし、ZOZOSUITに続く計測技術として、とても注目をいただきました。

計測プラットフォーム部とは

計測プラットフォーム部は、これら計測技術の活用を通して、「計測データと推奨サイズの精度向上により、お客様にオンラインでも最高の購買体験を提供する」ことをミッションとしています。

バックエンドチームのミッション

計測プラットフォーム部の中で、私の所属するバックエンドチームは計測プラットフォーム基盤を支えるために、高速で安定したシステムづくりを目指しています。

バックエンドチームの技術戦略

Scala

私たちバックエンドチームは、プログラミング言語に、Scalaを採用しています。Scalaは強力な型システムに支えられた堅牢なシステム作りを容易にしてくれます。また、DDDとも相性が良く、ビジネスの変化に柔軟に対応するためのツールとして大きく貢献してくれます。

DDD

DDDとは「Domain Driven Design」の頭字語で、Eric Evans氏が2003年に考案した設計手法です。日本国内でも「ドメイン駆動設計」として知られており、多くの開発チームに採用されています。この定義については様々な説明がありますが、「現実世界の問題に立ち向かうため、ソフトウェアによって対象の抽象化を図り、その解決を容易にすることを目指す」ための方法論であると理解しています。私たちバックエンドチームでも、このモチベーションからDDDを設計手法に採用しています。

ZOZOSUITからZOZOMATへ

ZOZOMATのシステムアーキテクチャを検討し始めた時期は、2019年の4月頃でした。開発の初期段階で抱いた印象としては、計測フローに若干の違いはあるものの、ZOZOSUITのシステムからそれほど大きな変更は必要ないと感じました。しかし、設計を進めていく中で、その印象は次第に覆されました。まず始めに、ZOZOSUITとZOZOMATの比較を通して、システムを設計する上で考慮する必要のあった違いについて解説します。

計測フロー

ZOZOSUITでは、ユーザーがカメラを固定した状態で、その前で時計回りに一周します。その間に、12回の撮影が自動で行われ、計測が完了します。一方、ZOZOMATではユーザー自身が左右の足それぞれ、6枚の画像を撮影します。

計測結果の表示画面

それぞれの計測フローを終えた後、ユーザーに計測結果の画面が表示されます。どちらにおいても、3Dの立体図が表示され、各計測値が表示されます。

ZOZOSUITによる計測結果の表示画面

ZOZOSUITによる計測結果の表示画面

ZOZOMATによる計測結果の表示画面

ZOZOMATによる計測結果の表示画面

計測のデータモデル

詳細を割愛していますが、下図はZOZOSUITの計測データモデルです。

ZOZOSUITの計測データモデル

ZOZOSUITの計測データモデル

一方、ZOZOMATでは、計測値と3Dデータを左右に分けて、それぞれ管理するようになりました。

ZOZOMATの計測データモデル

ZOZOSUITの計測データモデル

計測におけるデータフロー

ZOZOSUITでも、ZOZOMATでも、計測値と3Dデータはクライアントアプリで生成されます。ZOZOSUITでは、計測後にデータをサーバーにPOSTしますが、データがサーバーに送信されるタイミングは1回のみでした。一方、ZOZOMATでは左右の足それぞれの計測後にデータをサーバーにPOSTする必要がありました。そのため、データがサーバーに送信されるタイミングは必ず2回以上あります。これについては、一度にまとめることも考えられましたが、計測が失敗した場合の考慮が必要でした。計測が失敗した原因の調査には、その計測中のデータが必要でした。そのため、計測が失敗を繰り返した時に、一度に送信するデータ量が、とても大きくなる懸念がありました。これらの理由から、データ生成の都度、サーバーにデータを送信することとなりました。

計測データの状態管理

ZOZOSUITでは「計測完了」のみが状態として存在し、とてもシンプルでした。

ZOZOSUITの状態遷移

ZOZOSUITの状態遷移

一方、ZOZOMATでは、以下の4つの状態管理が必要になりました。

ZOZOMATの状態遷移

ZOZOMATの状態遷移

ユビキタス言語

DDDでは、設計において対象を的確に表す命名が、特に重要であると考えられますが、その命名された用語のことをユビキタス言語と呼びます。ZOZOSUITでは、計測そのものを指す用語として、Measurementが採用されました。しかし、ZOZOMATでは全体の計測と左右の足それぞれの計測を分けて命名する必要がありました。そこで、全体の計測をSession、左右の足それぞれの計測をScanと命名することが採用されました。

その他の表示画面

ZOZOSUITでは、計測データを元にする画面として、計測結果の表示画面と推奨サイズの表示画面がありました。一方、ZOZOMATでもその2つの画面がありましたが、それに加え足型診断の表示画面が必要になりました。

推奨サイズの表示画面

推奨サイズの表示画面

足型診断の表示画面

足型診断の表示画面

ZOZOMATでの課題

これらの違いによって発生した課題を解説していきます。

計測データを単一のデータとして表現できなくなった

ZOZOSUITの計測データは、メタデータに対し計測値と3Dデータを、一対一のデータとして表現できました。しかし、ZOZOMATでは1つのメタデータに対し左右それぞれの計測値と3Dデータで構成されるため、一対多の構造をとる必要がありました。これにより、参照系の処理時に結合が必要となり、多くのことを考慮する必要がありました。

データベースでの結合

データベースで結合を検討する場合、DynamoDBのようなNoSQLでは、そもそも結合のためのAPIは存在しません。また結合がサポートされているRDBMSであっても、データサイズが大きくなるに連れて、3つのテーブルを結合することはパフォーマンスを悪化させます。

アプリケーションでの結合

アプリケーションで結合を検討する場合、3つのクエリの発行が必要になります。これは単一のデータソースから単一の行を取る場合と比較して、参照系の処理を複雑にし、パフォーマンスも悪化させます。

データの状態管理が複雑になった

ZOZOSUITでは、バックエンドが扱う状態として、「計測完了」があるのみでした。一方、ZOZOMATでは計測の開始時に左右の計測データの親データを生成し、「計測開始済」の状態に移ります。その後、子データとなる左右の計測データが生成され、それぞれの「計測完了」に状態が遷移します。そして最後に、計測データの検証が行われ、「全体の計測完了」に状態が移ります。このように管理する状態が増えたため、更新系の処理も複雑度を増しました。

ドメインの関心ごとが増えた

ZOZOSUITでの主な関心ごとは、計測結果の表示と推奨サイズの表示でした。計測結果の表示は、計測値の入出力のみで対応可能でした。そして、推奨サイズの表示については、機械学習の推論サーバーへのAPIリクエストが必要でした。これはZOZOMATにおいても共通であり、この記事では詳細について取り上げません。一方、ZOZOMATではこれに加え、足型診断というコンテキスト、つまり新たな関心ごとが追加されました。これにより、さらにドメインモデルを柔軟に保つための検討が必要でした。

CQRSによる解決アプローチ

CQRSとは

CQRSは、「Command and Query Responsibility Segregation」の頭字語で、Greg Young氏が2010年に考案したデザインパターンです。源流には、Bertrand Meyer氏が1997年に提唱したCQS、「Command-Query Separation」という原則があります。これらは端的に説明すると、「更新系(Command)と参照系(Query)の分離」を勧める考え方です。

システム構成図

ZOZOSUITとZOZOMATの実際のシステム構成図です。

ZOZOSUITのシステム構成図

ZOZOSUITのシステム構成図

ZOZOMATのシステム構成図

ZOZOMATのシステム構成図

参照系をシンプルに

CQRSを採用することにより、参照系のための最適化ができました。具体的には、先述の状態管理にある「全体の計測完了」に遷移した計測データに対し、メタデータと計測が成功した左右の計測データを結合したリードモデルを構築します。これを実現するため、DynamoDB StreamsをトリガーにLambdaを起動させます。そして、その処理の中で「全体の計測完了」に状態遷移した計測データに対し、計測結果の表示画面用に最適化されたリードモデルをAuroraに永続化します。これにより、計測結果の画面表示に必要な処理から結合をなくし、1回のデータアクセスで処理を完結させることができました。

最適化されたリードモデル

最適化されたリードモデル

イベントソーシングパターン

ZOZOSUITでは、単一の状態のみを扱うため、一度生成されたデータを、ユーザーアクションによって更新することはありませんでした。一方、ZOZOMATでは先述の通り、4つの状態管理が必要でした。この事情から、更新系のデータモデルにイベントソーシングパターンを採用しました。

イベントソーシングパターンとは

イベントソーシングパターンは、一般的にCQRSパターンとともに採用されます。ドメインのイベントをジャーナルとしてスタックし、そのジャーナルの再生により現在の状態を取得するデザインパターンです。

状態管理に対する課題の解決

ZOZOSUITでは、リリース後に計測値の補正を行う処理が必要になったことがありました。この時、私たちは履歴を管理する独自の仕組みを実装しましたが、それはとても煩雑なものでした。一方、ZOZOMATでは計測フロー中の状態管理もありましたが、もし同様のデータ補正処理が必要になったとしても、履歴管理の保守性と追跡可能性が担保されるようになりました。代表的な履歴を遡るユースケースとしては、カスタマーサービスの問い合わせ対応やデータ分析による行動解析がありますが、備えとして欠かすことができないものであることは必至でした。

ドメインの関心ごとを分離

ZOZOMATで追加された足型診断の表示画面用データは、更新系のシナリオの中では、扱われる必要のないものでした。それは、計測値のデータから計算を要する導出項目であったためです。そのため、更新系の処理の中で、この計算結果を永続化することは、以下の懸念を発生させていました。

  1. 集中すべきコンテキスト(計測結果の保存)の処理に、別のコンテキスト(足型診断の計算)の処理が入り込み、アプリケーションコードが複雑になる
  2. 計測結果の保存のみが行われるべき処理に、足型診断の計算も加わることによって、レイテンシにインパクトを与える可能性がある

一方で、参照系の処理中に都度計算をさせることも、以下の懸念を発生させました。

  1. 参照系の処理をシンプルに保つことを難しくする
  2. 計算コストによるレイテンシ低下を招く

これらを解決するために、Lambdaの処理するリードモデルに、以下のような足型診断の計算結果を含めることにしました。これによって、すべての懸念は解決されました。

より最適化されたリードモデル

より最適化されたリードモデル

さらに、将来において、足型診断に新しい項目が追加されることを想像してみます。例として、ZOZOMATにより、外板母子の傾向分析が可能になるとします。こうした拡張を検討する場合でも、本来参照系の処理でしか必要のないデータにより、更新系の処理に改修を発生させることは望ましくありません。もちろん、DynamoDBのようなNoSQLを採用していれば、RDBMSのテーブルにカラムを追加するような懸念もないかもしれません。しかし、このような関心ごとの分離によってアプリケーションの保守性が向上する効果は、とても大きなものだと実感できました。

CQRSの実践、その後

ドメインモデルの洗練

ZOZOSUITでも、DDDによるアプリケーションコードの関心ごとの分離や、レイヤー化が意識されていました。しかし、ZOZOMATで採用したCQRSによる参照系と更新系の二極化は、ドメインモデルが参照系について意識することがなくなりました。そして、更新系のみに集中することで、より深いドメインモデルに洗練させることを可能にしました。

洗練されたドメインモデル

洗練されたドメインモデル 洗練されたドメインモデル 洗練されたドメインモデル

複雑さ

システムの複雑さの観点で言えば、単一のデータストアのみを採用したアーキテクチャと比較した場合、複雑さは増したと思います。しかし、それぞれのデータストアに独立した耐障害性を獲得できたことを鑑みても、十分なトレードオフができたと思います。

メッセージングの管理

メッセージングについては、私たちが採用したDynamoDB Streamsとその周辺に用意されているエコシステムによって、多くのことが解決されます。DynamoDBへのデータ更新をトリガーにLambdaを起動し、参照系のためのリードモデルを永続化する。この一連の中で、キューの管理、エラーの管理、リトライの管理、こうした仕組みのすべてがマネージドなものとして提供されます。これらによって、私たちはメッセージングに関する多くの懸念から解放され、アプリケーションの開発に集中できています。

結果整合性の問題

一貫性に付随する課題に対し、私たちの今回のユースケースはとてもシンプルでした。それは、計測が完了した後で、参照系のデータに一切の変更が発生しないことに起因します。これは、参照系データが読まれるタイミングでの遅延さえ許容されれば、一貫性の問題は発生しないことを意味します。この観点において、結果整合性の問題によって、私たちを悩ますものはありませんでした。

将来の課題解決

参照系と更新系のAPI Count

参照系と更新系のAPI Count

これは、参照系と更新系のエンドポイントから、それぞれの呼び出し回数の推移を表したグラフです。上の実線が参照系で、下の実線が更新系です。多くのWebサービスに共通した性質かもしれませんが、ピークタイムを比較した時に、10倍以上の差がつく状態でした。

将来的なZOZOMATのシステム構成図

将来的なZOZOMATのシステム構成図

現在、参照系と更新系のサーバーは同居させた構成をとっていますが、将来的にこの図のように独立した構成をとることにより、以下の課題解決をもたらしてくれると思っています。

スケールの分離

参照系と更新系が独立することにより、それぞれのアクセスパターンに応じたスケールの戦略が選択可能となります。これにより、より細やかな弾力性が獲得できます。

サイジングの分離

参照系と更新系が独立することにより、それぞれのワークロードに適したサイジングが可能になります。参照系と更新系で必要なシステムリソースが異なる場合、より適切なリソース配置が可能になります。

耐障害性の分離

参照系と更新系が独立することにより、それぞれが疎になる箇所を拡げられます。先述のような参照系と更新系のデータストアに依存する耐障害性のみならず、より独立した耐障害性を獲得できます。

さいごに

今回は、ZOZOMATのバックエンドで採用したCQRSアーキテクチャについて紹介しました。初めてCQRSに取り組む機会として、規模的に小さなマイクロサービスから始められたことは、とてもいい機会だったと思います。CQRSは、提唱から今年でちょうど10年目の節目を迎え、特別に新しいものではありません。一方、その採用のために、これまでは様々な技術的障壁があったように思います。しかし、現在のAWSをはじめとするマネージドサービスにより、CQRSを実践するための周辺環境は十分に整っているように思います。

計測プラットフォーム部は、社内のPoC要素の高い新規事業を扱うことが多い部署という事情があります。一般的に、アーキテクチャおよび技術の選定は、様々な事情(安定性、コスト、構築までのスピード等)を考慮する必要があると思います。しかし、私たちバックエンドチームは様々な技術の先行事例を社内のナレッジとして残すことを使命に、多くの技術的挑戦に取り組んでいます。

計測プラットフォーム部バックエンドチームでは、ZOZOMATでより精度の高いサイズを推奨するバックエンドエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください!

www.wantedly.com

カテゴリー