オライリー・マイクロサービスアーキテクチャを読む ~マイクロサービスの概念とその利点~

 マイクロサービスアーキテクチャ、というやつは、数年前からいつの間にか僕たちにとって非常に馴染みの深い隣人になった。
 僕も彼の顔には見覚えがあるし、周りの人間も、彼とは長い付き合いだとでも言わんばかりに、彼のことを声高に賞賛し、時にこれでもかというほどこっぴどく貶した。
 しかし、いつの間にか僕たちの隣人としてうろつき回る彼が、一体何者なのか、どこから来たのか、なんのためにここに連れてこられたかを知っている人間はきっと、彼を僕たちの日常へ誘い込んだ、ごく一部の幼馴染たちだけなのだろう。

と、いうことで、いつの間にかみじかになってしまった割に、具体的な定義や設計思想、想定される使用ケースなど、きちんと理解している気が全くしない、件のマイクロサービスアーキテクチャとやらを、この際だからきちんと勉強してやろうと思い立って書き始めました。
題材は先日帰国した時に購入したオライリーのマイクロサービスアーキテクチャと、プロダクションレディマイクロサービス。

ということで、勉強した内容を自分用メモもかねて、ここにまとめていこうと思います。
太字で囲ってある箇所は正規の引用ではなく、解釈し要約した物を書いています。もし間違いがあればご指摘ください。

第1章 マイクロサービス

マイクロサービスとは

マイクロサービスの定義

マイクロサービスの定義 : マイクロサービスとは協調して動作する小規模なサービスのことである。
まぁ定義は大事だよね。これは割とイメージと合ってる。

モノリシックなシステムの問題点と凝集性について

モノリシックなサービスの問題点:
モノリシックなシステムは大規模化すればするほど変更が必要な箇所はわかりにくくなる。
モノリシックな大規模システムは各機能を抽象化しモジューライズすることで管理を容易にしようとする手法が一般的だが、この手法を取っていても各プロセス内の指針がない境界は損なわれてしまうことが多い。
結果として、類似の機能が各所に散在し、修正や実装は困難になりがちである。

これもまた経験的に真であるとわかりますね。
いくらモジュールとして独立させ、ドメインの境界を定義し設計をしていったとしても、現実の開発現場ではスケジュールや技術力の不足に起因する現場都合の例外的な実装も増えて来ます。
また、設計がシステムそのものの複雑さに耐えられなくなり最終的に当初の設計理念に沿わない実装も徐々に増加してくることは珍しくないでしょう。

モノリシックなシステムでは、機能を抽象化しモジュール化することで凝集性(関連するコードを同じ箇所に集めるようにすること。)を高め修正や拡張が複雑になりすぎないようにすることが多い。
この凝集性の概念はマイクロサービスでも同様に重要であり、凝集性を高めるというアプローチを独立したサービスに対して行う。

ふむふむ。 本題ではないのですが、面白いことが書いてありますね。
凝集性はRobert C Martinが単一責任原則について語ったエッセイの中で紹介された定義が強く認識されてるみたいなんですが、その定義というのが、

凝集性の定義: 変更する理由が同じものは集める、変更する理由が違うものは分ける。

なんだそうです。
DRY原則を徹底しすぎて大量のコードから参照を持ったせいで変更が加えづらくなったクラスとか結構経験があるので、なるほど納得です。
このエッセイはオライリーから出てる「プログラマーが知るべき97のこと」というエッセイ集に載ってるみたいです。

マイクロサービスにおける凝集性

マイクロサービスで凝集性を実現するためのアプローチ: サービスの境界をドメインコンテクスト(ビジネス)の境界に合わせ、サービスの責任範囲を明示する事でサービスが過度に肥大化するのを防ぎ、サービスを大規模にしようとしてしまう衝動とそれに付随して発生する複雑さを回避する。

まぁ認識通り。

サービスの大きさに関する指針

サービスが大きすぎるかどうかの判断基準に関して
・ドメインの複雑さや言語の使用によって行数やコードの量は大きく変化するためコードの量はあまり関係ないが、チームのサイズとサービスの大きさのバランスは一つの指針になる。
→チームで全てのソースが管理できなくなった場合、サービスを分ける事を考えた方が良い。

・感覚的な判断も指針となり得る。メンバの多くが大きすぎると感じているときは実際に大きくなりすぎている可能性が高い。(ほんとか?)
・一つの定義として、Jon Eavesは二週間で書き直せるサイズをマイクロサービスの基準として特徴付けている。 → あくまで彼の経験則

ドメインの境界に合わせるに比べてなんか頼りない感じの例示だな、おい。

サービスの大きさとメリット・デメリットの関係

サービスが小さくなればなるほどマイクロサービスアーキテクチャの利点と欠点が最大化される。小さければ小さいほど相互に強調し合う必要がある箇所が増えるため複雑性も増す。 この複雑性にうまく対応できるようになると、よりサービスを小さく分割できるようになる。
確かにサービス間の協調の方法になれてない時はすごく処理が冗長に感じたし、設計も自信が持てなかったとこはあるな。

マイクロサービスの自率性

この本で説明されるマイクロサービスは独立したエンティティなのでサービスは必ずしもPaasにデプロイされるとは限らない。

まぁそれはそうでしょうね。サービスの中には一部オンプレで動くサービスやコンテナの中で走り続けるOSプロセスが合ったっていいわけだし。

それぞれのサービスは同一のマシンに詰め込まないように努めるべきである。 この分離によって、機能の呼び出しにオーバーヘッドがかかることは予想されるが、一方で構成が簡潔化し分散システムをより構築しやすくなる。 また、各サービスが独立することによって、これらのオーバヘッドを緩和するより新しい手法を導入しやすくなる。

ここでいうマシンは、かなり定義が曖昧なことはきちんとメンションされてますね。
ソフトかハードか、ソフトにしてもコンテナレベルなのかVMレベルなのかとか、マシンに関する定義はかなりあいまい。
あとで詳しく説明してくれるらしい。
ソースコードレベルならどれでも大きな差はない気がするけど、実行時にどういう振る舞いをするかはこのマシンの定義が大きく関わってくる。と思う。

マイクロサービスの分離と協調

サービス間の呼び出しは、全てネットワーク経由で行うことで、サービス間の分離を強制し、サービス同士が密結合することを防ぐ。 それぞれのサービスはAPIを公開し、サービス同士の協調はAPI経由で行う。

これも認識してた通りかな。ソースレベルでの結合が許されない以上、自然にAPI経由で連携しましょうという話になる。
この後の話題にも関係してくるけど、入力出力が一般的なフォーマットに則っていれば言語や基盤も関係なくなるので、各サービスが使用する技術という意味でも独立できる。

APIの設計とマイクロサービスの独立性

APIを公開する際、何を公開し何を隠すかについてはきちんと設計しなくてはいけない。それぞれのコンシューマの変更なく独立して変更・デプロイできなくてはいけないため、あまりに詳細な共有を行いすぎると、結果として自身のサービスとAPIをコンシューム(利用)するサービスが密結合してしまうことになりかねない。 マイクロサービスの黄金律は他のサービスには何も変更を加えずに単独で変更・デプロイできることであり、この黄金律に反する設計ではマイクロサービスのメリットを十分に享受することができない。

はい。おっしゃる通りです。特にコメントはないです。

マイクロサービスの利点

マイクロサービスの利点は分散システムの利点であり、サービス指向アーキテクチャや分散型アーキテクチャの利点をより拡大するものである。

技術異質性

それぞれのサービスがマイクロサービス化する事でそれぞれのサービスは好きな技術スタックを選択できるようになる。 それぞれのサービスが技術的に独立した基盤を持てる場合、それぞれのサービスは自身にとって最適な基盤を選択できることに加え、最新の技術を他のサービスに対して影響を与えることなく検証・使用することができる。

これは重要ですよね。うちはEC2にコンテナ立ててRDB使ったクラサバで、うちはヒストリ解析が主だからサーバレスでいいや、DBはDynamoDBでみたいな選択もできるわけなので。
また独立して最新の技術試せるっていうのも大きくて、大規模開発ではフレームワークやミドルウェアのバージョン上げるだけでもひと仕事なんですよね。
独立して最新の技術を検証・使用できれば最新技術のメリットを享受できるだけじゃなくて、組織としての技術スタックも積まれることになるので。

ただし、複数の技術の採用にはオーバヘッドがかかるため、NetflixやTwitterなどの企業はJVM上で動くことを条件づけるなど、制約を与えることで調整を測ることも多い。  

まぁそうですね。Google並みに開発者が揃ってて多くのライブラリに対する保守が行えるなら話は別かもしれないけど。(あくまでクライアントライブラリの話です。)

回復性

サービス間がマシンレベルで分離されることにより、そこに隔壁が生まれ、一箇所で起きた問題が他のサービスに波及しづらくなる。
ただし、サービスの一部がダウンすることで協調動作ができなくなり、エンドユーザが一部の機能が使えなくなることがある。
サービスの開発者はその影響に対して対応した実装を行い、またサービス提供者はどのように復旧するかをあらかじめ想定しておく必要がある。

そうですね。マイクロサービス同士は疎結合とはいえ、どうしても相互依存する形になるので、どこか一箇所で問題が起きた場合に自分のサービスがどのような挙動をすべきかをあらかじめ考えて置かなくてはいけませんね。
先日うちの現場でもこの話をしました。

スケーリング

モノリシックなサービスの場合全てのサービスのリソースを一気にスケーリングしなくてはいけないが、マイクロサービスの場合、サービスがドメインレベルで分離しているため、負荷の高いサービスだけをスケーリングすることができる。

特にコメントはないです。その通りですね。

デプロイの容易性

サービスの単位が大きくなればなるほどビルド・デプロイにかかる時間は大きくなる。ビルド・デプロイの時間が長くなれば必然的にデプロイ回数は減るため、デプロイ間の変更が大きくなり、継続的統合(CI)が難しくなる。
マイクロサービスにおいて、デプロイの対象はサービスだけなのでビルド・デプロイともに時間が節約され、より細かい単位でのデプロイが可能になる。

次に上げるメリットとも関わるんですが、アジャイルを効率的に回すためには、こまめなデプロイを行い変更箇所を小さくすることが求められますね。
ごもっともです。

組織面の一致

モノリシックな開発現場では単一のチームとして開発することは難しかったが、サービス規模が小さくなることで、開発チームとサービスの一致が可能になる。
小規模コードベースで働く小規模なチームで開発した方が効率がいいことは自明であり、開発効率を最適化することができる。

本文の中ではアジャイルという言葉は使われていませんでしたが、小規模チームによる小規模コードベース開発といえば、やはりアジャイル開発が真っ先に頭に浮かびます。
まああんまりアジャイルアジャイル言ってるとアホっぽくなるのであれなんですが、要するに機動性の高い小規模なチームで開発を行えれば効率がいいですし、大規模な開発現場でも独立した小規模チ開発ームをたくさん持つことでそのメリットを享受できるようになりますよということだと思います。

プラットフォームに対する柔軟性

PC web, Mobile web, Mobile Native, Wearable, Tabletなどサービスを提供するチャネルは凄まじいスピードで拡大し、サービスはそれらのチャネルの拡大に対して柔軟に対応していかなくてはいけない。
モノリシックなサービスの場合、公開される接合部は荒く大きいくくりのものが多くなりがちだが、個別にサービス同士がAPIを経由して協調し合うマイクロサービスにおいてはサービスの提供する接合部は粒度が小さく機能の再利用や新規チャネルとの統合がしやすくなる。

これに関しては正直良し悪しかなと。サービスの粒度が小さくなれば当然個別のAPIの粒度も細かくなる。APIの粒度が細かくなればクライアントの実装もシンプルになる代わりにコードの行数も増えてくる。
ここら辺はAPIの設計の話なので、Real World HttpのClient sideから見たWebapiやWeb API: The Good Partsを読んだ方がはやい。

交換可能性

機能がサービスごとに分割されていることで、APIスペックを変更しない限りにおいて、サービスのコードは容易に置き換えが可能になる。
巨大なレガシシステムのように、一部の機能を置き換えることで起こりうるあらゆる想定不可能な不具合リスクを気にすることなく、部分的な書き換えや置き換えが可能になる。
またコードベースも小規模で、数百行程度に収まるようなものであれば、開発者が愛着を持ってしまい書き換えができなくなるという事態も防ぐことができる。

個人的に前半部分は今まで言ってたことの言い換えって感じだなぁと思ったのですが、後半の部分大事ですよね。
今作ってるものを捨てて新しいものに置きかえようってなったとき、どうしても愛着のあるシステムには抵抗があるものですし。

以上。以下途中でメンションした参考文献。
では!