こんにちは、プロダクト開発部の中田です。
今回は、イベント連携基盤の技術選定を行なった話をご紹介します。
思った以上にボリュームが多くなってしまったので、以下のように複数回に分けて投稿します。 それぞれ投稿でき次第、記事のリンクを追加予定です。
- イベント連携基盤の技術選定をした話 〜背景・課題編〜 ←今回はこの話
- イベント連携基盤の技術選定をした話 〜EventBridgeさわってみた編〜
- イベント連携基盤の技術選定をした話 〜Axon + MSKさわってみた編〜
- イベント連携基盤の技術選定をした話 〜比較・結論編〜
最初は、「背景・課題編」ということで、イベント連携基盤を導入しようと考えた背景や課題をまとめたいと思います。
本記事の概要
複数の機能間や、モノリスおよびマイクロサービスのノード間をまたいで、ドメインイベントを非同期に連携するためのイベント連携基盤の導入検討を行ってきました。
MessageBrokerとなるコンポーネントの候補として、主に以下の2つを比較検討しました。
- Amazon EventBridge(以降、EventBridgeと記述)
- Amazon Managed Streaming for Apache Kafka(以降、MSKと記述)
本記事では、検討にいたる背景や課題と、イベント連携基盤に求める要件をまとめます。
背景
非同期のイベント連携が有効と思われるユースケース
コドモンのプロダクトは、こども施設向けや保護者向けの様々な機能を提供しています。 機能拡張に伴い複雑さを増すアプリケーションの保守性を維持・改善するため、DDDやクリーンアーキテクチャの考え方を取り入れて、リファクタリングやリプレイスを進めています。
既存機能の多くはまだモノリスアプリケーションに含まれていますが、モジュラーモノリスへの整理や、モノリスをマイクロサービスに分解する取り組みを行なっています。また、新機能については、初めからモノリスと切り離したマイクロサービスとして開発することも多くなってきました。
そんな中、境界づけられたコンテキストの分割を検討したり、機能改修の実装方法を検討する際に、複数の機能やノードをまたぐ連携(それゆえに複数の機能開発チームをまたぐ連携)が必要になるような要件を聞くことが増えてきました。
例えば、以下のようなものです。
また、コンビニ払いの例のように、ある機能やマイクロサービスの更新を、複数の機能が検知したいこともあります。 この電子決済基盤を利用する機能は、「用品販売」「いつでも請求」など複数存在し、今後さらに増える想定です。
これらの例に出てくるような各機能は、それぞれ別の機能開発チームが担当しているものとします。 (チーム体制や担当機能は随時見直しているので、今後は変わっているかもしれませんが)
このような場合、機能間およびチーム間の連携を疎結合に保ったり、プロダクト全体の可用性を高めるため、Pub/Sub等の非同期で結果整合性を前提とする連携方法が適していることがあります。
コドモンの現状
上記のような要件が出てきたときに、各チーム・機能ごとに都度個別に1から連携方式を考える必要がある状況では、開発の負担が大きく、スピーディーな開発と価値提供を阻害する要因となりえます。
そういった状況では、開発実績が多く実装難易度の比較的低い、同期的なAPIやバッチ処理による連携が選択されることも多いです。 その影響で機能間・チーム間の結合度が高くなってしまうことがあります。
例えば、前述の登園予定の削除の例では、以下のようなことが起こりました。
- 送迎バス運行管理機能の都合で、登降園管理機能のAPIの改修が必要となり、登降園管理機能の担当チーム内の開発優先度の見直しが必要になる
- 結局、登降園管理機能の担当チームではすぐに対応できないので、送迎バス運行管理機能の担当チームが登降園管理機能のAPIの改修を行う
このような対応は、見方によっては、チームの枠を超えた柔軟な連携としてポジティブに見ることもできますが、開発組織の規模が拡大していくフェーズでは、コミュニケーションコストの増大やドメイン知識の分散などが問題になってくると思われます。
プロダクトとしても、送迎バス運行管理機能の障害時に、登降園管理機能も使えなくなる、といった影響が発生するリスクが生まれかねません。
また、この例の機能は、どちらもモノリス内で実装されている機能です。 モノリスはPHPで実装されており、特にフレームワークも利用していない状態で、同期的なWebAPIの提供を前提とした構成になっています。そのため、非同期処理を採用しようにも、そのためのデーモンプロセスを別途起動することなどを考える必要があります。
このような背景があり、複数の機能・チームが、モノリスとマイクロサービスの双方から容易に利用できる共通のイベント連携基盤がほしいと考えました。
イベント連携基盤への要件
次に、イベント連携基盤、および、それを利用するアプリケーションに対して、求める要件を整理したいと思います。
基本要件
まず、既存のモノリス(PHP)やマイクロサービス(Kotlin、Go)からイベント連携基盤を利用し、Pub/Subの仕組みが実現できることが大前提となります。
そして、ユースケースやイベントの種類、イベント量の増加に対応して、スケールできることが必要です。 なお、ドメインイベントの連携を主眼においているため、大量データの大規模ストリーム処理というよりは、比較的少量かつ多種のイベントを扱うことを想定しています。(もちろんイベントの種類によっては大量データになることも考えられます。)
そのうえで、できればコストを抑えつつ、小さくはじめられることが望ましいです。
ちなみに、コドモンでは基本的にAWS上でシステムを構築しており、現時点で他のクラウドサービスへの移行やマルチクラウド化は考えていないため、AWSのサービスの利用を第一に考え、移植性はいったん重視しないことにしています。
RASIS
その他の非機能観点については、RASIS(Reliability、Availability、Serviceability、Integrity、Security)を参考に、考えを整理をしてみたいと思います。
Reliability(信頼性)
冗長化を行い、ノード障害やAZ障害に対してサービス継続できる構成にすることが必要となりますが、このあたりはAWSのサービスであれば多くの場合カバーされている部分だと思うので、あまり比較要素にはならないかもしれません。
なお、コドモンは現時点ではマルチリージョンには対応していません。
Availability(可用性)
Pub/Subの仕組みを入れること自体が、可用性を高めることを目的の1つとしています。 PublisherおよびSubscriberのどちらか一方で障害が発生した際に、もう一方への影響を抑え、プロダクト全体として利用可能な範囲を広く保つことを目指します。
また、MessageBroker自体に障害が発生した場合、Publisher/Subscriberアプリケーションは(イベント送受信以外の部分は)継続動作するように実装できることが望ましいです。
アプリケーションとMessageBrokerのいずれも、障害中のイベントの蓄積や復旧後の自動リカバリができると、復旧時間を短くできます。
例えば、MessageBrokerにイベント送信を行うAPIがあり、イベント送信のみが失敗した場合、APIレスポンスはいったん正常に返却したうえで、裏ではイベント送信のリトライを続けて障害復旧後の自動リカバリを行なえると、結果整合性が保てます。
Serviceability(保守性)
保守性も特に重要な点です。 MessageBrokerとなるサービス自体の保守性と、MessageBrokerを利用するアプリケーションの保守性の両方を考える必要があります。
まず、MessageBrokerについては、バージョンアップ等の頻度または影響が少なく、難易度が低いことが望ましいです。 そのため、なるべくサーバレスやフルマネージドのサービスを選びたいところです。
機能開発の負担を下げることも目的としているので、イベント連携基盤を利用するアプリケーションについても、利用するための実装が容易であることが求められます。 少なくとも、機能ごとに個別実装する部分はできる限り容易にしたいところです。 難しい部分はなるべくない方が望ましいですが、もしあった場合も、それらを隠蔽した共通部品を用意したり、プラットフォームやイネイブリングでカバーすることができるとよいです。
また、複数のチーム・機能で利用するため、チーム間および機能間で責務の分離とリソースの分離を行い、疎結合にできる必要があります。
障害対応や問い合わせ対応において、イベントの配信状況の調査や、リカバリのための蓄積・再配信がしやすいことも重要です。
Integrity(保全性)
保全性としては、非同期のイベント連携では特に、イベントの配信セマンティクスや順序保証が重要になると思います。
ドメインイベントの連携においては、ほとんどの場合でイベントの欠損は許容されないので、at least onceの処理ができることは必須となります。 一方で、イベントの重複も許容しないexactly onceを厳密に実現するには、イベント連携基盤に限らずシステム全体での実装難易度が上がると思われるので、共通のイベント連携基盤の要件としては必須としないことにします。 多くの場合は、at least onceにしてSubscriber側が冪等(べきとう)処理を行うことが機能実現と実装コストのバランスがよいと思われます。
また、イベントの順序保証については、ユースケース次第では不要だったりSubscriber側のハンドリングでカバー可能なことも多そうです。とはいえ、順序保証が必要な場面ではシーケンス全体で順序保証が必要になるので、イベント連携基盤を通すことで順序保証が崩れるようだと利用が難しくなります。 今後、イベント連携と組み合わせたEventSourcingの導入を行う場合は、順序保証がより重要になると思われます。
Publisher/Subscriber間の状態不整合の発生抑止や検知ができることも重要ですが、これについては、イベント連携基盤単体では難しいと思われるので、特に不整合の影響が大きい機能では、リコンサイルの仕組みなどを別途検討する必要があります。
Security(安全性/機密性)
MessageBrokerには、コドモンのシステム内からのみアクセスできるものとし、外部からの直接のアクセスは許可しないようにしたいです。 他社や行政との協業のため、社外システムと連携する案件も増えてきていますが、その場合は外部連携のための専用コンポーネントを中継し、アクセス制限を行いながらイベントを連携したいと考えています。
また、イベントデータ保存時は暗号化を行う想定ですが、データ項目単位での暗号化までしようとすると利用アプリケーション側の実装や運用の負担が大きくなります。個人情報などは削除要請への対応も考える必要がありますが、MessageBrokerの特性的に一部のデータだけの削除が困難なことも考えられます。 イベントデータには個人情報・機密情報やそれと直接紐づく情報は含めない方針とすることも検討が必要かもしれません。
MessageBrokerの候補
冒頭でも記載したとおり、MessageBrokerとなるサービスの候補として、EventBridgeとMSKの2つを検討しました。 本記事では、これらの概要と検討開始当初の期待を簡単にまとめておきます。
サービスの選定においては、下記資料などを参考にし、共通のイベント連携基盤として、個別の機能・ユースケースごとのリソース作成・管理の負担が抑えられると思われるものをピックアップしました。
https://speakerdeck.com/fatsushi/event-driven-architecture-on-aws
EventBridge
- サーバレスなイベントバスで多種のイベントのPub/Sub連携が実現できる
- カスタムイベントバスにアプリケーションからイベントを発行できる
- 配信先としてAWSサービスとの統合が容易で、API destinationsによるAPI連携も可能
MSK
- メッセージング基盤として広く利用されているOSSであるApache Kafkaのフルマネージドサービス
- AxonFrameworkと統合することで、イベント駆動のアプリケーション実装が容易にできる
- ストリーム処理基盤やデータ連携基盤としても利用可能
検討の取り組み方法
機能開発において、イベント連携の仕組みは機能の実現上は必ずしも必要ではなく、従来のAPI・バッチ等の方式でも実現可能なことは多いです。 むしろ、従来の方式のほうが実装における不確実性は低いので、多くの開発案件が控えている中で、イベント連携基盤の優先度をあげることが難しいことも予想されました。
ですが、背景に記載したような課題は今後じわじわと増えてくると思われるので、備えは必要だと考えます。 (というのは建前で、本音は個人的にやってみたかったというのも大きいです。)
コドモンでは、「0.5投資制度」といって、1週間のうち半日程度を自由に使って自己投資を行うことができます。 この制度を利用して、有志メンバー数名で検討を進めてきました。 短い時間を使って少しずつ進めてきたので期間は長くかかってしまいましたが、開発の優先度や期間と工数の制限などに縛られずにじっくり進めることができたのはよかったと思っています。
また、検討の過程では、AWSのアーキテクトの方や社外の有識者の方に相談させていただくことができたのもありがたかったです。
まとめ
コドモンでイベント連携基盤がほしいと感じた背景を説明し、イベント連携基盤に求める要件をRASISを参考に整理してみました。
そのうえで、イベント連携基盤のMessageBrokerの候補として、EventBridgeとMSKを挙げました。
実際にEventBridgeとMSKを触ってみて気づいたことや、比較検討の結果については、次回以降の記事でお伝えしていきたいと思います。