見出し画像

ひとりABD~マイクロサービスアーキテクチャ 4章 統合編~

こんにちは。HITO-LinkCRM開発チームのk.miuraです。
ギリギリまで「恋について本気出して考えてみた」という記事を書こうか迷ってましたが、今回は「ひとりABD」ということでやってみたいと思います。

ABDについては、公式サイトをご覧ください。
平たく言うと、みんなで本を読んでまとめてプレゼンして共有する会です。
HITO-Link部でも昨年はよくやっていたのですが、コロナでフルリモートに移行してからはなかなかやる機会に恵まれない状況です。
ということで、今回は「ひとりABD」と題して、勝手に本を読んで一部をまとめてアウトプットするという取り組みをしてみたいと思います。
今回はオライリーのマイクロサービスアーキテクチャから一番大事そうな4章を読んでみました!今後も何章かピックアップして続けられたらいいなって感じです。
(これからマイクロサービスアーキテクチャに挑戦しようとしているエンジニアがざっくばらんにまとめている記事です。解釈違いなどあればやんわり教えてください…!)

(編集後記)
書く前から気づいてましたが、こんな記事を読むくらいなら原著を読んだ方が5000倍いいです。買って読みましょう!


理想的な統合技術の探索

スライド2

マイクロサービスの統合技術を考える際に、技術に求める重要なポイントが上記の4点です。

・破壊的変更を回避する
サービスを変更すると、それを呼び出すコンシューマ側にも変更が必要な場合がありますが、こういった事態は可能な限り避けたいものです。
たとえば、サービスから送信するデータに新しいフィールドが追加されたとて、コンシューマには影響を与えないようにするべきです。

・APIを技術非依存にする
マイクロサービス間の通信に使うAPIは技術非依存であるべき。マイクロサービスの実装が特定の技術スタックに決まってしまうような統合技術は避けましょう。

・コンシューマにとって単純なサービスにする

コンシューマが使いやすいサービスが理想。しかし、クライアントライブラリを提供するような場合は、コンシューマ側の手間を省けても、サービスとの結合を進めてしまう代償があります。

・内部の実装詳細を隠す
結合度を上げてしまうため、コンシューマを内部実装に結合させたくありません。

共有データベース

スライド3

複数のサービスから共有のデータベースにアクセスするという統合形態は避けるべきです。
スキーマの変更が必要な場合、他のサービスが使用しているスキーマを壊さないように慎重にやる必要があります。
また、コンシューマがDB側に技術依存してしまいます。RDBでデータを格納していたものが、あとあと非RDBの方がよいとわかった場合も、変更する決断を下すのは難しいでしょう。
さらに、顧客の振る舞いに関するロジックはどこにあるのでしょうか、それぞれのコンシューマが関連するロジックを持つことで凝集性を失います。
データベース統合では、強い凝集性と疎結合の両方を失います。いかなる代償を払っても避けてください、とのことです。

同期と非同期

スライド4


サービス間の通信について、大きく同期と非同期の2つがあります。大まかな対比は表のとおり。
一般的には同期の方が考え方としてわかりやすく、非同期の方が考慮すべき要素が多く複雑になりやすいです。

オーケストレーションとコレオグラフィ

スライド5

オーケストレーションコレオグラフィという二つのアーキテクチャ形式があります。
オーケストレーションはひとつの中枢となるサービスが、一連の関連するサービスをリクエスト/レスポンス呼び出ししていく方式です。
このパターンだと中枢のサービスがハブとなりどんどん大きくなっていきます。
また、この図でいうと、顧客サービスは顧客が登録されたときにどのサービスを呼び出すべきなのか全て把握していなければなりません。

コレオグラフィはイベントベースの連携手法です。顧客サービスは各サービスを呼び出す代わりに、顧客作成イベントを発行します。
他のサービスはそのイベントをサブスクライブし、イベント発生時に適切な処理を行います。
欠点としては、顧客作成→各サービスの呼び出し、というビジネスプロセスが明示的ではなくなり、システム内の暗黙的な関係になることです。
また、それぞれのサービスの処理が適切に行われたかどうか監視する仕組みが必要です。
しかし、コレオグラフィのほうが一般的には疎結合で柔軟性があり、変更を受け入れやすいシステムにすることができます。

リモートプロシージャコール(RPC)

ローカルで呼び出しを行い、別のリモートサーバでそれを実行するテクニック。
クライアントスタブとサーバスタブを作成できるRPC実装を使うと、ごく短期間で開始できます。
通常のメソッド呼び出しを行うだけでそれ以外のすべてを理論上無視できるのが大きな利点です。
しかし、それを上回る欠点を含む実装もあります。

・JavaRMIのように、特定の技術プラットフォームに強く結びつくいたRPCメカニズムがある場合あり、技術的統合が起こる
・ローカル呼び出しはリモート呼び出しとは異なるはずだが、RPCリモート呼び出しの複雑さを隠しすぎている場合があり、開発者が知らずにリモート呼び出しをすることもありうる
・オブジェクトのフィールド追加削除などインタフェースの変更時に、新しいサーバとクライアントを同時にデプロイする必要ある場合など、変更に対して脆弱

上記のような欠点はありますが、ProtcolBuffersやThriftのような最新の機構はクライアントとサーバの同時リリースの必要をなくしています。
データベース統合と比べるとリクエスト/レスポンス方式の選択肢としてRPCは改善してはいます。

REST

RESTに関する詳細な説明は、ググれば出てくるのでこのnoteでは省きます。
RESTはHTTPと相性がよいです。HTTPはサポートツールや技術の大規模なエコシステムをもたらしてくれる点で有利です。

・アプリケーション状態エンジンとしてのハイパーメディア(HATEOAS)
RESTには「アプリケーション状態エンジンとしてのハイパーメディア(HATEOAS)」の概念も導入されています。
ハイパーメディアはコンテンツに様々な形式の他のコンテンツへのリンクを含む形式です。このリンクを利用してコンシューマがAPIを検出できます。
クライアントサーバ間の結合を減らすことに役立ちらしいですが、人類には早すぎてあまり使われていないようです。(自分も読んでて咀嚼しきれず…)

・JSONかXMLか他の何かか
HTTP上のRESTでは様々なテキスト形式が扱えます。
XMLはハイパーメディアコントロール形式のlinkを標準サポートしている、XPathが使える、という点で筆者はJSONよりXML推しです。

・HTTP上のRESTの欠点
HTTPRESTのクライアントスタブはRPCより作成が難しい。
WebサーバフレームワークがHTTP動詞のすべてをサポートしているわけではないものもあり、RESTの形式が制限される場合があります。
また、性能的にはRPCの方が低遅延です。

非同期イベントベース連携の実装

スライド8

イベントベースの連携の実装方法としては、RabbitMQのようなメッセージブローカーを使う方法や、AtomというREST準拠仕様のフィードをHTTPで発行する方法があげられます。
ちなみにHITO-LinkCRMではメッセージブローカーとしてAWSのSNSとSQSを利用しています。
メッセージブローカーはイベントの発行とサブスクライブの両方に対処してくれます。
メッセージブローカーはサービスとは別のシステムなので、このインフラを用意して稼働させ続けるのにはそれなりのコストがかかりますが、一度稼働できれば、疎結合のイベント駆動アーキテクチャを効率的に実装できるようになります。
メッセージブローカーに知性を詰め込みたくなりがちなので、あくまでただのミドルウェアとして扱うように心がける必要があります

Atomフィードは関連ライブラリを使えるのが便利ですが、HTTPベースなので低遅延は得意ではありません。
Atomフィードを適切に扱うために、メッセージブローカーなら簡単にできることを独自実装してしまうことがありがちです。結局メッセージブローカーと同じ要件を求めるなら、メッセージブローカーの利用に切り替えるが吉。

・非同期アーキテクチャの複雑さ
非同期アーキテクチャには次にあげるような複雑さがあります。
非同期レスポンスが返ってきたときの処理をどうする問題、ひとつの不正なリクエストによって全てのワーカーが停止するような「壊滅的フェールオーバー」パターンの発生、処理を失敗したメッセージの送り先となるキューの用意とそのメッセージの閲覧とリトライする仕組み…etc
また、非同期アーキテクチャを実装する場合には、監視の章で述べられていますが、相関IDを利用することで、サービスをまたいでリクエストを追跡するような仕組みも検討するべきです。

状態マシンとしてのサービス

サービスはそのドメインオブジェクトの境界付けられたコンテキスト内での振る舞いに関するロジックを全て所有しているべきです。
主要ドメインのライフサイクルを明確にモデル化することで、状態の衝突に対する対処をする場所がひとつになり、状態変化に基づいて振る舞いを加える場所も得られます。
RESTでもRPCでも、どの統合技術を選んだとしてもこの考え方は重要です。

マイクロサービスの世界におけるDRYとコード再利用のリスク

一般的にコードの重複を避けるように(Don't Repeat Yourself)、と言われています。再利用可能なコードを共有ライブラリとして作成することはよくありますが、マイクロサービスでは危険な場合があります。
サービスをまたがったコードの共有はサービス間の結合につながります。
例えばシステムの中核を表すドメインオブジェクトのライブラリがあった場合、このライブラリに対する変更が発生した場合、全てのサービスに変更を加える必要がでてきてしまいます。
「サービスの内部のDRYは守り、サービスの境界をまたがるDRYについては寛大に」というのが筆者の経験則です。

クライアントライブラリを作成する場合も注意が必要です。
サーバロジックがクライアントライブラリに混入しないように注意が必要です。
クライアントライブラリを作成する場合は、アップグレードのタイミングはクライアント側で決められるようにしましょう。
そうすることによって、個々のサービスを独立してリリースできる状態を保ちます。

参照によるアクセス

サービスからリソースを取得した場合、リクエスト後にリソースが変更されている可能性があります。最新の状態が取得できるように、元リソースへの参照を含めることが必要です。
常に特定のサービスへのアクセスをしていると負荷が高まる可能性があるので、HTTPのキャッシュ制御などを適切に利用しましょう。

バージョニング

スライド13

まず、破壊的変更を最大限避けることがもっとも重要です。
クライアント側のサービスは可能な限り柔軟であろうとするべきです。
これに関連してポステルの法則「送信するものに関しては厳密に、受信するものに関しては寛大に」というものがあります。

どうしても破壊的な変更が発生してしまう場合があるので、コンシューマ駆動契約のような仕組みで、早期に発見できる仕組みを整えましょう。

クライアントがサービスと統合できるか判断するための仕組みとして、セマンティックバージョニングという仕様があります。
MAJOR.MINOR.PATCHという形式のバージョン番号を振ります。
後方互換性のない変更が行われた場合はMAJORが増加し、後方互換性のある機能追加の場合にはMINORバージョンがあがります。PATCHは既存機能にバグ修正が行われたことを示します。
例えばヘルプデスクアプリが顧客サービスのバージョン1.2.0に対して正常に機能するように作られていた場合、
顧客サービスが1.3.0に変更されても、ヘルプデスクアプリは変更する必要がありません。新しくバージョン2.0.0がリリースされた場合は変更の必要があるかもしれないことがわかります。

一時的に異なるエンドポイントを共存させるパターンもあります。
この場合、呼び出し側がリクエストをルーティングする手段が必要です。リクエストヘッダやURIにバージョン番号を含めたりします。

同じサービスの新旧複数のバージョンを同時に動作させることもできます。
しかし、サービス内部にバグがあった場合、それぞれのサービスを直してデプロイするなど、色々と問題が起こり、複雑さの原因になるので推奨されていません。
ブルーグリーンデプロイメントカナリアリリースのために短期間だけ、共存させるならありです。

ユーザインタフェース

ユーザインタフェースの統合手法は下記の4つあります。

・API合成
・UI部品合成
・フロント向けのバックエンド(BFF)
・ハイブリッド手法

API合成はそれぞれのUIコンポーネントからそれぞれ必要なAPIを呼び出すパターンです。
このパターンの欠点の一つとしては、サービスを利用するアプリやデバイスごとに必要な情報が変わる場合がありますが、サービス側は呼び出しもとに応じてレスポンスを調整することはほとんどできません。
また、このUIは誰が作成するのかという問題もあります。別のチームがUIを作成している場合、ちょっとした変更でも複数のサービスチームに変更をリクエストする必要があるかもしれません。
さらに、通信が多くなってしまう場合もあり、モバイルデバイスにとっては特に問題になります。

UI部品合成は、サービスにUI部品を直接提供させ、それを組み合わせるパターンです。
サービスとそれを使用するUIを同じチームが作成する場合は最適です。
しかし、やはりこれらの部品をまとめて組み立てるレイヤが必要になります。また、UXの一貫性を保証するためには各サービスのUIが一貫的な実装をする必要があります。
さらに、ネイティブアプリケーションの場合は、UIコンポーネントを提供できないので、API合成のパターンに戻る必要がでてきます。
加えて、サービスが提供する機能がウィジェットやページに収まりきらない場合もあります。

フロント向けのバックエンド(BFF)はモバイルや顧客WEBサイトなど、特定の特定のユーザインタフェースやアプリケーション向けの専用のバックエンドのことです。
ここで気をつけるべきは、BFFに入り込むべきでないロジックが入り込むことです。ビジネスロジックはサービスの中に閉じ込め、BFFではユーザインタフェースに特化した振る舞いだけを追加するべきです。

上記3つをいい感じに組み合わせたハイブリッド手法になっていてもかまいません。
重要なのは機能の凝集性を維持することです。

サードパーティソフトウェアとの統合

何らかの外部サービスとの統合が必要な場合に問題なる点が3つあります。

・制御の欠如。多くの技術的な判断がベンダ側ですでに下されているということ
・カスタマイズの問題。多くのエンタープライズ向けのツールの場合、カスタマイズ性が高すぎて複雑になったり、小さな変更によってカスタマイズが壊されたりすること
・統合スパゲティ。複数の外部サービスとの統合により、一方はSOAP、一方はXML-RPC、などのスパゲティ状態になってしまうこと

これらを解決するカギは状況を思い通りに持っていくことです。
例えば、独自のサービスで外部サービスをラップする、CRMのような多機能なツールの場合は、中核ドメインを抽出してファサードとなるサービスを用意する、などです。
ストラングラー(絞め殺し)パターンもレガシーなプラットフォームから徐々に移行したい場合などに使えます。
古いシステムへの呼び出しをインターセプトし、徐々に新しいサービスへとルーティングしていきます。これにより、ビックバン型で書き換えることなく徐々に移行できます。

まとめ(原文ママ)

・いかなる代償を払ってもデータベース統合を避けます
・RESTとRPCとの間のトレードオフを理解し、RESTをリクエスト/レスポンス統合の優れた出発点と積極的にみなします
・オーケストレーションよりもコレオグラフィを選びます
・ポステルの法則を理解して体制のあるリーダー(TolerantReaders)を使って破壊的変更を避け、バージョンが必要ないようにします
・ユーザインタフェースを合成レイヤと考えます

以上です!
長くなってしまいましたが、マイクロサービスの統合手法の技術的選択肢や実装パターン、考慮すべき点などがなんとなく伝えられていればと思います。

HITO-LinkCRMでも今後マイクロサービスアーキテクチャを導入するかどうか検討していくところです。今後もこの本を読み進めて、情報共有できたらと思います!

以下、宣伝です。

※ゆるく開発やチームの様子を呟くのが半分、実家の犬の写真が半分のアカウントです。
https://twitter.com/crmruketa

※プロダクトの話を何もしていませんが、万が一HITO-Link CRMに興味を持っていただけたら、こちらから資料請求くださいm(_ _)m
https://www.hito-link.jp/crm/download

※弊社コミュニティのエントリーフォームです!興味を持ってくれた方は、エントリーしてみてだくさい!

※↑のTechコミュニティの詳細についてはこちらのnoteで説明しています!


この記事が気に入ったらサポートをしてみませんか?