本記事は『実践マイクロサービスAPI』(José Haro Peralta著)の「第1章:マイクロサービスAPIとは何か」から一部を抜粋したものです。掲載にあたって編集しています。
マイクロサービスの定義
マイクロサービスとは何だろうか。マイクロサービスを定義する方法はさまざまである。ここでは、マイクロサービスアーキテクチャのどの側面を強調したいかに応じて、それぞれ少し異なるものの、関連のある定義を紹介したい。マイクロサービス分野で最も影響力のある著者の1人であるSam Newmanの定義は、「マイクロサービスとは、一体となって動作する、小規模で自律的なサービスのことである」という最小限のものだ。
この定義は、マイクロサービスが互いに独立して動作するアプリケーションでありながら、それぞれのタスクの実行に共同で取り組めることを重視している。また、マイクロサービスが「小規模」であることも強調している。ここで言う「小規模」とは、マイクロサービスのコードベースのサイズのことではない──マイクロサービスが明確に定義された狭いスコープを持つアプリケーションであり、1つのことを適切に行うという単一責任の原則に従っていることを指している。
大きな反響を生んだJames LewisとMartin Fowlerの記事では、マイクロサービスがさらに詳しく定義されている。LewisとFowlerはマイクロサービスを次のようなアーキテクチャスタイルとして定義している。「1つのアプリケーションを一連の小さなサービスの組み合わせとして開発するアプローチ。これらのサービスはそれぞれ独自のプロセスで実行され、軽量なメカニズム(多くの場合はHTTP リソースAPI)を使って通信する」。
この定義は、サービスがそれぞれ独立したプロセスで実行されると述べることで、サービスの自律性を重視している。LewisとFowlerは、「小さな」サービスと述べることで、マイクロサービスの責任の範囲が狭いことも強調している。そして、マイクロサービスはHTTPなどの軽量なプロトコルを通じて通信すると明確に表現している。
定義
マイクロサービスとは、システムの各コンポーネントを個別にデプロイ可能なサービスとして設計するアーキテクチャスタイルのことである。マイクロサービスは明確に定義されたビジネスサブドメインを中心として設計され、HTTPなどの軽量なプロトコルを使って互いにやり取りする。
この定義から、マイクロサービスをアーキテクチャスタイルとして定義できることがわかる。このアーキテクチャスタイルでは、サービスは少数の明確に定義された関連する機能を実行するコンポーネントである。図1に示すように、この定義は、マイクロサービスが特定のビジネスサブドメイン(たとえば、決済処理、電子メールの送信、顧客の注文の処理など)を中心として設計され、構築されることを意味する。
マイクロサービスは(通常は独立した環境で実行される)独立したプロセスとしてデプロイされる。マイクロサービスの機能には、明確に定義されたインターフェイスを使ってアクセスする。本書では、Web APIを使ってアクセスできるマイクロサービスの設計と構築について説明するが、メッセージキューなど、他の種類のインターフェイスを使ってマイクロサービスの機能にアクセスすることもできる。
マイクロサービスとモノリス
マイクロサービスとは何かがわかったところで、モノリシックアプリケーションパターンと比較してみよう。マイクロサービスとは対照的に、モノリスとは、すべての機能が1つのビルドとしてまとめてデプロイされ、同じプロセスで実行されるシステムのことである。たとえば図2は、決済サービス、注文サービス、配送サービス、カスタマーサポートサービスの4つのサービスを持つフードデリバリアプリケーションを示している。
このアプリケーションはモノリスとして実装されるため、すべての機能がまとめてデプロイされる。モノリシックアプリケーションのインスタンスを複数実行し、冗長性とスケーラビリティを確保するためにそれらのインスタンスを並行して実行することもできるが、それぞれのプロセスでアプリケーション全体が実行されることに変わりはない。
定義
モノリスとは、アプリケーション全体が1つのビルドとしてデプロイされるアーキテクチャパターンのことである。
状況によっては、アーキテクチャとしてモノリスを選択するのが正しいこともある。たとえば、コードベースが小さく、あまり大きくならないことが予想される場合は、モノリスを使うことになるだろう。モノリスにも利点がある。まず、実装全体が同じコードベースに含まれているため、さまざまなサブドメインのデータやロジックに簡単にアクセスできる。
そして、何もかも同じプロセス内で実行されるため、アプリケーション全体でエラーを簡単に追跡できる。つまり、コードのさまざまな部分にブレークポイントをいくつか設定するだけで、何かがうまくいかなくなったときに何が起きているのかを詳細に把握できる。さらに、すべてのコードが同じプロジェクトのスコープ内にあるため、別のサブドメインの機能を利用するときに、普段開発に使っているエディタの生産性機能を活用することもできる。
ただし、アプリケーションが成長し、より複雑になるに従い、このタイプのアーキテクチャは限界を迎える。そうなるのは、管理が手に負えなくなるほどコードベースが成長したときと、コードを理解するのが難しくなったときである。さらに、同じプロジェクト内の他のサブドメインのコードが再利用できる状態になっていると、コンポーネントどうしの密結合という結果につながることも多い。密結合が発生するのは、コンポーネントが別のコードの実装上の詳細に依存しているときである。
モノリスが大きいほど、テストに時間がかかるようになる。モノリスのありとあらゆる部分をテストしなければならないし、新しい機能を追加するたびにテストスイートがさらに大きくなる。結果として、デプロイに時間がかかるようになり、開発者がつい同じリリース内で変更を積み上げるようになるため、リリースがより難しくなる。多くの変更がまとめてリリースされるため、リリースに新しいバグが紛れ込んだ場合、バグの原因となった変更を突き止めてロールバックするのは難しいことが多い。
また、アプリケーション全体が同じプロセス内で実行されるため、あるコンポーネントのリソースをスケーリングすると、アプリケーション全体をスケーリングすることになる。簡単に言うと、コードの変更に伴うリスクが徐々に高くなり、デプロイメントの管理がより困難になる。
このような問題への対処にマイクロサービスはどのように役立つのだろうか。 マイクロサービスは、モノリシックアプリケーション関連の問題のいくつかに対処するために、コンポーネントどうしを厳格な境界で切り離す。マイクロサービスを使ってアプリケーションを実装する際、マイクロサービスはそれぞれ異なるプロセス(多くの場合は異なるサーバーまたは仮想マシン)で実行され、まったく異なるデプロイメントモデルを使うことができる。実際には、それらのサービスをまったく異なるプログラミング言語で記述することもできる(だからといって、そうすべきだというわけではない!)。
マイクロサービスのコードベースはモノリスのものよりも小さく、それらのロジックは自己完結型で、特定のビジネスサブドメインのスコープ内で定義される。このため、マイクロサービスのほうがテストしやすく、テストスイートの実行にモノリスほど時間がかからない。コードレベルでは、プラットフォームの他のコンポーネントに対する依存関係がないため(一部の共有ライブラリを除く)、マイクロサービスのコードのほうが明確で、リファクタリングしやすい。
つまり、コードを徐々に改善していけるため、より管理しやすいコードになる。結果として、コードに小さな変更を加えて、より頻繁にリリースできるようになる。リリースは小さいほど制御しやすく、バグが見つかった場合のロールバックも容易である。とはいえ、マイクロサービスが万能薬ではないことを強調しておきたい。マイクロサービスにも制限があり、マイクロサービスならではの課題がある。
マイクロサービスとは何かを理解し、モノリシックアプリケーションと比較したところで、一歩下がって、このタイプのアーキテクチャが登場するに至った背景を振り返ってみよう。
現在のマイクロサービスとここまでの道のり
いろいろな意味で、マイクロサービスは新しいものではない。マイクロサービスの概念が普及するずっと前から、企業はコンポーネントを独立したアプリケーションとして実装し、デプロイしていた。単に、それらをマイクロサービスと呼んでいなかっただけである。AmazonのCTOであるWerner Vogelsは、Amazonが2000年代の初めに、このタイプのアーキテクチャを試験的に導入し始めたときのことを次のように語っている。
その頃には、AmazonのWebサイトのコードベースはアーキテクチャパターンとおぼしきものがない複雑なシステムに成長しており、新しいリリースの作成やシステムのスケーリングは深刻な悩みの種となっていた。これらの問題に対処するために、Amazonはコード内でロジックの独立した部分を探し、それらを個別にデプロイできるコンポーネントとして切り離し、それらのコンポーネントの前にAPIを配置することにした。また、その過程で、これらのコンポーネントに属しているデータも特定し、APIを使わない限り、システムの他の部分からそれらのデータにアクセスできないようにした。
Amazonは、この新しいタイプのアーキテクチャをサービス指向アーキテクチャ(service-oriented architecture)と呼んだ。Netflixも、このタイプの大規模なアーキテクチャスタイルの先駆者であり、そのアーキテクチャを「細粒度のサービス指向アーキテクチャ」と呼んだ。
「マイクロサービス」は、このタイプのアーキテクチャを表す用語として2010年代の初めにもてはやされた。たとえばJames Lewisは、2012年にクラクフで開催された第33回学位会議の「Micro-Services―Java, the Unix way」というプレゼンテーションで、この概念を使っている。この概念は、マイクロサービスのアーキテクチャ的な特徴に関するMartin FowlerとJames Lewisの2014年の論文と、一世を風靡したNewmanの著書『Building Microservices』によって不動のものとなった。
現在、マイクロサービスは広く使われているアーキテクチャスタイルである。テクノロジーが重要な役割を果たす企業のほとんどが、すでにマイクロサービスを使っているか、その導入に向けて動いている。スタートアップがマイクロサービスを使ってプラットフォームの実装を開始するのも当たり前のことになった。とはいえ、マイクロサービスは万人向けではない。マイクロサービスは大きなメリットをもたらすが、かなり厳しい課題も抱えている。
Web APIとは何か
次に、Web APIについて説明する。Web APIが、「アプリケーションプログラミングインターフェイス(API)」というより一般的な概念の特例であることがわかるだろう。ここで重要となるのは、APIがアプリケーションの上位層にすぎないこと、そしてさまざまな種類のインターフェイスが存在することである。そこで、APIとは何かを定義することから始めて、マイクロサービス間の統合の促進にAPIがどのように役立つのかを見ていく。
APIとは何か
APIとは、アプリケーションとプログラミング的にやり取りできるようにするインターフェイスのことである。プログラミングインターフェイスは、ユーザーインターフェイス(UI)を使ってアプリケーションとやり取りするグラフィックインターフェイスとは対照的に、コードまたはターミナルから利用できるインターフェイスである。コマンドラインインターフェイス(CLI:ターミナルからアプリケーションを操作できるインターフェイス)、デスクトップUI、Web UI、Web APIなど、何種類かのアプリケーションインターフェイスがある。図3に示すように、アプリケーションでは、これらのインターフェイスを1つ以上使うことができる。
この概念を具体的に理解するために、よく知られているクライアントURL(cURL)について考えてみよう。cURLはlibcurlライブラリに対するCLIである。libcurlはURL(Uniform Resource Locator)とのやり取りを可能にする機能を実装しているが、cURLでは、それらの機能にCLIを通じてアクセスできる。たとえばcURLを使って、URLに対してGETリクエストを送信できる。
$ curl -L http://www.google.com
また、cURLで-Oオプションを指定すると、URLの内容をファイルにダウンロードできる。
$ curl -O http://www.gnu.org/software/gettext/manual/gettext.html
cURLのCLIの背後にあるのがlibcurlライブラリである。なお、ソースコードから直接アクセスし、このアプリケーション用に新しいタイプのインターフェイスを構築することもできる。
Web APIとは何か
APIとは何かを理解したところで、Web APIの決定的な特徴について説明しよう。Web APIとは、HTTP(Hypertext Transfer Protocol)を使ってデータを転送するAPIのことである。
HTTPはインターネットを支える通信プロトコルであり、テキスト、画像、動画、JSONなど、さまざまな種類のメディアをやり取りできるようにする。HTTPはURLの概念を使って、インターネット上でリソースの位置を特定する。HTTPには、リクエストメソッド(GET、POST、PUTなど)やHTTPヘッダーなど、サーバーとのさらに厳密なやり取りを可能にするためにAPIテクノロジーで活用できる機能がある。Web APIは、SOAP、REST、GraphQL、gRPC、その他のテクノロジーを使って実装されている。
APIはマイクロサービスの統合促進にどのように役立つか
マイクロサービスでは、相互の通信にAPIを使う。つまり、APIはマイクロサービスのインターフェイスである。これらのAPIは標準プロトコルを使って文書化される。APIドキュメントは、マイクロサービスとやり取りするために何をする必要があるか、マイクロサービスからどのようなレスポンスが返されると期待できるかを正確に伝えるものとなる。
APIドキュメントがうまく書かれていればいるほど、APIコンシューマがAPIの仕組みをより明確に理解できるようになる。その意味では、APIドキュメントはサービス間のコントラクトに相当する(図4)。クライアントとサーバーの両方がAPIドキュメントに従っている限り、通信(やり取り)は期待どおりにうまくいく。
FowlerとLewisは、マイクロサービスを統合するための最も効果的な戦略として、「スマートエンドポイントを提供し、ダムパイプを使って通信する」というアイデアを世に広めた。このアイデアは、次の2つのことを確立するUnixシステムの設計原則に端を発している。
- システムは、1つのことだけを行う、独立した小さなコンポーネントで構成すべきである。
- 各コンポーネントの出力は、別のコンポーネントの入力として簡単に使えるように設計すべきである。
Unixプログラムはパイプラインを使って相互に通信する。パイプラインは、あるアプリケーションから別のアプリケーションにメッセージを渡すための単純なメカニズムである。このプロセスを具体的に理解するために、Unixベースのマシン(MacやLinuxコンピュータなど)のターミナルから実行できる次のコマンドチェーンについて考えてみよう。
$ history | less
historyコマンドは、bashプロファイルを使って実行されたすべてのコマンドのリストを表示する。コマンドのリストは長くなることがあり、lessコマンドを使ってhistoryの出力をページ単位に分けたいことがある。あるコマンドから別のコマンドにデータを渡すには、パイプ文字(|)を使う。この文字は、historyの出力をキャプチャし、lessコマンドの入力としてパイプ処理せよというシェルへの命令である。
この種のパイプは、あるプロセスから別のプロセスへメッセージを渡すだけなので、「ダムパイプ」と呼ばれる。図5に示すように、Web APIはHTTPを使ってデータをやり取りする。データトランスポート層は、私たちが使っている具体的なAPIプロトコルについて何も知らない点では、「ダムパイプ」に相当する。データを処理するために必要なロジックはすべてAPI自体に含まれている。
API自体は安定していなければならないが、APIの背後にあるサービスがAPIドキュメントに準拠している限り、どのサービスについてもその内部実装を変更することができる。つまり、APIのコンシューマは以前とまったく同じ方法で引き続きAPIを呼び出すことができ、同じレスポンスを受け取るはずである。このことは、マイクロサービスアーキテクチャのもう1つの重要な概念である置換可能性(replaceability)につながる。
置換可能性とは、エンドポイントの背後にあるコードベースは完全に置き換えることができるべきだが、そうしたとしても、エンドポイント──ひいてはサービス間の通信は依然としてうまくいく、というものだ。APIとは何か、サービス間の統合の促進にAPIがどのように役立つかを理解したところで、マイクロサービスがもたらす最も重要な課題を調べてみよう。
本記事内容の続きは、『実践マイクロサービスAPI』に収録されています。マイクロサービスの課題とそれを克服する方法、REST APIとGraphQL APIの設計・構築、さらにマイクロサービスAPIのセキュリティ、テスト、デプロイの方法まで詳細に解説。マイクロサービスAPIを利用するすべてのエンジニアの足元を支える1冊です。