事業特性やチーム体制、スピードも考慮に入れた技術選定のプロセス
まず案1「各サービスを個別に分けて作る」というある意味ナイーブな案です。かなりスピーディに開発する必要があったため、複数サービスで並行作業がしやすくなるメリットなどが挙げられました。今後プロダクトが増えたときも、サービスを増やせば対応できそうです。一方、前述のとおり各サービスで共通して利用したいデータも多い中で、サービスを分けてしまうと「どのサービスがデータのオーナーになるのか」が決めきれないという問題が出てきました。また、分散サービスの常であるトランザクション管理や整合性の課題はどうするか、エンジニアチームの構造や規模と異なることで運用の負荷が上がらないか、トレーサビリティ・オブザーバビリティはどう担保するのかなど、考慮事項がかえって多くなることも懸念でした。
次に案2「全部をひとまとめにする」という対極の案です。案1で挙がったような懸念は解決できそうですが、アクセス傾向が異なるサービス(特に登降園)を一緒にしてしまうとスケーリングポリシーの管理の煩雑化や、サーバーやデータベースリソースの利用効率が低下してしまう懸念がありました。また、ビジネス要件やロジックが異なる全てのサービスを共通で持つことで、データベースが複数の興味領域を併せ持ってしまうことも気にかかりました。データベースをスケールさせる際にも最もデータ量が多いサービスに引きずられる形になります。
これら2つは、全部分けるか全部まとめるかといったある意味極端な案です。以降、その間で現実的なバランスを探っていきました。
例えば、案3「園児の活動系、保護者連絡系、その他で分ける」は、サービス共通で使う園児の活動に関するデータを切り出しながら、保護者連絡系は一つの大きな機能とみなし、それ以外は「園関連のデータ」とまとめる考え方です。案1よりはトランザクション管理がやりやすいですが、案2ほどではないもののアクセス特性が異なる複数の領域のデータを併せ持つ形になり、実装面で考えるとあまりメリットが大きくありません。
案4「登降園、保護者連絡系、その他で分ける」は、他のサービスに比べて特徴的なアクセス傾向(朝夕のスパイクアクセス)がある登降園サービスを分けるアーキテクチャです。案3がデータの関連性から「園児の活動系」を切り出したのに対し、こちらはアクセス特性を重視した形になります。これによってアクセス傾向の違いには対応しやすくなりますが、トランザクション管理の点では複数のDBの間で管理する必要があります。
案5の「登降園、その他で分ける」は、シンプルに登降園はアクセス傾向が異なるため、分ける考え方です。「その他」の部分には保護者連絡系、園児の活動系が全部入るため若干Fatな感がありますが、トランザクション管理はシンプルになり、アクセス傾向の違いにも対応しやすくなります。
これらについてチームで議論しつつ、何がいいと思うか、各エンジニアが投票するような形でメリット・デメリットを挙げていきました。
議論をアーキテクチャに落とし込んだ結果
議論の末、次のような構成を取りました。
- ゲートウェイとしてICTフロントエンドサービスを置く
- アクセス特性が異なる登降園サービスとその他の4サービスの二系統に分ける(案5)
- サービスごとにビジネスロジックを実装する「サービスレイヤー」を持つ
- データにアクセスする層を「バックエンドサービス」とし、統合した4サービスで一つ、登降園サービスで一つ、計2つのDBを持つ
図に起こすとこのようになります。
各サービス固有のロジックを持つところはサービスレイヤーとしてまとめ、それぞれ Application Load Balancer(ALB)とAmazon Elastic Container Service(Amazon ECS) on AWS Fargateで実装しています。AWS Fargateの上ではRuby on Railsが走っています。これによって、将来的にプロダクトが増えたときはこのサービスレイヤーを拡張することで対応できるようにしています。
データベースへのアクセスはバックエンドサービスとしてまとめ、こちらもやはりALBとAWS Fargateを使いつつ、データベースにAmazon Auroraを利用しています。「登降園とそれ以外で分ける」の分割案は、特にこのバックエンドサービスに反映されています。
このバックエンドサービスには、CRUDのような共通化されたデータアクセス用ロジックが実装されています。単純にDBだけを置くのでなくここにもAWS Fargateでアプリを走らせているのは、複数のサービスレイヤーのシステムから共通化されたデータにアクセスする際に直接データベースを共有するのは悪手であるという考えで、一定のビジネスロジックも考慮したバリデーションなどの制御も入れられるようにするのと、今後データ量やトラフィックが増加してバックエンドを冗長化する必要が出た際にもサービスレイヤーのシステムとしてはバックエンドの構成は気にしなくても良いようにするためです。
これによって、共通化すべきところはする、すべきでないところはしない、バランスの良いアーキテクチャを作ることができました。その結果、7月にリリースしてからユーザーも増え始めていますが、安定してサービスを提供できていますし、アップデートサイクルもしっかり回せています。
当初の検討材料の一つであった複数サービスの並行開発効率という点でも、ビジネスロジックの実装を行うのは主にサービスレイヤーであり、そこは分離されているため問題ありません。今後のプロダクト追加にも対応できるので、事業の拡充予定も立てやすくなっています。
これからも走り続けるために、泥臭い改善が必要
まだまだやりたいことがたくさんありますが、泥臭いところでいえば全サービスにおけるシステム監視の整備をしたいと思っています。
創業当初から走り続けてきている中で、初期から運用しているアプリもあれば、今回リリースした新しいアプリもあります。それぞれ構成も一致していない中でその都度ベストだと思う方法で監視機能を入れてきました。しかし、精度高く必要な情報が取れているかといえば必ずしもそうでなく、まだまだ改善の余地があるのが正直なところです。例えば利用しているツールを見ても、基本的にはAmazon CloudWatchを使っているところが多いですが、サービスによってはDatadogだったりSENTRYだったりと、統一できていないところがあります。こういった表立っては見えないところの改善が、サービスの継続的な進化や「保育の質の向上」というビジネス目標達成のためには不可欠だと考えています。
AWS ソリューションアーキテクトより一言
サービスの分割設計や粒度、その技術要素を考えるときに陥りがちな一つのアンチパターンは、「ビジネスの要件やチーム構成の実態を考慮せずにアーキテクチャや技術を考えてしまうこと」だと思います。
私もソリューションアーキテクトとして技術相談を受ける中で、例えばまだまだ小規模なチームなのに「マイクロサービスで構築したい」「そのためにKubernetesを使おうと思っている」といったお題をいただくことがしばしばあります。しかし、本当はマイクロサービスにすることが大事なのではなく、何を実現したいのかが先にあり、その手段としての各選択肢の優劣を並べて冷静に比較することが大事なはずです。
その点でユニファさんのアーキテクチャ選定プロセスは、挙げられるだけ選択肢を挙げた上で、技術的要件はもちろんのこと、事業やチームのコンディションなどを一つひとつ検討している、とても素晴らしいものだと感じました。また、こういった技術的な意思決定の過程、考えをステップバイステップで共有していただけるのは、私自身大変勉強になりました。
今回は7月にリリースした5サービスの話でしたが、ぜひ今後もユニファさんがどういう「選択」をしていくのか、チェックしていきたいと思います!(赤沼さん、また記事にして発表してください!)