はじめに
マイクロサービスアーキテクチャ、流行ってますね。今日もそこらじゅうで「柔軟性が高いですよ」「モダンなアーキテクチャですよ」といった宣伝文句が飛び交っています。そのメリットは数多くありますが、実際に導入してみると「思ったものと違う」となることも少なくありません。
導入したはいいもののマイクロサービスアーキテクチャの恩恵を十分に受けられてない例、結局モノリスだった頃より苦労が多くなる例など、みなさんの身の回りでも思い当たる節があるのではないでしょうか。綺麗な設計思想であっても実際のシステムに適用することはなかなか難しいものです。
しかし、システムエンジニアの仕事は理想と現実を可能な限り近づけることだと思っています。本稿では、私の経験を元にマイクロサービスアーキテクチャの理想と現実のギャップについて掘り下げていき、マイクロサービスアーキテクチャ導入において陥りやすい罠と、理想に近づけるための対策について考えていきたいと思います。
この記事の主な対象者
- マイクロサービスアーキテクチャの理想と現実のギャップを知りたい人
- マイクロサービスアーキテクチャ導入における落とし穴を知りたい人
- 理想のマイクロサービスアーキテクチャに近づけるための対策を知りたい人
マイクロサービスアーキテクチャの理想
まずはマイクロサービスアーキテクチャの理想像から考えていきます。
マイクロサービスアーキテクチャの概念自体は古くから議論されているものであり、特定の誰かが考案したというよりは業界全体で徐々に浸透してきたアーキテクチャです。
ただ、本記事ではJames Lewis/Martin Fowlerの解説記事1を概念の原点としてマイクロサービスアーキテクチャの理想像を仮置きします。
この記事では、マイクロサービスアーキテクチャの利点の次のように述べています。
- サービスごとに別々の変更サイクルで管理できる
- サービスごとに独立してデプロイできる
- サービスごとに異なる言語、フレームワーク、技術要素を選択できる
- サービスごとに管理主体や責任チームを分割できる
要約すると、理想のマイクロサービスアーキテクチャとは、「サービス間の結合度が限りなく低く、サービスごとに自由で柔軟な管理が可能なアーキテクチャ」だと解釈できます。何と素晴らしい世界でしょうか。
しかし、実際にマイクロサービスアーキテクチャを導入してみると、とても理想通りにはいかないことが多いです。たいていの場合、必要以上にサービス間の結合度が高くなってしまい、俗に言う分散モノリスのような状態になります。
なぜそのような状態に陥ってしまうのでしょうか。具体的な例を見ていきながら、その原因と対策について考えていきましょう。
サービス分割単位の現実と対策
サービス分割はマイクロサービスアーキテクチャにおいて最も難しいテーマの一つですね。よくあるサービス分割の失敗例は、分割基準に一貫性が無いパターンです。
上記の例ではドメインベースの分割や機能ベースの分割が混在し、結果的にサービス間の境界線があいまいになっています。例えば新たに「ユーザ登録機能」を追加したいとなった場合、全てのサービスに機能追加する必要があるかもしれません。マイクロサービスってなんだっけ状態です。
ドメインベースや機能ベースの分割が悪いわけではないのです。ただ、境界を適切に分離できる分割基準を明確に決めておかないとすぐにサービスは密結合になってしまいます。
また、サービスを分割しすぎるパターンもよく見ます。
上記は極端な例ですが、マイクロサービスの規模が大きすぎるから調整したい、サービスの独立性を重視したいなど、細かく分割してしまうきっかけはいろいろあります。
そして実際にシステムを構築してみると、一部のサービスは機能が極端に少なく、分割する意味なかったね、というパターンです。結果として、チームをサービスごとに分割できなかったり基盤コストが高くなったりします。
特に基盤コストの増大は頭を抱えているアーキテクトも多いのではないでしょうか。サービスが増えるとコンピューティングリソース、ネットワーク通信量が純粋に増大する上、データベースまで分割するとさらに費用はかさみます。思わぬコスト増を避けるためにも、サービス分割は慎重に進める必要があります。
対策
初めは可能な限りサービスを大きい単位でくくりましょう。
モジュラモノリス等の構成によりマイクロサービスよりも大きい単位でシステムを構築することも選択肢の一つです。
バラけたマイクロサービスをまとめて再編成することは難しいですが、モジュラモノリスのような構造をマイクロサービスに分割することは(マイクロサービス化を見据えて構築しているなら)比較的簡単にできます。
初期段階で業務の規模感や将来的な拡張度合いを全て完璧に把握することはほぼ不可能です。そのため、サービスの大きさを可変にしておいて業務的な変化に柔軟に対応可能としておくと失敗しにくいかと思います。
費用の観点だと、サービスが増えてもコストが増えにくい言語を採用することも選択肢の一つです。JavaからGoにするだけで基盤コストを気にせずサービスを分けられるようになったという話も...。
共通化・標準化の現実と対策
マイクロサービスアーキテクチャでは共通化は諸刃の剣です。思想としては「サービスごとに別々に管理、設計できるのが嬉しいよね」なのですが、どうしてもシステム全体で統制したい観点はあります。
例えば、ログや分散トレーシングの方針はシステム全体で一致させておかないと運用負荷が高まります。マイクロサービスで重要となる機能(冪等性担保、サーキットブレイカー、etc)を各サービスで個別で作ることも非効率です。
よって実際にはある程度共通的な部品や標準化が必要になることが多いです。この際、どこまで共通化するか、どこまで標準化をするかという境界線が非常に重要になります。
過度な共通化はシステム全体の柔軟性を下げてしまいます。
例えば、ある程度共通部品として作ると決めた時点で開発言語は1つに決まってしまいます。言語が決まると共通的なCICD定義やビルドツールを作成することも多いですが、結果的にどんどん他言語の採用ハードルが高まってしまいます。
また、他の言語を利用することを考慮すると、標準化でログ形式やトレーシング対応などサービスが満たすべき要件を整理する必要があります。漏れなく要件を定義することは非常に骨が折れますし、漏れがあった場合システム全体として整合性や保守性が低下してしまいます。
他にも、共通化範囲が大きくなりすぎると、その部品や標準化ドキュメントを保守するチームの負荷が高まります。共通範囲はシステム全体を俯瞰してみる必要があるうえ、部品の実装難易度が高いことも多く、人員確保が難しい分野です。おいそれと各サービスを保守している人員に共通範囲を担ってもらいにくいところが難しい点です。
対策
共通化・標準化は可能な限り最小限の範囲に留めましょう。
特にシステム規模が大きいほどなんでも共通化したくなってしまうのですが、そこはぐっと我慢して本当に必要か吟味しましょう。
また、システム特性や組織形態によって共通化範囲は変わることに留意しましょう。大規模人員によるウォーターフォール開発では共通化範囲を大きくして全体の統制を重視するべきですし、技術に明るい人員が多くてアジャイル開発に慣れているような組織では共通化を最小限にして独立性を重視する方が適しています。
チーム分割の現実と対策
まず問題となりうるのは、チームとサービスが対応していない場合です。
エンドユーザの必要とする機能単位で開発できるメリットもありますが、
- チーム間で必要な調整の量が増える
- コードの所有権が無いため、コード品質が低下する
などのデメリットが大きいです。そのため、マイクロサービスアーキテクチャの思想と真逆のアプローチとされています。
対策
基本的には1サービス(または1モジュラモノリス)を1チームが管理するよう人員編成しましょう。
管理チームが責任をもってサービスを所有し、単独で品質管理、変更管理を実施可能なことが理想です。
複数のサービスに跨る機能が多いと感じたら、サービス分割の方針に再考の余地があるかもしれません。より疎結合なサービス分割を目指しましょう。
他には、チームの規模が適切でない例です。
理想はどのマイクロサービスも「2枚のピザを配りきれる程度」つまり6~10名程度のチームで編成することです。しかし実際はそこまで均等にサービスを分けられることは少なく、どこかでサービス規模とチームの規模に乖離が発生するものです。
当然ですが、一度取り決めた人員配置を再編成することは非常に難しく、一度乖離が発生したら適切なチーム編成に戻しにくいです。
サービスの規模とチームの規模に乖離がある状態のままだと、チーム負荷の不均衡によるパフォーマンスの低下が発生し、開発や運用が立ち行かなくなることもあります。
対策
根本的な対策はサービスの規模感を均一にすることです。
小さいサービスはモジュラモノリスでまとめたり、大きいサービスの分割を検討したりすることで管理対象の規模感を均一に保つことができれば人員の不均衡も解消します。
ただし、マイクロサービスアーキテクチャと組織体制は相関関係があるので、簡単に再編成できないのが実情です。その場合は負荷が高いサービスで自動化ツールを導入したりスポット人材を導入したりすることで地道な効率化を推進しましょう。
デプロイ単位の現実と対策
サービスごとにデプロイできるようマイクロサービスアーキテクチャを採用したのに、気が付いたら複数サービスを一括でデプロイしなければならないようになっていた...ということも多々あります。
デプロイ単位が大きいとロールバックが困難になったり本番リリースの手順が複雑化したりと、様々な弊害が発生します。柔軟なデプロイができるようにマイクロサービスアーキテクチャを導入したのに、モノリスの方が遥かに楽だった、となりかねません。
その原因も色々とあります。単純にサービス間の結合度が高くなってしまっている他、組織的に人員が足りなくてまとめてリリースした方が効率的と判断された、業務的にシステム全体の整合性を担保した状態で無いとリリース許可が下りないなど、組織的業務的な原因も考えられます。
マイクロサービスアーキテクチャは組織構造、コミュニケーション構造との密接な関係があるので、綺麗な設計をするだけではメリットを享受できないのが難しいところです。
対策
主な対策はサービス間の結合度を下げることと、デプロイ/リリースを視野に入れたマイクロサービスアーキテクチャを検討することです。
前者に関しては以下のような結合度を下げるための方法を検討しましょう。
- APIのバージョニング方式を検討し、1マイクロサービスの変更が他サービスへ伝搬しないようにする
- サービス間の連携にイベント駆動を導入し、直接の依存関係を減らす
- サービスやデータベースの分割方針を修正し、依存関係を可能な限り減らす
後者に関してはプロジェクトや組織ごとに条件が異なるので一般的な解決策はありません。要件定義の段階でマイクロサービスアーキテクチャを導入する目的とその条件を示し合わせておきましょう。
おわりに
最後に、マイクロサービスアーキテクチャを導入するにあたって「やってはいけないこと」を羅列しておきます。詳細はmicroservice-io4やその他記事56に書いているため、興味ある人は読んでみてください。
- 「マイクロサービスアーキテクチャの導入」を目的としてはいけない
- 明確な目的がないままマイクロサービスアーキテクチャを導入してはいけない
- 新規開発にマイクロサービスアーキテクチャを導入してはいけない
- シンプルで小規模なシステムにマイクロサービスアーキテクチャを導入してはいけない
- システムの一部だけでマイクロサービスアーキテクチャを導入しようとしてはいけない
結局システム開発に銀の弾丸はありません。アーキテクチャもただの手段でしかありません。だから我々はアーキテクチャの利点や欠点を正しく認識し、目的を達成するための適切なアーキテクチャを慎重に検討していくしかないのだと思います。
本記事が検討の一助となれば幸いです。
-
https://microservices.io/patterns/decomposition/decompose-by-business-capability.html ↩
-
https://scrapbox.io/kawasima/Microservices%E5%88%86%E5%89%B2%E5%A4%A7%E5%85%A8 ↩
-
https://blog.vte.cx/%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AF%E3%82%A2%E3%83%B3%E3%83%81%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-27fa8de772ff ↩