Gakuです。
久しぶりにQiitaに投稿します٩( 'ω' )و
マイクロサービスについて、かれこれ2年くらい考えておりまして、まだまだ未完成ですが、いい感じになってきたので、その開発手法を今回は記述させていただきたいと思います。
全貌をお伝えすることは難しいので、サービス間連携部分を重点的に記述させていただきます。
間違っている部分もあるかもですが、その際はコメント欄で優しく教えてくれると嬉しいです。
前提
・個人開発でのサービス運用を考えており、kubernetesなどが使える高価なクラウドサービスは使用しないことを前提としております。(※課金死直行の富豪プログラミングは個人開発ではつらい)
・dockerやdocker-composeを使うことになりますので、まだ、知見がない人はまずそこから勉強した方が良いです。
・言語は何でも良いと思いますが、僕はgolangを使用しています。
マイクロサービスとは
マイクロサービスの詳しい内容は、ネット上を見ればいろいろ出てくるので、それを参照すれば良いと思います。また、オライリーから出版されている「マイクロサービス」という技術書は、非常に読むのが困難(英語翻訳感+専門用語のオンパレード)ですが、しっかり書かれているので、もっと詳しく「マイクロサービス」を知りたい!という方にはオススメの書籍です。
マイクロサービスをすごく簡単に説明すると、管理者サービス、商品管理サービスなど、細かな粒度でリポジトリを分け、はたまた、リポジトリ単位で担当者を分け記述していくアーキテクチャのことを指します。また、対照的にRuby on Rails単体などで、backendを記述していく方法をモノリシックといいます。
これだけ聞くと「マイクロサービスめっちゃええやん!」と思ってしまいます(※2年前の僕)が、様々なツールや技術を駆使して作る必要があり、思った以上に複雑なアーキテクチャです。
これまでの僕のマイクロサービスアーキテクチャ
簡単にではございますが、僕がマイクロサービスアーキテクチャで実装する際の構成方法を紹介します。
[サービスの構成]
・golangを使用
・DBは各サービス毎に持たせる
・作成したサービスはCircleCIなどを利用し、自動でGCR上へdocker imageとして格納します。
(※GCRはそこまで高くないクラウドサービスです。その割に導入効果が高いと判断したので、ここだけクラウドサービスを利用)
・GCRに格納したイメージをdocker-compose.ymlで呼び出すことで、1ソースで環境を構築できるように実装する。
[サービス間連携]
・gRPCを使用
この構成だと、docker-compose.yml一つで開発環境、本番環境を作成できるので非常に楽です。
ただ、実装面で「gRPC」の使いにくさに不満を持っておりまして、どうにかしたいと日々悩んでおりました。
不満としてはこんな感じ
[gRPCの不満]
・自動で生成したソースコードをclientとhost双方に配置する必要がある。
→配置めんどくせぇ。
・ソースコードの自動生成には元となるprotoファイルを作成し、そこから生成する。
→protoファイルどこに置くのがベストなんや問題。考えても答えは見つからないので、とりあえずhostに置いとく。みたいな運用になる。
・protoファイルを変更した場合、clientとhost双方へ配置した自動生成ファイルの再配置、ならびに双方のソースコードを適切に変更する必要がある。
→俺はちょこちょこサービス毎での実装が実現できると聞いたからマイクロサービスを勉強しているのに、一つの仕様変更で、気づいたらめっちゃエラー出る不快感。
・下記のようにAの下にBとCというサービスが紐づいているサービスをテストする場合、A単体でテストコードを記述することができず、BとCを引っ張ってきてテストを実施する必要がある。
A -- B
|- C
→A単体でテストしたいんじゃああああ!
とまぁ、こんな感じです。僕のgRPCに対する知見が足りていないのだとは思います。
しかし、世間一般で「gRPC最強!」説を唱えている方々は多くいらっしゃいますが、僕には難しくてきついなと思える連携方法でした。
そこでメッセージキュー方式
オライリー「マイクロサービス」を読んでいると「イベント駆動方式の非同期通信でサービス間を連携するのが良い」と記述されておりましたので、gRPCで連携している部分を全てメッセージキューで連携するように考えてみました。
メッセージキューを図解するとこんな感じです。
[動作]
・サービスAはメッセージキューサーバへメッセージを送信する。
・サービスBはメッセージキューサーバを監視しており、自分宛てに送られたメッセージがメッセージキューサーバに送信されると、処理を実施する。
この構成だと、テスト実施時にサービスAとメッセージキューサーバがあれば、簡単なサービスBモックを作成することでテストが実装可能となります。さらに疎結合となるので、サービスBを変更したとしても、サービスAはメッセージキューサーバにメッセージを送っているだけなので、影響が出ません。
また、重たい処理をサービスBで行う場合、リクエスト/レスポンス形式では処理が終わるまで待機する必要があるが、この場合だと、メッセージキューサーバへメッセージを投げた瞬間、解放となるので、ユーザに待機という不快感を与えることを軽減できます。
問題点としては、疎結合なので突然死した場合などによる原因特定が困難らしい点(なので、各サービスは監視ツールを使って監視しておく必要がある)や、新たにメッセージキューサーバを作成する必要がある点があげられる。
問題点はあまり実感していないので、今後出てきた際にちょこちょこ対応していこうと考えている。
実装
簡単な実装を以下のリポジトリで行いました。
gaku3601/study-NATS
メッセージキューサーバにNATSというツールを使用しております。
NATSにアクセスするクライアントをgolangで記述する場合、以下を参照すれば幸せになれます。
nats-io/go-nats
実装内容
serviceAとserviceBを作成し、NATSと連携することでマイクロサービスアーキテクチャを実現しています。
コードは以下のような形で実装しております。
1.serviceAはBFFを想定し、echoでGETリクエストを取得、その後、NATSにメッセージを投げております。
2.serviceBはserviceAからのメッセージを監視し、メッセージが飛ばされたら処理を実行します。
3.最後にserviceAもserviceBからのメッセージを監視しており、serviceBの処理が終わり次第、
serviceAへレスポンスをメッセージとして送信します。
それをserviceAで受け取り、ブラウザ画面へ表示します。
文字として起こすとめんどくさそうな処理ですが、NATSはこのリクエスト/レスポンスをメッセージキュー方式でも簡単に書けるようになっているので、すごく使いやすいです。
簡単なコードを記述してみれば、そのすばらしさに気づくと思いますので、触ってみると幸せになれると思います。
NATS以外にもSQSやRabbitMQなどいろいろなメッセージキューサーバが存在するので、そこらへんは各々で調査の上使ってみれば良いかと思います。
おわりに
とまぁ、こんな感じです。
マイクロサービスアーキテクチャは作成するサービスの規模、開発メンバーの人数、その他使用する技術が多義に渡っているなどの理由から「これ!」といった、開発方法がネットを探しても見つからないのが現状です。
その点がマイクロサービスは難しいといわれる要因と思ってます。
ただ、各サービス毎にDBを分けて、連携していればそれはもう「マイクロサービスアーキテクチャ」なので、自分なりにいろいろ試行錯誤して、Webの文献にとらわれず「ストレスフリーな環境」を考えていける楽しさはあります。
今回ご紹介したものも僕の中で決定版!という考えはあまりなく、処理速度や監視といった面でまた新たな問題が出てくることが想定されます。
それをブラッシュアップし、超絶快適に実装可能なアーキテクチャを今後も考え続けていこうと思ってます。
マイクロサービスやってみたいけど、どうサービス間連携を行えばいいんだ?とか,gRPC使ってるけど、きっつい!という僕みたいな方へ、少しでも解法となる記事となれば幸いです。
でわでわ٩( 'ω' )و
宣伝
ブログやっております。マイクロサービスや最新フロントエンド、機械学習系を主に取り扱っております。
よかったらのぞいてやってください。
Gaku's Memo