【後編】Spring Bootで始めるマイクロサービスの開発
2021年11月2日:ウェビナーアーカイブ配信中!
「Springフレームワーク徹底解説 〜初心者から上級者向けセッションと事例のご紹介」
ダウンロード:「2021年 Springの現状」資料
ダウンロード:「2020年 Springの現状」資料
【前編】Spring Bootってなにがいいの
【後編】Spring Bootで始めるマイクロサービスの開発←本記事です
前回に引き続いてJavaフレームワークであるSpring Bootを題材として開発系の内容を記事にしたいと思います。
今回はマイクロサービスアプリケーションが増えてきている理由と、マイクロサービスにSpringを使うメリットについて説明いたします。
モノリシックアプリケーションからマイクロサービス
Javaに限らず、小規模でスタートしたアプリケーションは成長するにしたがって巨大化していきます。たとえば典型的なウェブ3階層のアプリケーションが以下の図のように成長していくとしましょう。
多くの組織ではソフトウェア開発は役割ごとにチーム分割されていて、上記であればフロント、バックエンド、データベースと分かれています。開発初期はアプリケーションのサイズも小さく、設計もクリアなため、3階層の各階層の間は密接に連携をとることができます。
たとえば各階層ごとに2人で構成された6人チームで開発しているとしましょう。フロントで表示するカテゴリ一覧をバックエンドから取得するためには
- バックエンドチームがフロントエンドにデータを提供する実装を追加
- フロントエンドチームはバックエンドにデータを要求し、それを表示する実装を追加
- データベースチームはカテゴリ一覧に使うテーブルやクエリの作成をおこなう
あたりの作業を足並みそろえて実施する必要があります。合計6人であれば、エンジニア間の距離も近いのでディスカッションや設計、具体的な変更作業にそれほど時間がかからないでしょう。
ただ、スタートアップはともかく、多くのエンタープライズでの大きな製品の開発には各チームが数十人で構成されることが珍しくありません。そのような状況では今までのレイヤーごとのチーム分けに加えて、扱うコンポーネントごとのチーム分け(右図の縦線)が発生します。そういった状況で先ほどと同じようにフロント、バックエンド、データベースにまたがる変更が必要な新機能の開発が必要となれば、それはちょっとしたプロジェクトになります。
まず、同じコンポーネント内のレイヤー間で協議をおこない変更ポイントを洗い出しますそうすると図にあるように多くの場合は隣のコンポーネントとの共有箇所などに影響が及ぶことがわかり、影響があるレイヤーのコンポーネント間で調整する必要が発生します。そして全員で足並みをそろえて、更新作業を実施します。小規模な昔であれば1日で終わっていた作業が、チームが大きくなり関係者の数が増えて数週間を要するようになってくるでしょう。
マイクロサービスが着目されたのは、こういった規模の問題を解決するのに有用な手段だからです。
たとえば先ほどの3階層アプリを「オリジナルの設計を維持したまま、モノリスとして徐々に巨大化させる」のではなく、「小さく(マイクロ)分けたコンポーネント(サービス)を組み合わせる設計」にしたとしましょう。
図でいうと以下のような状況です。
今まではフロント、バックエンド、データベースと技術視点でチーム分けをしていましたが、この構成ではサービスというビジネス寄りの視点で機能ごとにチームを分割しています。たとえばフロントからデータベースまで担当する決済サービスチーム、買い物サイトの中心的な商品ページをフロントからデータベースまで担当するショッピングページサービスチーム、在庫管理と発送システムの倉庫サービスチームといった具合です。
従来のような技術ごとに縦割りされていたチームでは商品ページや決済ページの構成を変更しようとすれば、それはチームをまたがる作業となります。一方、機能(サービス)ごとにチームを構成していたとすれば、各機能内に閉じる設計変更はチーム内で完結しますし、更新も特定サービスのみなので範囲が小さいです。どちらが素早く、的確な変更を加えられるか想像すると、後者のほうが有利であることが分かると思います。なお、図にあるように複数サービスをまたがる変更(多くないことが期待される)はサービス間の調整が必要になるので時間はかかります。
こういったサービスごとに分割するテクニックはドメイン駆動設計(DDD)などが有名です。ただ、DDDはアプリケーションの中でドメインに基づいてコードを分割する考え方ですので、その具体的な実装方法(技術的な解決法)はプロジェクトや開発者に委ねられています。マイクロサービスはこのDDDを使ったコード分割を、1つのアプリケーション内のコンポーネントレベルではなく、サービス(大きなアプリを構成する小さなアプリ)レベルで強制的におこないます。開発するサービスは他のサービスとコードが分離され、成果物のサイズも小さくなるため、変更(影響範囲が小さい)やテスト(項目数が減る)のコストも下がるといったメリットがあります。このように適切に分けられたマイクロサービスは、多くの人が頭を悩ませてきた「ソフトウェアが大きくなると開発の効率が悪くなる」という問題への有力な1つの解決法です。
ただし、マイクロサービスを採用するといくつかの代償が発生してきます。たとえば以下のようなものです。
- サービス間の結合度が高い(サービスごとに綺麗に役割を分離できていない)と、従来のモノリスよりも複雑で開発効率が下がる
- サービス間でのソースコードの共有が難しくなる(外部ライブラリなどでの共有はできるが、そこが昔と同じようにネックにならないように注意)
- サービス間の通信ドロップなど、新たに考慮するべき点が発生する
これらの代償コストが、マイクロサービスによるメリットより小さい場合にのみマイクロサービスを採用すべきです。
逆に言えば、マイクロサービスを採用することが適さない場面もあるということです。
もし、今後マイクロサービスを開発に利用していきたい場合は、簡単なアプリケーションでの利用から試してみることをおすすめします。複雑なアプリケーションほどマイクロサービスの設計も難しくなります。
慣れないうちは簡単なアプリケーションでマイクロサービスを使った開発に慣れるべきでしょう。
小さなアプリ(サービス)向きのSpring Boot
開発フレームワークは作りたいアプリケーションのかたちによって変わります。たとえば、2010年ごろの巨大なJavaのエンタープライズ向けのサービスと、当時に流行りだした Ruby on Rails は使うべき場面が全く異なります。大企業が数百人体制で1つの Ruby on Rails でアプリケーション開発/保守することはしませんし、5人程度のウェブ系のスタートアップがJavaの巨大なフレームワークを選択することもないでしょう。
マイクロサービスを構成する1つの小さなアプリの開発は、どちらかというと巨大なエンタープライズJavaというよりRuby on Railsに近いです。数万、数十万行のコードで動く重厚なアプリではなく、1つ1つは数千行程度で動く小さなアプリの集まりです。そういった状況でJavaを使った開発をしたいのであれば、それに適したフレームワークを選択すべきです。コンパイルとデプロイに数十分使うことを想定したフレームワークを、5000行のアプリに使う理由はありません。そういった理由で「より軽量でモダンな開発手法を使いやすい Spring Boot」はマイクロサービスの用途に適しています。
重厚なフレームワークとSpring Bootの比較は前回の記事でおこなっているので、詳しくは以下をご参照ください。
【前編】Spring Bootってなにがいいの
なお、Spring(Spring Boot以外のプロジェクトも含む)には「マイクロサービスとして使われることを想定した機能」のいくつかが最初から搭載されています。たとえばサービス間の通信をトレーシングするための仕組みが搭載されていたり、アプリケーションの入り口としてよく利用される「APIゲートウェイ」なども提供されています。Spring Boot以外の他のマイクロフレームワークを利用することでSpring Bootと同じように小さなサービスを迅速に開発することはできますが、上記のようなマイクロサービス独特の機能が必要であれば頑張って作り込む必要があるかもしれません。
マイクロサービスの詳しい話は専門書籍などに譲り、ここではSpring Bootを使ったマイクロサービスの構成例について紹介します。以下にSpringの公式ページにあるマイクロサービスのサンプル構成図を記載します。
図の左側はアプリケーションへのアクセス端末で、ブラウザだけではなくモバイルアプリ(スマホアプリ)やIoT機器からのアクセスも受け付けています。これらの端末からの通信はアプリケーションの入り口にある「API Gateway」で、ルールにしたがってバックエンドにあるSpring Bootで作られた複数のサービスに振り分けられます。これは自分で転送ルールを細かく定義できる「リバースプロキシ」のような存在ですので、スクラッチから自分で開発するのに比べて低コストで信頼性があり、既成のリバースプロキシアプリを使うよりも柔軟にプログラムで動きを制御できます。
SpringはこのAPI GatewayをSpring Cloud Gatewayとして提供しています。
※Spring Cloud Gateway
バックエンドにあるSpring Bootのアプリケーション群は先ほどの例のように「決済サービス」「ショッピンページサービス」などと分離されています。これらはそれぞれが自分自身のデータベースなどを持っており、1つのサービスとして完結しています。
サービス間の接続は
- ショッピングサービスで注文がされる
- ショッピングサービスが注文を決済サービスに依頼(REST APIや、メッセージキューなど)
- 決済サービスが依頼を処理
といったように必要時に連携をおこないます。
サービス間の連携はアプリケーションには不可欠ですが、連携しすぎてサービス間の結合が強くなりすぎるのを防ぐ必要があります。たとえば、ショッピングページサービスがカートにいれるごとに決済サービスに情報を送っていたら、ショッピングサービスは決済サービスなしには動作しないアプリケーションになってしまいます。
なお、図の右側にある図はマイクロサービスを構築するときに便利なSpringのフレームワーク群です。たとえば、Sluethはさきほど説明したサービス間の通信をトレースするための仕組みです。
マイクロサービスになぜコンテナが使われるか
最後にSpring Bootというトピックから外れてしまいますが、マイクロサービスにコンテナが採用されることが多い理由を説明します。マイクロサービスは極論してしまえば従来のモノリシックなアプリケーション内のモジュールを小さなアプリケーションとして強制的に分離して疎結合化する仕組みです。そのため、マイクロサービスの各サービスを仮想マシンはおろか、ベアメタルで構築しても間違いではありません。実際にマイクロサービスほどではないにせよ、多くのエンタープライズアプリケーションは仮想マシンレベルで機能分離されていることが多いです。
結論から言ってしまうと、これはコストとスピードの問題に起因しています。たとえば20個のサービスから構成されるマイクロサービスを仮想マシンで構成したとしましょう。必要な仮想マシンの台数は各サービスごとに「フロント(ないものも多い)、バックエンド、データベースを冗長性を持たせて構築」と考えると100VMを超えてしまう可能性があります。開発環境自体はマイクロサービスなのでサービスごとに5VM程度を構築すればいいかもしれませんが、サービス間の連携のテスト環境や、ステージング環境、本番環境にそれぞれ100VMを動かすのであれば構築/維持コストは馬鹿になりません。そして、各サービスのソフトウェアアップデートが毎日のように走る環境でVMにたいしてその変更を手動で加えていくのは現実的ではないでしょう。
コンテナ(Dockerなど)やコンテナオーケストレーション(Kubernetesなど)には、以下の特徴があります。
- 必要なライブラリなどを含めたOSごとアプリケーションをイメージ化できる
- イメージを別環境でコンテナ化することで、簡単にアプリを移行/更新することができる
- コンテナ間の接続に必要なネットワークなどの基盤をプラットフォームとして提供している
- 手順書ではなく構成定義ファイルで複数のコンテナ間の連携を構築できる
- 構築だけでなくメンテナンス操作(落ちたマシンの復旧など)も多くが自動化されている
- 同じ役割の1VMに比べて1コンテナは必要とするリソース(CPU,Memory)が少ない
これらの特徴を使うことで、マイクロサービスの膨大なサービスを人の手を多く煩わせずにスピード感を持って構築/更新/管理することが可能となります。100VMではスピードとコスト面でマイクロサービスを採用できなくても、100コンテナであれば採用できる可能性があるということです。
VMware TanzuではKubernetesの基盤提供だけではなく Springのサポートや、お客様のアプリケーションの新規/モダン化の開発支援などをサービスとして提供しています。
ご興味がある場合はぜひお問い合わせください。
関連ページ
この記事の著者:伊藤裕一
2011年よりインフラ業界でL2/L3ネットワーク及びHCIのサポート/プリセールスのエンジニアに従事。
2020年からVMwareでコンテナやKubernetesおよび開発/運用のモダン化を実現するTanzuの製品担当となる。
趣味でPythonプログラムを書いています。
DevOps Hubのアカウントをフォローして
更新情報を受け取る
-
Like on Facebook
-
Like on Feedly