アーキテクチャについてひたすら考えるメモ
アーキテクチャといってもいろいろある
- クリーンアーキテクチャ(オニオン、ヘキサゴナル)
- DDD
- モノリス
- マイクロサービス
- モジュラモノリス
最適解のようなものはないと思うが組織やプロダクトのフェーズでどういったアーキテクチャを選択するのがいいのか考える。
マイクロサービス
モノリスの辛みを解決できるとされるアーキテクチャ。プロダクトにおけるドメイン境界ごとにサービス分割し、完全に独立させたサービスを束ねることで実現する。完全に独立させることができるのでサービスごとに全く違った技術スタックも使えるし、DBも基本的にサービスごとに用意する。分割したサービスをデプロイするのにコンテナ技術が相性が良くデプロイされたコンテナを束ねるのにk8sが使用されることになる。
そのため、k8sやk8sまわりのエコシステム、CI/CD、サービス間の分散トランザクション、BFFなど考えることもやることもかなり多く、どう考えても組織が大きく、それなりの覚悟をしないとうまく運用できる気がしない。
逆コンウェイの法則やコンウェイの法則がよく言われるが、大きくなったエンジニア組織を小さく分割するためはマイクロサービスにするしかないという気もしなくもない。
後述するECSやCloud Runのような各クラウドベンダーのマネージドサービスを使うことで比較的に容易にマイクロサービスを実現することは可能かもしれないけど、envoyやistionのような機能で作るサービスメッシュとかも考えるとやっぱりやること多いよなという気持ち。
どこまで真面目にマイクロサービスを作るかという話でもある気がするが一つのモノリスを分割してマイクロサービスにするだけならそこまで難しくなくできるのかもしれない。
EKS環境を運用するとはこういうこと
モジュラモノリス
マイクロサービスが一般的になってきて、みんなマイクロサービスの難しさに気づいてきた。しかし、巨大モノリスが負債になるのも嫌だとみんな思ってる。そこで、モジュラモノリスが注目されているように感じる。有名なのだとRailsで古くから開発しているShopifyがモジュラモノリスにしたブログが有名。
モジュラモノリスは一見最適解のようにも見えるが、結局サービス分割をするためどの粒度でサービス分割をするかという難しさは避けられない。また、マイクロサービスよりもサービス境界を保つことが難しいためサービス間通信をどのようにするのかはかなり考えなければならない。例えば、サービス間通信は単なる関数呼び出しにするのか、それともgRPCやRestのような通信を伴うようにするのか、もし通信をするなら分散トランザクションをどうするのかなど。
さらに、 DBをマイクロサービスのようにサービスごとに分割するのかなども考える必要がある。
Shopifyはサービス間通信は通信を行なっておらずDBも1つだけにしてるらしい。
このようなサービス境界は静的解析などで解決しようとする組織が多そうだった。RubyであればShopifyがそのようなサービス間の不正な依存を解析するgemを出してるのでそれが使える。Goでそのような静的解析は見つからなかったのでもし厳密に管理したいなら自分で開発する必要があるかもしれない。
ナレッジワークの例がいい感じだった
各モジュールでパッケージを切って、internalパッケージ化に実装することで他のパッケージからは呼ばれないようにしていてモジュール境界を静的解析を使わずに保ってる。公開したいメソッドをinternalパッケージの外に出す。
モジュール間通信は普通の関数コールでできそうだけどAPI通信でもできる。API通信の場合、トランザクションは考えなければならないけどあまりマイクロサービス的トランザクションにこだわるとモジュラモノリスの旨みがなくなるのである程度の妥協は必要
Goでモジュラモノリスやるならモジュール間通信をAPI通信するにしろしないにしろナレッジワークのパッケージ構成を真似したい、今の所(そうすれば静的解析ツール作らなくて済む)
モノリス
上記のようにマイクロサービスが難しいからといってモジュラモノリスが簡単なわけではない。特にスタートアップのような組織もプロダクトもまだ小さいフェーズであればとりあえずモノリスで作り上げるのがほぼほぼ最適解と言っていいだろう。
その後、組織が大きくなりチーム分割が必要になったときにモジュラモノリスやマイクロサービスを検討すればいい。
ただし、モノリスといっても将来的なサービス分割を意識してコードを書いたり、クリーンアーキテクチャのようなアーキテクチャを採用して分割しやすいようなコードを保つ意識は大事。
クリーンアーキテクチャやDDD
クリーンアーキテクチャやオニオン、ヘキサゴナルがどういったアーキテクチャでどのように実装するかはいろんなところで書かれているしそこまで大事ではない。どういったアーキテクチャであれドメインロジックを他と切り離し、テストを書きやすいようにし、頻繁なリファクタリングに耐えられるような変更容易性の高いプログラムにすることが大事でそこらへんを意識していればクリーンアーキテクチャに近づいていくはず。
DDDは原書通りであればドメインエキスパートと密にコミュニケーションを取る必要があり、組織的な話でもあるので気軽に導入できるものでもない気がするがDDDで語られる概念は全てのアーキテクチャの話の土台になるので早い段階でDDDの基礎は身につけた方がよい。
でDDDの値オブジェクトやエンティティで表現するビジネスロジック、エンティティとドメインサービスの違いとアプリケーションサービスでの実装の仕方なんかはけっこう使えるものが多いと思っていて、軽量DDDはアンチパターンと言われそうだけどドメイン部分の実装に役立つんじゃないかなとは思ってる。
DDDのユビキタス言語について以下の記事読んですごいハッとなったので貼っておく。あのマーチンファウラーも日本語が母国語なんだからクラス名に日本語使えよって来日したときに言ったとか言わなかったとか。
Cloud Runを使ったマイクロサービス
カウシェのアーキテクチャがすごく良さそう。スタートアップにおいて
CloudRunでざっくりサービス分割してそれぞれデプロイするだけでマイクロサービスができちゃう。サービス間通信のところさらっと書いてあるけどトランザクションが必要な処理であれば分散トランザクション的なことは考えないといけない。
が、k8sまわりをまるっと運用しなくていいのとデプロイの手軽さと、勝手にいい感じに増えるスケーラビリティ的なメリットが非常に大きい。特にスピード感重視のスタートアップのアーキテクチャとしてはかなり良く見える。
でも、結局どの粒度でCloud Runにデプロイするか、複数のサービスとクライアントがどうやり取りするか、カウシェと同じようにするならAPI Gateway(BFF)用意する必要があるなど考えなければならない。
もし、今スタートアップでアーキテクチャ考えてって言われたらCloud Runでとりあえずモノリスなアプリをデプロイすると思う。その後の追加機能単位でCloud Runに乗せるでももう少し大きい単位でCloud Run乗せるでもいいと思う。BFFはあったほうがいいんだろうけどサービスが増えてからでもいいのかもしれない。単一モノリスなら必要ないし。(カウシェのAPI Gatewayはenvoyの役割も担ってると思う、たぶん)
ECSを使ったマイクロサービス
Cloud RunとECSの立ち位置は同じようなもの(?)だと思うからECSでマイクロサービスやってる事例はないか調べてみた。
ECSサービス間の通信にALB噛ませたらHTTP1.1になって失敗するのでECSサービスディスカバリが使えるよという記事。ちなみにたぶんもうHTTP2.0対応されてると思う。
ECS Service Connectというワードもけっこう出てきたけどECSサービス間の通信をするために利用できるんだろう。
確かに、クラスター内でECSサービス間通信するためのドメイン解決できないと成り立たないよな。Cloud Runでも同じようなことをやってるんだろうか。
まとめ
何やるにしてもむずい。
とりあえず、規模の小さいサービス、組織で考えるとモノリス on Cloud Run(ECS) -> (モジュラモノリス) -> マイクロサービス on Cloud Run(ECS) -> マイクロサービス on GKE(EKS)の優先順位で考えていけばいいのかなと勝手に納得した。
いずれにせよ早すぎる最適化は避け、今できるベストのアーキテクチャを選択し、無理がでてきたら検討するのがいいのかもしれない。
アーキテクチャ難しい