業務でモノシリックなTransactionScriptで書かれたアプリケーションをマイクロサービスに移行しようと検討している中で、下記の学術記事を読んだ。
Microservices antipattern and pitfalls
知識整理のためにまとめる。
Data-Driven Migration AntiPattern
- モノシリックなアプリケーションをマイクロサービスに移行する場合にやりがちなアンチパターン。
- モノシリックなアプリケーションを機能的に分離すること。そして、その分離したサービスのみに所属したデータベース(スキーマ)を巨大なデータベースから分離していくこと。この二つがゴールとなる。
- とりあえずアプリケーションのモジュールとデータベースを分割して、マイクロサービスに移行しようとしても、それは歪で、移行のコストは高くなっていく。
About Microservices
- マイクロサービスなシステムは小さく、一つの関心事のみのために作られる。そして、それぞれのマイクロサービスはそれぞれのデータのみを保有する。
- マイクロサービスは、自分自身に境界付けられたコンテキストのみをサポートし、他のマイクロサービスと何も共有せず、ただ定義されたインターフェースを公開し、何にも依存しない。
- 上の定義に従えば、テスト・開発・デプロイは容易なものになる。
Functionally First, Data Last
- データベースの分割は、ソースコードの分割よりも複雑で困難。
- まずは機能的にモジュールを分割していく。そして機能的にその切り離したマイクロサービスの関心事が最小になったところで、そのサービスが処理するリクエストやモデルを分析し、切り離したサービスのみが扱うデータベース(スキーマ)を切り出す。
- この方法が最も移行のコストを下げる方法。
The Timeout AntiPattern
About Microservices
- マイクロサービスアーキテクチャは各サービスが定義されたプロトコルを使用して互いに交信しあう。
- マイクロサービスアーキテクチャの困難の一つにサービス間の可用性・応答の管理がある。
Using Timeout Value
-
Consumer
であるマイクロサービスが接続もしくは購読をしている最中にPublish
側サービスからの応答が来なくなった場合、Consumer
サービスは無期限に待機するか、定義されたリトライ回数分のリトライを行うだろう。 - こういう場合、
Publish
側が配信を行っていないことはどうすればConsumer
は確認できるか。この場合、よく使われるのはタイムアウト値を設定することである。 - しかし、タイムアウト値には欠点がある。例えば、サービス間の平均通信レイテンシが
1000ms
だとして、最大レイテンシは5000ms
だとする。タイムアウト値を5000 × 2 = 10000
msと設定したとすると、ユーザーがエラーだと気づくまでに10秒かかることになる。これがタイムアウト値設定の場合の欠点。
でも、これそもそものレイテンシ遅くない。。?
Using The Circuit Breaker Pattern
-
次はサーキットブレーカという死活監視をサービス間に置く方式。
-
サーキットブレーカは何らかの方法(ping,etc..)で
Publish
サービスの状態を監視し、もしPublish
が応答を返さなくなったら、サーキットブレーカを通過していたConsumer
からの通信対して、Publish
サービスが応答を返さなくなったことを伝える。
-
サーキットブレーカパターンはいくつかの著名なOSSでも使われている方式で、タイムアウトはアンチパターンとなっていた。
The "I was Taugut to share" AntiPattern
About Microservices
- オブジェクト志向プログラミングで作られたアプリケーションでは、文字列を扱うユーティリティや汎用的な計算ロジックを含んだ共通モジュールを、いくつかのモジュールで共通で参照する構成はよく見られる。
- しかし、これはマイクロサービスにおいてはデプロイ・テストなどの容易性を実現するに当たって、障害となってしまう。ではどうすれば共通ユーティリティモジュールを
100
を超えるマイクロサービスでも共通的に扱えるようにすれば良いのか。
4Patterns
記事では4つのパターンが紹介されていた。
Shared Project
- 共有ユーティリティをプロジェクトとしてマイクロサービスのプロジェクトと共有しておき、コンパイル時に動的にバインディングする方法。これは開発効率については有益だが、変更を誰がなぜ行ったのか、ということがわかりづらく、リリース間際になって共通ユーティリティの変更が破壊的な影響をサービスに与えてしまい、テストをし直すという状況にもなり得る。
shared Library
- 共通ユーティリティをモジュールとして管理する方式。これは開発効率を損なう可能性はあるが、
Shared Project
よりも優れている点はバージョン管理が可能ということ。サービスのオーナーはバージョンアップされたモジュールを使うか、現在バージョンのモジュールを使うが明確に判断が下せる。
Replicated
- 共通ユーティリティを各サービスコンポーネントがそれぞれ保有する方式。これは
DRYの原則
には反するが、依存を外部に全く持たなくなるという点では有利。だが、この方式の欠点は共通ユーティリティにバグがあった場合、全てのコンポーネントの保有する共通ユーティリティを修正しなければならないということ。そのため、この方式をとる場合はその共通ユーティリティが変更が限りなく少なく、安定したモジュールである場合に限るだろう。
Consolidation
- この方式は共通ユーティリティを分解して、サービスごとに特化した部分のみを各サービスに分離して含めるという方式。つまり事実上、共通ユーティリティを使わないという方式だと言っているように思う。これは使用している共通ユーティリティが頻繁に変更されるものであれば、検討しても良いかもしれない。
続く
今回は三つのアンチパターンとそれに対するプラクティスを記載した。
- Data-Driven Migration AntiPattern
- The Timeout AntiPattern
- The "I was Taugut to share" AntiPattern
継続して投稿していこうと思います。