マイクロサービス何もわからんけど勉強したことまとめる雑メモ
マイクロサービスパターンを読み終わったので学んだことと思ったことをまとめていく。
マイクロサービスパターンを読んで
- アプリケーションをデプロイ単位のサービスに分割し、それぞれDBを持つ。
- ゴッドクラスの除去。(どこでも使われるようなビジネスのコア的なクラス。たしかにこれ絶対ある。)
- DDDとの親和性。
- マイクロサービス間の通信はgRPCでやるもんだと思ってたけど、別にRestでも何でもいいらしい。
- マイクロサービスで同じような処理があるなら共有ライブラリを作成することができる。
- ただし、ライブラリの場合、ライブラリの変更に伴い使用しているマイクロサービスにも変更が必要になってしまう可能性があるのでその場合はサービスとして実装したほうがいい。ライブラリとして公開するなら変更が少ないものにしたほうがいい。
- サービスをどう区切るかみたいな話はだいたいDDDの話なのかなと思った。
- API合成やプロトコル変換をし、クライアントへの影響を少なくするためにAPI Gatewayをマイクロサービスの前に置く。
- サービス間のプロセス通信で非同期でメッセージベースのやりとりをすることができる。
- Kafukaを使用した例やpub/subなどが技術としてある。
- サービスからサービスの同期的な呼び出しは可用性を下げることになる。
- イベントソーシングの活用。
- イベントソーシングはdatabaseとKafukaのようなメッセージブローカーの間くらいのやつ
- ビッグバンリライト(0からレガシーを書き直す)はだいたい失敗するからやめたほうがいい
- モノリスのマイクロサービス化はストラングラーアプリケーションから徐々にマイクロサービス化したほうがいい
- なのでマイクロサービス化は明確な期間を定め長期化することを覚悟したほうがいい
- モノリスからマイクロサービス化するときDBをモノリスから剥がすの大変そうな気がする
逆コンウェイの法則
システムを設計する組織は、組織のコミュニケーション構造をコピーしたような設計しか作れない
逆にマイクロサービスを作りたければそのような組織を作っていなければならない。
マイクロサービスをやるということは分散されていて自律的でスピード感のあるエンジニア組織を作ることであり、真髄は組織づくりなんだなと感じた。
マイクロサービスに対して感じていた難しさはもちろん技術力的な話でもあるんだけどそれ以上に組織作りという大きな壁があるんだろうなと思う。
circuit breaker
マイクロサービスは他のマイクロサービスを呼ぶことも多いが、呼び出し先のサービスがさまざまな理由で落ちていることも考えられる。circuit breakerは連続的な失敗の数が指定されたしきい値を超えたら、一定の期間が過ぎるまで、RPIプロキシは呼び出しを直ちに跳ね除ける。
実際に実装する場合はOSSを使用するか自前で実装するかだけどOSSを使うで良さそう。
go-circuitebreaker
メルカリが出してるOSS。
サービスディスカバリ
マイクロサービス間の通信においてサービスインスタンスの数や場所は動的に変化するためその情報を登録、検索するための仕組みをサービスディスカバリという。SpringではNetflixのEurekaというものがあり、Spring Cloudに統合されている。
Spring Cloud
Springの話だけど、分散アプリケーションを開発するためのSpringベースのフレームワーク。Netflixが開発しているOSSが多く存在する。モジュールがかなりあるのでSpringあるあるで全部理解するのは辛い。
EurekaのようなアプリケーションレベルでのサービスディスカバリはSpringでしか役に立たないため、インフラレベルでのサービスディスカバリの方が良いとされている。
つまり、kubernetesが良い感じにしてくれるという話。
サーガ
マイクロサービスにおいてデータの整合性を保つためにはACIDトランザクションではなくサーガ(saga)と呼ばれるものを使わなければならない。サーガはACDを保証するが、I(分離性)を保証しない。
- 保証可能トランザクション ロールバック可能な範囲
- ピボットトランザクション ここまできたら大丈夫だよ的なところ
- 再試行可能トランザクション 絶対成功する処理
サーガにはコレオグラフィとオーケストレーションがある。
コレオグラフィはpub/subを使ってサービス間でやりとりをする。これは循環依存が発生しやすかったり、サーガの実装が一箇所にまとまらず実装が複雑になるなどの理由があるため、複雑なマイクロサービスではオーケストレーションが推奨されている。
オーケストレーションは中央集権的なサーガオーケストレーターが存在する。
カウンターメジャー。
CQRS(Command Query Responsibility Segregation
複数のマイクロサービスからデータを取得したい時、通常API合成をし、取得できるが非常に大きなデータを取得することになる場合、メモリに全て乗るためこういった場合はCQRSを使用することができる。
CQRSは関心ごとの分離である取得と書き込みのクエリを分離することが目的である。
マイクロサービスというかDDDの文脈から来てるっぽい。
参考になるかも
APIゲートウェイ
APIは誰が開発運用した方がいいか?Netflixはクライアントチームがそれぞれ公開するAPIの開発、運用を担当している。APIの共通処理やゲートウェイの運用自体はAPIゲートウェイチームが担当し、公開しているAPIについてはクライアントチームが担当する。ゲートウェイは1つでも良いがweb、モバイルなどクライアントのチーム単位で用意してもいい。
これがいわゆるBFFパターンなのでBFFというのであればAPIを使用するクライアントがクライアントごとにゲートウェイを作るのが正解なんだと思う。(適当にBFFという言葉を使っていた。)
Consumer Driven Contract Test(CDC)
サービスの提供側(Provider)とサービスの呼び出し側(Consumer)があるときに、Consumerが期待通りにProviderを呼び出せているかをテストする手法。
マイクロサービスにおいてサービスからサービスを呼び出すことは考えられ、その時のテストにおいてCDCが取り出されることが多い。
これは、
- マイクロサービス全体のE2Eテストを実施する。
- サービスからサービスを呼び出してる箇所においてCDCテストを実施する。
このどちらかを選択することになるっぽい。
SpringであればSpring Cloud Contractというものがある。
Provider側でcontract(期待する振る舞い)を書く。
contractを元にユニットテストを自動生成する。
Providerが変更されたときは毎回上記が実行、更新されるようにする。
ここら辺はCIでできそう。
Consumer側
Privider側のcontractを元にスタブのjarファイルを作成して、ローカルのmavenリポジトリにうんたらかんたら
このスタブを使ってユニットテストを自作する。
これは、結局スタブを作成してユニットテスト自作するので、自動生成の部分とcontractの定義がシナリオテストっぽく書けるのがうまみなのか?
Goだとどうするんだろ
pactというものがありそうだけどあんまり事例なさそう
今、マイクロサービス運用している組織はE2Eテストちゃんとやってるイメージがある。
メルカリとか。
サービスメッシュ
マイクロサービスシャシー
マイクロサービスを運用するにあたってのさまざまな横断的関心ごとを実装するためのフレームワーク的なもの。SpringでいうならSpring Cloudがこれにあたる。Goにもある。
このマイクロサービスシャシーは各言語ごとなので当然Spring CloudであればSpringでしか使用することはできない。これを解決するのがサービスメッシュ。
ここら辺はkubernetesの話
モジュラーモノリスについて
マイクロサービスが難しそうならモジュラーモノリスはどうなのか。
Shopifyの例
Spring
Spring Modulithなるものがあるので使ってみたい。
モジュール同士の境界の補償はテストで担保してるっぽい
モジュール呼び出しはメッセージシステム的な何かだそうで
SpringであればGradleのマルチプロジェクト構成もありかもしれない
ただ、gradle.buildが複雑になるのであんまりやりたくない
Go
採用例
gRPC。
protoで定義するserviceがxxxServiceになっているので生成されたインターフェイスをそのまま実装して使う。モジュール間通信はgRPCでやってる。と思う。
たぶん、モジュール同士の境界を強制できない。静的解析ツールなどでいこれを解決しようとしている。
トランザクションはどうしてるのだろうか?
DBはたぶん分けてない。
感想
- マイクロサービスをやろうとするくらいの覚悟がある組織はそれなりの規模感の組織かつ相当の気概が必要なのとテックに振ってる企業じゃないとだいぶ茨の道な感じがするのでモジュラーモノリスはもっと流行ってもいいんじゃないかと思った。
- k8sの運用踏まえてマイクロサービスは厳しいけど既存のモノリスが辛いなら全然モジュラーモノリスはありだなと思った。
- ただ、あくまでモノリスには変わりないのでちゃんとやらないと中途半端なモノリスになりそう。
- Shopifyのブログにも書いてあったけどモジュール同士の境界は強制しないと破綻しそう。そこら辺はSpringみたいなフレームワークの方がやりやすそうだなと思った。
- Springの場合、Spring Modulithを使用するなら依存関係が共通になってしまうのでマルチプロジェクトの方が依存関係まで含めて疎結合にできそうだけど、そしたらモジュール間通信をちゃんと実装しないといけない。
- Goはモジュールごとにgo.mod作るのがいいなと思った。
- あと、workspaceでモジュール管理するの知らなかったからそれで良い感じにモジュール管理できるならよさそう。
- Goの場合、モジュール同士の境界を静的解析などで強制させる必要があり、そこがめんどそうだなと思った。
- やったことないからわかんないけどそのworkspaceでモジュール管理をする場合、モジュールごとにgo.modがあってルートディレクトリにmain.goが一つあるイメージでいんだろうか。
- DBはマイクロサービス同様、モジュールごとに分けてもいいし、一つのDBでやるにしてもdatabaseを分けるくらいはしたほうがいいのかもしれない。
- API合成してGraphQLなりRestなりで公開したくなるんじゃないのと思うんだけど、そしたらAPI Gatewayが結局欲しくなるんじゃないのと思った。モジュラーモノリスのモジュールの中にAPI Gatewayモジュール作るか、別レポジトリで公開するのはありなんじゃないかと思ったけどどうなんだろう。そうしないと、呼び出し元からのアクセスが増えて煩雑になる気がしなくもなくもない。
- 結局モジュラーモノリスだろうがマイクロサービスだろうがトランザクションの実装はたぶん必要でちゃんとやると普通に難しいんじゃなかろうかという。
余力があったら記事にまとめる。