そのマイクロサービスは正解なのか?

thumbnail

マイクロサービスも技術として成熟し、数々の失敗例とともにその欠点も多く語られてきました。
一方でコンテナ技術やクラウドサービスの発展により、マイクロサービスが昔より容易に実現できるようになってもきました。
そのためモノリシックなサービスは時代遅れのように捉えられ、安易にマイクロサービス化を進めてしまう例は今も多く見ます。

マイクロサービスは非常に繊細な技術であり、使い方次第では大きな技術的負債になり得る技術です。
しかし現実問題では、「別機能だから別サービスにする」のような安易なマイクロサービス化が蔓延しています。
そのためマイクロサービスからモノリスに統合するリファクタリングも過去に経験してきました。

本記事では、改めてマイクロサービス化を進めるにあたっての観点をまとめていきます。

マイクロサービス化をいつ進めるべきか

マイクロサービス化は銀の弾丸ではなく、メリットもデメリットもあります。
そのためメリットがデメリットを上回らない限り、マイクロサービス化を技術選択する合理性は薄いのではないでしょうか。
つまりマイクロサービスを技術選択するにあたり、どのメリットを求めるかが重要になってきます。

マイクロサービス化のメリット

ではマイクロサービス化のメリットは、なんなのでしょうか。
『マイクロサービスアーキテクチャ』にて記載されているメリットを軸に、私の考えをまとめていきます。

技術異質性

まずマイクロサービス化により、サービスごとに異なる技術選択ができることができると言われています。

たとえば何年も運用してるサービスなどは、コードベースが肥大化し、修正の影響範囲が膨大になってきます。
フレームワークや言語のバージョンをあげることもままならない状況に陥りがちです。
このような状況下でマイクロサービス化を進めれば変更も容易になり、大きな恩恵を受けられるでしょう。

状況的には、あまりない

一方で「この機能はこの言語/技術のほうが合っているから、サービスを分けよう」という状況は経験上あまりない印象です。
特に言語やフレームワークは分けることで、採用に苦労したり、開発できる人が制限されてしまうデメリットもあります。
「新機能は新しい言語/技術で開発して、モチベーションを高めたい」みたいな政治が絡む例もありますが、それだけを理由にしたマイクロサービス化には疑問を覚えます。

回復性

マイクロサービス化により、サービス全体の回復性が高まります。
具体的には、あるサービスに障害があっても残りのサービスは機能し続けることができるとのことです。

回復性はデメリットになり得る

しかしこのメリットを得るためには、障害を考慮した設計をする必要があります。
サービス間の依存関係が強いと、結局ほとんどこのメリットは得られることができません。

また特定のサービスだけ稼働し続けることは、データの一貫性が失われてしまうなど、予期せぬ不具合を誘発してしまうデメリットにも繋がります
たとえば「決算システムが落ちてしまうことで、商品の購入がタダで無限にできてしまう」ような状況になりかねません。

モノリスなサービスであれば、トランザクションを貼れば一貫性は担保できます。
マイクロサービス化を進めるのであれば、一貫性をどのように担保するかは大きな課題となります。

スケーリング

負荷状況に合わせ、サービス個別にスケールさせることができます。
高負荷サービスにおいてモノリスだとスケーラビリティの限界がくるため、これを感じたときにマイクロサービス化を進めるのは非常に納得感があります。

失われるスケーラビリティ

ただこれも、設計次第ではメリットは失われてしまいます。
たとえばBFFや神サービスが同期的に他サービスを呼び出すような構成の場合、全てのリクエストは1つのサービスを経由します。
そうなると、システム全体で見たときにスケーラビリティはモノリシックなシステムとあまり変わらなくなってしまいます。
サービスごとのスケーリングを機能させるためには、サービス間のメッセージングを非同期的に行ったり、リクエストが分散されるような設計が重要になります。

つまりただマイクロサービス化すればこのメリットは得られるわけではなく、そのメリットが得られるように意識する必要があります
しかし現実的にスケーリングの限界が始まる前にマイクロサービス化が進められ、スケーラビリティはあまり意識されることはありません。

デプロイ容易性

独立してデプロイ可能となることで、リリース間の差分を少なくすることができ、デプロイが容易になるとのことです。

ただこれも、サービスをまたぐ修正があるような場合むしろデプロイが困難になることがあります
たとえばデプロイのタイミングに制約がかかったり、反映の時間差で障害が発生してしまうようなケースです。
特に常に神サービスを経由しているような場合だとサービスをまたいだ修正が恒常化し、この問題は深刻化します。

組織面の一致

簡単にいうと、大規模コードベースに大規模チームだと生産性が落ちるので、小規模コードベースと小規模チームに分割しましょうという話です。
コンウェイの法則として知られるものですね。

またDDDの観点で言うと、コンテキスト(使われる言語)が変わる範囲でチームを分けるのも理由の1つになりますね。
OKRの観点においても、目指すものが変わる範囲でチームが分かれるほうが良いでしょう。

マイクロサービス化によりチームごとに独立してデプロイ・開発ができるようになります。
チームとサービスどちらが先かは鶏と卵ですが、「チームが違うから、サービス自体を分ける」という意思決定は合理的でしょう。

合成可能性

簡単に言うと、機能が他サービスでも再利用可能となるということです。

特に認証・認可は社内で共通化するとリソースの使い回しが効きやすくなります。
たとえばサービス間でユーザID・会社IDが共通化されてなくて、同じ社内サービスなのに横断的に分析できない例も多く見てきました。
Auth0などの外部サービスを利用するかのも含めて、少なくとも認証・認可をサービスから切り離すのは多くの場合において正解だと思っています。

合成可能性を発揮するには政治力が必要

一方で「将来的に使い回すかもしれないからマイクロサービス化した」という例も多く見ましたが、まさにYAGNIで技術的負債となってしまってるものがほとんどでした。
合成可能性を発揮するにはチームを横断して利用を強制する必要があり、社内文化や政治力に大きく依存します。

チーム間で需要があり、事前に使い回しの合意が取れた上で切り出すのなら良いでしょう。
しかし将来的に他チームに使用を強制することになるのであれば、それができる政治力と覚悟があるかは確認が必要です。

交換可能になるための最適化

これはサービスレベルで書き直しが効きやすくなるということです。
本書には『数百行レベル』という例示もありますが、FaaSレベルに独立できる機能であれば確かに気軽に切り離すのは賛成です。
たとえば過去に『Lambdaで最寄駅検索APIを作ってみた』という記事も書きましたが、この単位は切り出すのにちょうどよかったなと思っています。

交換可能性はそこまで重要なのか

一方で、現実は何千行・何万行もあるマイクロサービスがほとんどです。
そうなると、実質モノリシックなサービスと書き直しのしやすさはそうそう変わりません。

同じコードベースに存在すると同じ型の影響範囲内にいられるため不具合のリスクも減り、コードジャンプもできます。またテストもしやすいです。
結局のところサービス間の依存関係の方が重要であり、サービス間のメッセージングが増えるのであれば行数に関係なく開発コストは増えてしまいます。

そのためマイクロサービス化の理由として「コードベースを減らすため」というのには、私は同意しません

まとめ

まとめると以下のような技術的限界を感じたときに、マイクロサービス化を進めるのには合理性は感じます。

  • 特定の技術を使わなければ実現できない機能がある
  • 現在の構成にスケーラビリティの限界を感じた

しかしこのような状況は開発初期のサービスや、ベンチャーレベルではそうそう遭遇できる状況ではありません。
そのため、一番のマイクロサービス化の理由は『組織面の一致』となります。

つまりマイクロサービス設計は多くの場合、組織設計と相関を持つべきです。

おわりに

ただコードベースを分けたいだけであれば、同じサービス内でモジュールを分けるだけでも実現できます。
サービスレベルで分けるのには様々なデメリットがあり、またメリットの恩恵を受けるにはある程度の技術レベルを要求します。

多くの場合においてはモノリシックなサービスが正解となり、やるにしても認証・認可の切り出しか、FaaSレベルで特定の機能のみ切り出す目的で行うと良いでしょう。
少なくとも、その機能をマイクロサービス化するメリットを説明できるようでないと、技術選択するべきではないのではないでしょうか。


@dorarep
小学生の頃からフリーゲーム作ってました。今はフリーランスでフルスタックエンジニアしてます。