🐭

インプレスのマイクロサービスパターンを読んだ俺的まとめ

2020/12/17に公開

マイクロサービスパターン

1章 マイクロサービス

参考サイトの掲載のみで詳細の説明は省く。
https://codezine.jp/article/detail/11055

2章 サービスへの分割

マイクロサービスアーキテクチャの根本は、機能による分割であり、1つの大規模アプリケーションを作るのではなく、一連のサービスとして構成する。

そしてアプリケーションのメンテナンス性・テスト容易性・デプロイ容易性を引き合げるアーキテクチャスタイルである。

そもそもソフトウェアアーキテクチャとは?

システムを作り上げる上で必要となる一連の構造のことで、ソフトウェア要素とそれらの間の関係、両者の性質から構成される。

→アプリアーキとは部品(要素)への分解であり、それら部品の間の関係。アプリのイリティ(-ility)を決めるのは部品への分割とそれらの部品の関係。

ソフトウェアアーキテクチャの4+1モデル

ソフトウェアアーキテクチャに対する4つの異なる視点を定義し、下記のそれぞれのビューは特定のソフトウェア要素とそれらの間の関係によって構成され、アーキテクチャのある側面を描き出す。

下記の4つのビューに加えてビューを動かすシナリオがあり、特定のビューの中で、様々なアーキ要素がリクエスト処理するためにどのように連携するかを記述する。

  • 論理ビュー
    • 開発者によって作られるソフトウェア要素。オブジェクト指向言語ではクラスとパッケージであり、継承・依存などがある。
  • 実装ビュー
    • ビルドシステムの出力。Javaでは1つ以上のJarファイルやWarファイルを示す。これらの間には、モジュール間の依存関係やコンポーネントモジュール関係の合成関係がある
  • プロセスビュー
    • 実行時のコンポーネント。要素はプロセスであり、プロセス間の関係はプロセス間通信。
  • デプロイビュー
    • プロセスがどのようにマシンに展開されるか。マシン間の関係はネットワーキングを示す。

アーキテクチャスタイルの概要

アーキテクチャスタイルのとは、組織構造のパターンの観点から、システムの全体像を定義するものである。より具体的に言えば、そのスタイルのインスタンスで使えるコンポーネント(要素)とコネクタ(関係)の種類と、それらの組み合わせに関する制約を定義する。

階層化アーキテクチャスタイル

上記4つの度のビューにも適用できる階層化アーキテクチャ。

  • プレゼンテーション層
  • ビジネスロジック層
  • 永続化記憶層

デメリット

  • 単一のプレゼンテーション層
    • アプリが複数のシステムから呼び出されるかもしれないという事実を表現できていない
  • 単一の永続化記憶層
    • アプリが複数のデータベーを操作するかもしれないという事実を表現できていない
  • 永続化記憶層に依存する存在としてのビジネスロジック層
    • 理論的には、このような依存関係があると、データベースなしでビジネスロジックをテストすることができなくなる

疎結合の利点

  • サービス間が疎結合であること
    • サービスとのやり取りはすべてAPIを介して行われるため、サービスの実装はカプセル化され、クライアントに影響与えずに書き換えられるようになる
    • 疎結合にすることで、メンテナンス性やテスト容易性などを担保できる

共有ライブラリの役割

あるサービスをライブラリとして読み込む場合、そのライブラリに対して要件の変更があった場合、そのライブラリを読み込んでいるほかのサービスもビルド、デプロイしなおさなければならない。

そのため、ライブラリとして実装するものは、今後も変更の可能性がないもののみで構成すべき。

マイクロサービスアーキテクチャの定義方法

アーキテクチャの定義方法想像力を膨らませる職人技の世界であり、反復作業となるものではない。

アプリケーションはリクエストを処理するためのものである。

ここではRestAPIやメッセージングなどの特定のIPCテクノロジは明言せず、システム操作という抽象的な概念を使う。

システム操作とはリクエストを抽象化したもので、データを書き換えるコマンドか、データを読み取るクエリーになる。

システム操作はサービスがどのように連携するかを描くアーキテクチャシナリオになる。

  1. システム操作を洗い出す
    1. 「顧客がオーダーして... → FTGOアプリケーションへ」
    2. 「レストランはオーダーを受けて... → FTGOアプリケーションへ」
  2. サービスを洗い出す
    1. アプリケーションをどのように分割するかを決める。業務で分割。あるいはDDDのサブドメインによる分割。ゴッドクラスは撲滅させる。
      1. 「オーダーサービス」
      2. 「レストランサービス」
    2. 「キッチンサービス」
  3. サービスAPIと連携方法を定義する
    1. 個々のサービスのAPIを決める。ここで第一ステップで洗い出した個々のシステム操作をサービスに割り当てていく。サービスによってはサービス間連携が必要な場合もあるので、その場合の連携方法を定義する。

サービス間でのデータ整合性の維持

サービス間でデータの整合性を維持するのは難しい問題で、システム操作の中には複数のサービスデータを更新しなければならないものがでてくる。

従来の2相コミットによる分散トランザクション管理の仕組みを採用していたが、サーガという全く異なるアプローチが必要となる。

Sagaとはイベント駆動型のアーキテクチャで、ロールバックを行うことができない代わりに、補償という考え方を提供している。ロールバックではなく、インタラクションの操作を逆向きにした取り消し操作を行うことで疑似的なロールバックを行う。

3章 マイクロサービスアーキテクチャで使われるプロセス間通信

マイクロサービスアーキテクチャは、一連のサービスとしてアプリケーションを構成し、これらのサービスはリクエストを処理するために連携する必要がよくある。サービスインスタンスは一般に複数のマシンで実行されるプロセスなので、それらはIPC(プロセス間通信)を使って通信しなければならない。

代表的なIPCの手法

  • REST
  • メッセージング(メッセージベースの非同期通信メカニズム)

サービスディスカバリの使い方

RESTAPIのサービスを利用する際は、そのサービスのネットワーク上の位置(URI, Port)を知る必要がある。

クラスドベースのマイクロサービスとなると、このネットワーク位置はダイナミック(動的)に割り当てられ、自動スケーリング、エラー、アップグレードなどでサービスインスタンスの集合はダイナミックに変化する。

動的なネットワーク位置を知るための仕組みが、サービスレジストリというデータベースである。

サービスディスカバリメカニズムは、サービスインスタンスが起動、終了した際にサービスレジストリを更新する。クライアントがサービスを呼び出すと、サービスディスカバリメカニズムはサービスレジストリにクエリーを送り、利用できるサービスインスタンスのリストを入手し、その中のどれかにリクエストをルーティングする。

サービスディスカバリの実装方法は下記のいずれか。

  • サービスとそのクライアントが直接サービスレジストリを操作する
  • デプロイインフラストラクチャがサービスディスカバリを処理する。

4章 サーガによるトランザクションの管理

サーガとはメッセージ駆動のローカルトランザクションのシーケンスである。

サーガはACD、つまり原子性、整合性、持続性を保証するが、Iの分離性はサポートしない。

そのため、アプリケーションはカウンターメジャーを使うことで分離性がないことで引き起こされる並行処理の以上の影響を防いだり、軽減する。

XAトランザクションの問題点

2相コミットを用いて、トランザクションに含まれるすべての要素がコミットorロールバックかを保証するもの。

  • MongoDB, NoSQLデータベースを含む新しいテクノロジの多くがXAをサポートしていない
  • 分散トランザクションは同期IPC(プロセス間通信)であるため、可用性が下がる
    • すべてのサービスが利用可能である状態でないといけない
    • 全体の可用性はトランザクションに参加しているすべてのサービスの可用性の積であるため

実際のサーガの動き方

  • 全体の動き

サーガに参加しているサービスは非同期メッセージングを使って通信を行う。サービスはローカルトランザクションが完了するとメッセージをパブリッシュし、それが次のステップのトリガーとなる。

  • ロールバック

サーガは個々のローカルトランザクションでコミットしてしまうので、まとめて自動でロールバックはできない。そのため、明示的に補償トランザクションというものを書いて一つ一つ取り消し処理を行う。このとき、取り消し処理が行われる順番は、最後から行われる。

サーガのコーディネート

サーガの実装はサーガのステップをコーディネートするコードから作られており、システムコマンドによりサーガが開始されると、コーディネートコードはサーガの最初の参加サービスを選択し、ローカルトランザクションの実行を指示します。

そのトランザクションが完了するとコーディネートコードは次の参加サービスを選択して実行する。

サーガのコーディネートコードは下記の2種類。

  • コレオグラフィ
    • サーガの参加サービスに次のステップの判断を委ねる分散管理の方法。各サービスはイベントの交換により通信する
  • オーケストレーション
    • サーガオーケストレータクラスでコーディネートロジックを一元管理する方法。サーガオーケストレータは参加サービスにどの操作を実施するかを指示するコマンドメッセージを送る

コレオグラフィの問題

  1. サーガの参加サービスはデータベーストランザクションの一部として自分のデータベースを更新してイベントをパブリッシュしなければならない
  2. サーガの参加サービスは受け取ったイベントを自分のデータと紐づけなければならない。相関IDを持つ形でイベントをパブリッシュする

コレオグラフィの利点

  • 単純性
    • サービスはオブジェクトを作成、更新、削除したときにイベントをパブリッシュする
  • 疎結合
    • 参加サービスはイベントをサブスクライブするだけで互いについて直接的な知識を持たない

コレオグラフィの欠点

  • オーケストレーションと比べてわかりにくい

    • サーガの定義場所が散在してしまう。
  • サービス間の循環的な依存

    • サーガの参加サービスは互いのイベントをサブスクライブするが、それが循環的な依存関係を生み出すことがある。これは必ずしも問題ではないが、設計に問題がある可能性あり。
  • 密結合の危険

    • サーガの参加サービスは自分に影響を与えるすべてのイベントをサブスクライブする必要がある。

    ⇒結論、複雑なサーガでは往々にしてオーケストレーションサーガを使う方がよい

    オーケストレーションベースのサーガ

    サーガの参加サービスに、何をすべきか、を指示することだけを目的とするオーケストレータクラスを定義。

    オーケストレータはコマンド/非同期リプライスタイルのインタラクションを使って参加サービスと通信する。

    サーガのステップを実行するために、参加サービスに実行すべきコマンドメッセージを送ると、参加サービスは支持された操作を実行し、オーケストレータにリプライを送る。

    オーケストレータはそのメッセージを処理して次にどのステップを実行するかを判断する。

    オーケストレーションサーガの利点

    1. 依存関係が単純
      1. オーケストレータは参加サービスを呼び出すが、逆はない。よって依存関係も一方的になり、循環的な依存関係が生まれることはあり得ない。
    2. コレオグラフィより疎結合
      1. 各サービスはオーケストレータが呼び出すAPIを実装するため、参加サービスがパブリッシュするイベントについての知識は不要
    3. 関心毎の分離が進み、ビジネスロジックが単純化される
      1. コーディネートロジックが局所化する。

    分離性の欠如への対応策

    ACIDのIは分離性のこと。ACIDトランザクションの分離性は複数のトランザクションを同時に実行した結果が、それぞれを同時でなく順番に実行された時と同じ結果になることを保証する。

    サーガを用いると、個々のサービスでのトランザクションで加えられた変更がコミットともに他のサーガに見えてしまう。そうすると更新作業全体を完了しないうちに他のサーガがアクセスしてしまうと整合性が崩れかねない。

    異常の概要

    分離性の欠如により起こる異常は次の3種類。

    1. 更新の消失
      1. 他のサーガが加えた更新を読まずにサーガがデータを上書きしてしまう
    2. ダーティリード
      1. まだ更新をおえていないサーガが加えた変更をほかのトランザクションやサーガが読み取ってしまう
    3. ファジー/反復不能読みとり
      1. サーガの2つの異なるステップが別々のタイミングで同じデータを読み取ったとき、その間にほかのサーガがそのデータを書き換えていてデータの値がまちまちになる

    カウンターメジャー(対応策)

    • セマンティックロック
      • アプリケーションレベルのロック
    • データの読み直し
      • データを読み直して変更されていないことを確認してから上書きする
    • ファイルのバージョン管理

    サーガの構造

    サーガを構成する3つのトランザクションを挙げる。

    • 補償トランザクション
      • 補償トランザクションを使ってロールバックする可能性があるトランザクション
    • ピボットトランザクション
      • サーガにおいて、処理を最後まで続けるか、中断するかを決めるポイントとなるトランザクション。ピボットトランザクションがコミットされれば、処理は最後まで続行される。
    • 再試行可能トランザクション
      • ピボットトランザクションに続いて実行されるトランザクション。成功することが保証されている。

    5章 マイクロサービスアーキテクチャにおけるビジネスロジックの設計

    DDD推奨。

    アグリゲートパターンを使ったドメインモデルの設計

    旧来のドメインモデルでは、オブジェクトの明確な境界がなく、そのクラス自身の働きというのは開発者の主観に委ねることになっている。

    アグリゲートを使うことで、アグリゲート間の関係を疎結合にし、オブジェクトを明確に境界分けする。

    • アグリゲートとは
      • 1 つのユニットとして捉えられる境界の範囲内にあるドメインオブジェクトの集合。ルートエンティティと、1つ以上のその他のエンティティ、値オブジェクトから構成される。

    アグリゲートのルール

    1. アグリゲートルートだけを参照する

      アグリゲートルートに修正を加えれば、関連するエンティティにも修正が反映される仕組みにすること。ドメインの不変条件(最低支払金額=1000円等)を変更によって変わらないように維持するため。

    2. アグリゲート間の参照は主キーをつかわなければならない

      アグリゲートが他のアグリゲートを参照するときは、アグリゲートのIDなるものをキーとして参照する。IDを使うことでアグリゲート間が疎結合になる。=アグリゲート間の境界の明確化。

    3. 1つのトランザクションで1つのアグリゲートを参照

      1つのtxが作成、更新するアグリゲートは1つだけにしなければならない。

      複数サービスの操作を行う場合に、サーガを用いる。

    ミニマムで設計する。しかし意図的にアグリゲートを大きく定義する必要もある。

6章 イベントソーシングを使ったビジネスロジックの開発

  • イベントソーシング
    • イベントを中心に据えてビジネスロジックをを書き、ドメインオブジェクトを永続化するアプローチのこと。
    • アグリゲートを永続化するための方法の一つ。一連のイベントという形で永続化される

従来の永続化の問題点

アグリゲートの状態が変わったときに、イベント(=ドメインイベント)をパブリッシュできない。

データの同期をとり、通知を送るメカニズム。

データを更新するトランザクションの一部として自動的にメッセージをパブリッシュする機能はサポートされていない。

履歴や監査と同様、開発者が後付けでイベント生成路ロジックを書く必要があるが、これはビジネスロジックとうまく適合せず、バグを出す可能性が高い。

⇒これをイベントソーシングでは解決する

イベントソーシングの概要

従来の永続化は、アグリゲートをテーブルに、フィールドを列に、インスタンスを行にマッピングしていたが、イベントソーシングはドメインイベントの概念を基礎として、まったく違う概念でアグリゲートを永続化する。

ドメインイベントとは、アグリゲートの状態の変換を通知するためのメカニズム。

イベントソーシングはデータベースにイベントのシーケンスという形で個々のアグリゲートを永続化し、それをイベントストアと呼ぶ。

(例:イベントテーブルにイベントID、イベントタイプ、エンティティタイプ、エンティティID、エンティティデータなどのカラムを持って各イベントを行でもつ)

アプリはアグリゲートを作成、更新したときにアグリゲートが作成したイベントをイベントTBLに登録し、アグリゲートをロードするときはデータベースから読み取った一連のイベントを順番に適用する。

  1. アグリゲートのイベントをロードする
  2. デフォルトコンストラクタでアグリゲートのインスタンスを作る
  3. apply()を呼び出して各イベントの適用を繰り返す

7章 マイクロサービスアーキテクチャでのクエリーの実装

マイクロサービスアーキテクチャでクエリーを書くことは難しい。データは複数のサービスがそれぞれ管理しているデータベースに散らばっているため、クエリーはそれらのデータを集めてこないといけないから。しかし、従来の分散クエリーのメカニズムを使うわけにはいかない。

技術的に可能であっても、カプセル化の原則が破られてしまう。

解決策は下記の2パターン。

  • API Compositパターン
    • もっとも簡単。データを所有するサービスのクライアントがサービスを呼び出して結果を結合する
  • CQRSパターン
    • 上のものより強力だが、複雑。

API Compositパターン

複数のサービスのデータを読み取るクエリー操作を実装するための方法。

このパターンは、データを所有しているオーナーサービスを呼び出して、結果を結合するという方法でクエリー操作を実装する。

このパターン構成要素となるコンポーネントには次の2つがある。

  • API Composer
    • プロバイダサービスにクエリーを送り、結果を結合する。
    • クエリー操作は最短で処理されるのが望ましく、可能な限り並行処理としてプロバイダサービスを呼び出す必要がある。前処理の結果を後処理で使うなどのシーケンス的要素がある場合のみ直列で実装する
  • プロバイダサービス
    • クエリーが返すデータの一部を所有しているサービスのことを指す。

API Compositパターンの問題点

  • どのコンポーネントをクエリー操作のAPIコンポーザにするかの選択
    • ①フロントエンドに任せる
      • ファイアウォールの外にいるので、遅いネットワークでサービスにアクセスする場合、適さない
    • ②アプリケーションの外部APIを実装するAPIゲートウェイをAPIコンポーザにする
      • 他のサービスにルーティングをせずに、自らAPI合成のロジックを実装する
    • ③スタンドアロンのサービスとしてAPIコンポーザを実装する
  • 一般のトランザクションでみられるデータ整合性がない。
    • データ整合性を保つロジックは複雑になる

CQRSパターン

アプリケーションの1つ以上のクエリーを実装するために、複数のデータベースのビューを維持、管理する。

9章 マイクロサービスアーキテクチャのテスト戦略

人間より機械の方がうまくできることを人間にやらせるべきではない。

ソフトウェアを信頼できる形で素早く確実にリリースするには、自動テストは不可欠。

マイクロサービスアーキテクチャのテストの難しさ

マイクロサービスアーキテクチャはプロセス間通信が中心的な位置を占めるので、モノリシックアプリケーションよりも、プロセス間通信の役割が非常に重要。

Discussion