もりはやメモφ(・ω・ )

インフラなエンジニアからSREへ

マイクロサービスアーキテクチャ本の感想(4章の前半)

始まる前は長いと思っていたお正月休みも終わってしまいますね。 O'Reillyのマイクロサービスアーキテクチャ本感想の第3回です。

4章「統合」を読んで

4章はまず、そのボリュームに圧倒されます。実ページ数で47Pもあり、これは1章の13P、2章の17P、3章の10Pと較べて倍以上のページ数となっています。章タイトルは「統合」で、マイクロサービスとして分解した各サービスをどうやって組み合わせて実サービスにつなげていくかの話になります。時間と集中力の兼ね合いで4章は前半と後半に分けます。

マイクロサービス技術の一番大事なところが統合なんだぜ

「統合を間違えると大惨事」や「SOAの試みを苦しめてきた最大の落とし穴」といった脅し文句から4章は始まります。技術的な話の前に、選択しようとする技術から何を得たいのかを確認しており、それは以下であるとしています。

  • 破壊的な変更を回避すること
    • 疎結合で繰り返し言われた、他のサービスに影響を与えずに変更が可能であるという話
  • APIを非技術依存にする
  • コンシューマにとって単純なサービスにする
    • コンシューマに簡単に利用してもらいたいが、簡単にするためのクライアントライブラリは結合を生むというジレンマがある
  • 内部の実装詳細を隠す
    • 隠れモデルの変更は共有モデルやインタフェースに影響させないという話

僕の価値観が崩壊した「共有DBは大規模な共有APIであり脆弱である」という考え方

僕がこれまで携わったシステムでRDBを使用しないシステムは無く、その大抵のシステムに"共有DB"的なものが存在していましたが、共有DBは「4.3 共有データベース」でけちょんけちょんにされます。この項は衝撃的で実に面白い内容でしたし、項の終わりが「(データベース統合)はいかなる代償を払ってでも避けてください」というインパクトのあるものです。

疎結合の喪失

従来のRDBを用いたデータ参照は"一般的"であるとしながらも、DBへアクセスする全ての関係者とデータ構造全体を共有しているため、極めて結合状態にあり、スキーマ変更などの影響範囲が大きくてリスクが高くなるとしています。 加えて特定のDBミドルウェアへのアクセスには専用のドライバを使用するケースが大きく(JDBCのような?)、データストアしてRDBではなく非リレーショナルなデータストアミドルが適切であったと後から気づいたとしても、コンシューマ側への変更が多大であって、ミドルの変更は現実的ではなくなるという問題があります。

凝集性もグッバイ

DBを直接操作するようなケースでデータの変更ロジックが複数のコンシューマ側に存在しがちとなり、DB側の変更が複数のコンシューマの変更に繋がってしまう問題があります。複数のサービス内に同じテーブルのUPDATE文がありえてしまうという問題です。

データベース統合による弊害

データベース統合は各サービスがデータを共有しやすくなりますが、振る舞いの共有は何もせず、内部表現が直接公開されてしまうことで、破壊的変更が避けがたくなってしまうという問題があるため「(データベース統合)はいかなる代償を払ってでも避けてください」としています。

サービス間の連携は同期、非同期通信があるよ

同期通信と非同期通信に関する説明です。

  • 同期通信は処理の呼び出し、応答がくるまでブロックする
  • 非同期通信は呼び出して後のブロックを行わずに、以下の2パターンが存在する
    • リクエスト/レスポンスは同期通信でも行われるが、非同期でも利用可能で、処理の終わりをレスポンスで検知する
    • イベントベースは非同期通信のみで、他サービスに指示(リクエスト)を送らず、ただイベントが発生したことを通知し、他サービスがヨロシクやってくれるのを期待する

モデル化手法も知っておこう

ビジネスプロセスをモデル化する2つの手法について説明が入り、マイクロサービスであればコレオグラフィシステムを目指すべきとしています。

  • オーケストレーション手法は、モデル化されたフローチャートがそのままサービスのリレーションになる
    • わかりやすい
    • リクエスト/レスポンス
    • 特定のサービスが中央集権になりがち(貧弱な神がうまれる)
  • コレオグラフィ手法は、イベントベースで各サービスが正しく動くことを期待する
    • ビジネスプロセスが暗黙手な実装になり(わかりづらくなる)
    • イベント発行後に各サービスが適切に動作したかを監視する必要がある
    • 各サービスの変更に強くなる

リクエスト/レスポンスを実装するための技術

リクエストレスポンスの実装に適したRPCとRESTについて解説が入ります。

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

RPCはローカルで呼び出しを行い、リモートサーバで実行するテクニックで、メリットもあるとしながら、おすすめできないという結論になっています。

  • インターフェース定義に依存する、SOAP,Thrift,Protocol Buffers
    • インターフェースさえ同じであれば、異なる技術スタックを選択できるというメリット
  • Java RMIはインターフェース定義は不要だが同じ技術基盤を使用する必要があり、密結合が生じる
  • RPCは早く実装できる以外のメリットは少ない
  • ローカル処理とリモート処理が区別できなくなり、負荷を無視した処理を行いがち
  • RMIは密結合を起こしがちだが、Protocol BuffersやThriftならサーバとクライアントが別デプロイ可能となる

REST(REpresentational State Transfer)

Webからアイデアを得たアーキテクチャ。REST自体にも様々な形式がありリチャードソン成熟度モデルは一読すべしとあります。 ※英語力が低い僕としてはこちらの方が短くてありがたかった

リチャードソン成熟度はざっくりREST実装をどの程度成熟して使用できているかの評価モデルであり以下のような4レベルが定義されているとのこと。

  • レベル0は何も考えてない
  • レベル1はURIは使えている
  • レベル2はURIとHTTPは使えている
  • レベル3はURIとHTTPとHATEOASが使えている
    • HATEOAS(Hypermedia As The Engine Of Application State)は"アプリケーション状態エンジンとしてのハイパーメディア"の意味で、リンクを辿ることでコントロール達することができるような概念
      • 要するに何かを実行するために、どこを探せば良いかだけを知っておいて、実行する際は探して見つかったものを実行する

RESTはHTTPを利用することが以下の理由でメリットがあり一般的だそうです。

  • もともと動詞(GET,PUT,POST)を持っていて新たな概念を定義する必要が無い(SOAPが失敗したのはこの概念の新定義がイマイチだったとか)
  • HTTPプロトコルを制御する膨大なエコシステムを利用できる

RESTにおけるデータ形式JSONが人気ですがXMLにも利点があり、著者のSam Newman氏は自分は少数派としながらもXMLを推すそうです。

  • JSON
    • 形式が単純で利用が簡単
    • 人気
    • 欠点としてLinkコントロールの定義が無い
  • XML
    • Linkの問題が無い
    • 多くのサポートツールが優れている(XPath)

当然ながらHTTP上のRESTにも欠点はあり、それは以下としています。

  • クライアントスタブの作成は、RPCよりは大変
    • 優れた多くのHTTPクライアントライブラリは使用できるが、HATEOASまで実現しようとするとフルスクラッチになりがち
      • こっそりRPCを持ち込んだりしてしまう
      • 共有クライアントライブラリを作ったりしてしまう
  • HTTPの持つ動詞で、PUTやDELETEを実現するのは複雑になりがち
  • 大量トラフィックには向いているが、低遅延通信には向いていない
    • WebSocketの方が優れている

イベントベース連携をを実装するための技術

続いてコレオグラフィシステムで用いるイベントベース連携を実装するための技術についての解説で、はじめに考慮する点は以下の2点とあります。

  • マイクロサービスがイベントを発行する方法
  • コンシューマがイベント発行を検出する方法

これらの実装方法については以下があります。

  • 従来はRabbitMQなどのメッセージブローカが対応しており、構築と運用が大変なものの稼働させられればとても効果的な方法である
    • ただしシンプルに使うことが重要で、ベンダーはキューに機能を盛り込みがちなことは注意
  • HTTPを利用したATOMフィードを利用する方法
    • ただしRESTでも示したようにHTTPプロトコルであるための欠点(低遅延が苦手)はある
  • 現在JSONを使っていて問題なければそれでもOK(ここは文脈が読めなくて、JSONやめろと言っているようにも読めます。。。)

非同期アーキテクチャの複雑さ

これまでイベントベース連携すごい、非同期通信ナイスと持ち上げてきましたが、この項では実際の失敗談を交えながらそれらを実現するために生じる複雑さについて考える事を呼びかけられます。 筆者の教訓としては以下があったとしています。

  • ジョブの最大リトライ上限を設定
  • 不適切なメッセージの監視
  • 再現手法の準備
  • 最終的なメッセージ病院の準備(配達不能、失敗したメッセージの送り先のキュー)
  • 不適切なメッセージの参照および必要によってリトライするためのUIの準備
  • そもそもこの複雑なアーキテクチャで実装すべきかを真剣に検討するようになった
  • 相関IDを利用したプロセス境界をまたいだ適切な監視がマジで大事

覚えておきたい考え方など

ここで流れが変わり、2つの考え方とライブラリについて簡単な説明が入ります。

  • 状態マシンとしてのサービス
    • どんな(RPCでもRESTでも)実装を行うにしても、コンテキスト境界内の振る舞いに関連するロジックを、別のサービスに流出させては行けない
  • Rx(Reactive Extentions)
    • 複数の呼び出し結果をまとめ、それに対応して操作を実行するライブラリ
      • サイトによると「オブザーバブルシーケンスを使用した非同期でイベントベースなプログラムのためのライブラリ」とある
    • 主要な言語に対応している
    • サービスの呼び出しが増えて来ていて、特に1つの操作のために複数の呼び出しを行っている場合はRxを思い出すと吉かも

マイクロサービスではコード重複を認めよう、なぜなら

共有コードは結合を生みかねない。ゆえにカスタムテンプレートを利用して各サービスは都度それをコピーして開発し、コード重複は許容しようとしています。 DRY(Don't Repeat Yourself)は一般的にコード重複を避けるべしと理解されがちですが、正しくは「システムの振る舞いと知識の重複を回避する」ことを指すとのことで、

  • 一つのサービス内でのコード重複はDRYに
  • 全てのサービス間でのコード重複は許す

とし、経験則からサービス間の結合が及ぼす害は、コード重複の外よりも大きいためであるためとのことです。

クライアントライブラリについて

この項は項番が間違っているんじゃないかと思っています。「4.11 マイクロサービスの世界におけるDRYとコード再利用のリスク」に対して「4.11.1 クライアントライブラリ」となっているのですが、内容に関連性が無いためです。 この項ではクライアントライブラリについて、AWSSDKが良い手本であるとし、サーバAPI開発者がクライアントライブラリを作成すると、クライアントライブラリ側にサーバ側のコードが結合しがちであり、Netflixにおいても結合が発生し始めているともあります。大事なポイントとしては

  • クライアントコードを分離すること(サーバ側のコードから)
  • トランスポートプロトコルへの対処(サービスの検出や障害、目的のサービスに関連すること) ※ここは僕は理解できてないorz
  • クライアントライブラリのアップグレードのタイミングを、クライアントに管理させる

力尽きたのでここまでorz

やはり4章は長いですね。。。この感想文は1度さらっと読んでから、読み直しつつ書くというやり方をしていますが、今日は力尽きたのでここまでにします。次は「4.12 参照によるアクセス」です。