レガシーなWebアプリにサービス間連携のためのAPIを追加する

運用するWebアプリの数が増えてくると、それらの間で何らかの連携を取らなければならないケースが増えてくる。

例えば、二つのWebアプリ間での連携を考えてみる。最も典型的なのは、下図のように同じデータベースを共有するやり方だろう。

web-api1

異なる立場の人が同じ情報にアクセスする必要がある場合に、このような形でアプリケーションを分ける事はよくある。例えば、エンドユーザーとシステム管理者が使うアプリケーションを分けておきたいケースは多いと思う。

二つのアプリケーションが、同じデータベースに対して等しく関心を持つのならこれで何の問題も無いように思えるが、同じデータベースを扱うので、少なからずデータアクセスの処理やビジネスロジックが重複する可能性が高い。ただ、この段階では、コミュニケーションコストを考えて若干の重複には目をつぶるか、共有ライブラリを作るぐらいの解決策が適当で、Web APIを作って共有するのは大げさに感じられるかもしれない。

Webアプリの横恋慕的依存関係

では、以下のケースだとどうだろうか?

web-api2

二つのWebアプリは、それぞれに関心を持つ別々のデータベースにアクセスしており、一見それぞれ独立しているように見えるが、例外的に「Web app 1」の方は「Web app 2」で管理している情報の一部にアクセスする必要が出てきた。果たして、「Web app 1」は直接「Database 2」にアクセスすべきだろうか?

というのが、たまチームが最近直面した問題であった。

サービス全体が小規模、あるいは二つのWebアプリが両方とも同じ人間によって開発されているのなら、手っ取り早く「Web app 1」から直接「Database 2」にアクセスしてしまうのもアリかもしれない。しかし、Webアプリごとに別々の担当者がいるようなケースでは、自分の担当するアプリケーションのデータベーススキーマを変更する必要が出てきた時に、そこに依存している他のアプリケーションの動作に影響を与えないよう、細心の注意を払わなければならなくなる。

あるいは、このような例外を際限なく認めると、どのWebアプリがどのデータベースにアクセスしているかを把握するのが難しくなってしまうという問題もある。

web-api-dep-hell

共有ライブラリ

例えば、最初のケースで挙げたように「Database 2」についての共有ライブラリを作って、それを「Web app 1」から利用するという手がある。これによってデータベースの変更をライブラリの背後に隠蔽出来るようになり、より安全に変更を行えるようになる。

web-api3

しかし、ライブラリ方式にも問題はある。まず一つは配布の問題である。一度作って配布したら終わりというわけにはいかないので、変更がある度に利用者に知らせて、利用者はライブラリを更新し、アプリケーションをビルドし直す必要がある。あるいは、ライブラリ方式は言語やフレームワークに依存するというのが一番大きな問題かもしれない。全てのWebアプリが同じ言語・フレームワークで開発されていれば問題ないが、このMicroservicesの時代ではそうでないことも多々あると思われる。実際に、たまチームでは「Web app 1」と「Web app 2」で異なるフレームワークを利用していたために、ライブラリ方式を選択肢に入れることは出来なかった。

Web API

そこで選択肢として残ったのが、「Database 2」を利用するための Web API を立ち上げて、「Web app 1」からはそのAPIにアクセスするという方法である。

もし仮に Web APIサーバーを立ち上げる場合、データアクセスやビジネスロジック部分は共有したいので、「Web app 2」からその部分を切り離して単独のAPIサーバーとし、残った「Web app 2」のフロントエンド部分と「Web app 1」でそのAPIを共有するというのが、一番美しい、いわゆるマイクロサービス的な解法のように思える。

web-api4

もし、今から「Web app 2」を開発するなら、今風に始めからAPIサーバーとフロントエンドを分けた形で実装するだろうと思う。しかし、「Web app 2」は数年前にフルスタックなフレームワークの上に構築された、ガッチガチな一枚岩のWebアプリであり、ロジック部分だけを切り出してAPI化するというのは結構な大手術になってしまう。「Web app 1」側から利用する機能もそれほど多くなく、独立したAPIサーバーを置くほどのメリットも感じられなかった。

Webアプリに追加したAPIへのアクセス制御

そこで、「Web app 2」に Web API を追加することにしたが、この場合に問題になるのは API へのアクセスコントロールである。「Web app 2」は、エンドユーザーが利用するWebアプリなので、そこにそのまま API を追加すると、エンドユーザーもそのAPIにアクセス出来てしまう。

web-api5

APIに認証を追加するという方法も考えられるが、セキュアな認証の仕組みを導入すること自体が大仕事である上に、本来の目的がアプリ間連携であることを考えると、もっとより簡単で安全な方法があるような気もする。

そこで思い付いたのが、元から設定されているエンドユーザー向けのTCPポート(80番)の他に、新たにAPI専用のポート(8091番)でもリクエストを受け付けるようにして、そのポートに対してファイヤーウォール(たまチームではAWSのセキュリティグループ)でアクセス制御するという方法である。

web-api6

たまチームの「Web app 2」はTomcat上で動くWebアプリだったので、API用のHTTPポートを追加するためには、設定ファイル conf/server.xml に以下の一行を追加するだけでよかった。

<Connector port="8091" protocol="HTTP/1.1" connectionTimeout="20000" />

ポートを追加するだけだと、依然として既存のポートでもAPIにアクセス出来てしまうので、API内部ではアクセス元のポートを調べて、専用ポート(8091)経由の場合にのみアクセスを許可するようにする。

たまチームのケースでは、Webアプリはクラスタリング(冗長化)構成になっていてロードバランサーが間に入るため、実際の構成は以下のようになった。

web-api-in-aws

Microservices時代のプロジェクト管理を考える (2) – サービス開発者の責任 / 優先順位

水平型から垂直型への移行」によって、今までアプリケーションのビルドまで面倒を見ればよかったアプリケーション開発者は、(マイクロ)サービス開発者となって、サーバーの構築 (Provisioning) から運用の一部まで面倒を見ることになった。一つ一つのサービスの独立性が高くなり、開発者あるいは開発チームの自律性は高まるが、やることが増えた分、外部に対する責任をより明確にしておかないと運用やメンテナンスの局面で様々な問題に直面することが予想される。

前回も少し触れたが、開発者やチームの責任を考えるときは、ソフトウェアのモジュール、あるいはオブジェクト指向でいうところの「責務 (responsibility)」の考え方が参考になる。まずシステムが果たすべき役割を検討して、そこから個々のサービスの責務を割り出し、その責務を実現することを第一優先とする。責任を明確にする理由の第一は、優先度に沿ってリソースを効率よく分配することである。「そもそも我々は何のためにこのシステムを作っているのか」という組織やチームのゴールが共有されてないとバランスの悪いリソース配分をしてしまう危険性が高くなる。

よくある問題の一例として、技術者が技術者の責任を考えると、この優先度の考え方が欠落して必要以上に内部の問題に耽溺してしまうということがある。例えば、技術者の間でよく行われる成果物のチェック方法としてコードレビューがある。スケーラビリティやセキュリティといった比較的目的が明確で判定しやすい指標はよいのだが、可読性や保守性といった、人によって判断が分かれることが多い指標は、無駄に議論が長引いたり、最悪なケースだと技術者同士の衝突に発展してしまうケースもある。

お前さえいなければあああああ!!!
お前さえいなければあああああ!!!

プログラムデザインの良し悪しを判断する時に厄介なのは、重要性が高いものほど多くの人間の同意を取るのが難しくなることである。関数やメソッドレベルの設計はまだ良くても、クラス・パッケージレベルやフレームワーク、アーキテクチャレベルになると、必要になる前提知識も増え、考え方のスペクトラムも大きくなるため、宗教論争的になって同意に至るのは容易ではない。結果的に、声が大きい人の意見が採用されるか、衝突を避けるために同意しづらい事項についてはスルーして、反論されづらい些末な指摘ばかりを繰り返すことになる。

このようなことを避けるため、レビューをするとしても出来るだけ価値の高い外部視点のもの、例えばテストコードをレビューすることを過去の現場では奨めてきたのだが、これも現実にはなかなか難しかった。技術者同士でレビューするとすれば、単体から結合テストレベルのコードになるが、テストの価値が相対的に低いので些末な感じは否めない。受け入れテストなど、もっと上位のテストになれば、ユーザー視点からのレビューも可能になるというのが、Acceptance Test Driven Development (ATDD) などの考え方であるが、以前の記事でFitの失敗を紹介したように、この分野もなかなか前途多難である。

さて話を戻すと、まずサービス開発者の責任を明確にして、責任を果たす限りは自由な裁量に任せるという形にした方が、プロジェクトの参加者全員にゴールへの意識を持たせつつも、無駄なコミュニケーションコストを省くことが出来るし、ゴールと関係ない内部の詳細に耽溺することも無くなる。というわけで、次回以降では、まずサービスの受益者は誰かということを考えて、そこからサービス開発者の果たすべき責任を一つ一つ定義してみたいと思う。

Microservicesアーキテクチャの思想性

Microservices and the Goal of Software Development:
http://www.infoq.com/news/2015/03/microservices-software

  • ソフトウェア開発の目的は business impact を生み出すこと
    • Business impactとは、組織内で観測可能な効果のこと(新規顧客やオペレーションコストの削減など)
    • Lead time(機会の発見からその解答を生み出すまでの時間)を最小限にすること
    • これを持続的に行うのが難しい
  • 三種類のコード
    1. 最近書いて理解しているコード
    2. よくテストされ、文書化されているコード
    3. 誰も知らないコード(不明瞭な依存関係、変更の影響範囲が局所化されない)
  • ソフトウェア開発最大の問題は、大部分のコードが 3 に分類されるということ
  • コードはきちんと運用出来るものだけを維持するようにするべき(3のコードを排除すべき)
  • North氏の提案する二つのパターン
    1. コードの目的(意図)を明確にすること
      • ソフトウェアの半減期は短い(「半減期」とは放射性崩壊の速さを示す物理学用語)
      • なので、それぞれのコードの目的を明確にして安定化に努めるべし
    2. 理解しやすいようにコードを分割統治すること
  • 上の二つのパターンを実現するために有効なのが「置換可能なコンポーネントによるアーキテクチャ (replaceable component architecture) 」
    • APIによって意図が明確にされたコンポーネント
    • コンポーネントは Alan Kay の発明したオブジェクト指向のオブジェクトのごとく、メッセージのやり取りをする小さなコンピュータのようなもの
  • Microservicesは、置換可能なコンポーネントによるアーキテクチャになり得る
    • 置換可能性と一貫性を追求すること
    • コンポーネントの「小ささ」よりも「置換可能性」の方が重要

現場のエンジニアがMicroservicesアーキテクチャを評価するときは、どうしてもその実用性や現実性、例えばプロセス間通信の多用によるオーバーヘッドなどが問題にされがちである。しかし、そのコンセプトをつぶさに見ていくと、これは単にアーキテクチャだけの問題ではなく、ソフトウェア開発はどうあるべきかという、ある種の思想を提示していることに気がつく。

開発の現場には「エンジニアとは外から持ち込まれた問題に解を与える者だ」という強い思い込みがあることが多い。しかし、これはプログラミングに対する誤解を含んでいるだけでなく、一つのシステムを長く育てるというケースにおいては、持続可能なモデルではない。

Martin Fowler氏が「The New Methodology」の中で「ソフトウエア開発は、全てが「デザイン」である」と指摘したのはもう大分昔のことだが、この考え方が開発の現場においてどれだけ浸透しているだろうか。

ソフトウエア開発は、本質的に、問題に対する解法の実装だけでなく、問題自体の検証と再設定を含むサイクルを繰り返す作業で、プログラミングというのはそのための強力な武器である。既に設定された問題を解決するためだけにプログラミングが存在し、そしてその問題設定を開発から切り離せると考えるのは、ソフトウエア開発に対する大きな誤解である。

Microservicesで提案されているのは、一つのサービスだけでなく、それを開発する開発者(あるいはチーム)も、できるだけ完結した存在になることが望ましいということである。中央集権的な開発体制ではなく、いくつものサービスとその開発を担当する cross-functional なチームが自律的に連携して、全体として大きなシステムを実現するという、オブジェクト指向が理想としていたようなモデルである。

あまり理想主義的になり過ぎるのも良くないが、このような考え方のルーツの一つにオブジェクト指向があり、それが考え方としてはデザインパターンやアジャイル開発などにつながり、プロダクトとしてはインターネットやiPhoneなどにつながっていることを考えると、実用性や現実性はともかく、その思想の重要性に注目しておく必要はあるだろう。

Microservices時代のプロジェクト管理を考える (1) – 水平型から垂直型へ

たまチームで管理しているGitHubリポジトリの数もどんどん増えてきて、今ではデプロイ可能なサービスだけでも20個ぐらい、リポジトリの総数は30を超えた。既に書いた通り、これらのサービスに共通するサーバーの構築 (Provisioning) と管理は、これまで一人のインフラ担当者が頑張って面倒を見てきた。しかしこのままサービスの数が増えていくと、インフラ部分がボトルネックになって効率が大きく損なわれることが目に見えている。そこで Deployment や Provisioning の管理方法を変えよう、というのが前回の話であった。

この方針変更をたまチームでは「水平型から垂直型への移行」と呼んでいる。

従来の管理方法だと、アプリケーション開発者はアプリケーションのビルドにだけに注力すればよい一方、インフラ担当者は、すべてのアプリの Deployment、サーバーの Provisioning や Monitoring まで面倒を見なければならなかった。

horizontal

これを垂直型に移行すると以下のようになる。

vertical

アプリケーション開発者はサービス開発者となり、アプリケーションのビルドに加えて、そのアプリケーションが動作する環境の Provisioning も担当し、最終的には Server Image をアウトプットするようにビルドプロセスを変更する。インフラの共通部分は最小限に抑えて、チームメンバー全員で共同管理出来るようにする。

水平型も垂直型も、それぞれ一長一短でありトレードオフがある。インフラの共通部分を厚くすれば、同じような構成のサーバーを一括で変更したり、多くのProvisioningコードを再利用出来る一方、共通部分と各アプリケーション開発者間で生じるコミュニケーションコストは馬鹿にならない。

あるいは、高度に訓練されたチームであれば、メンバーの誰もが共通部分(インフラ)とアプリケーションを行ったり来たりしながら自在に開発出来るのかもしれない。しかし、残念ながら我々たまチームはそこまでのレベルには達していないので、共通管理する部分を出来るだけ薄くして、後はアプリケーション開発者の裁量に任せてみることにした。

この考え方はソフトウェアのモジュール(あるいはオブジェクト)の考え方に似ている。モジュールは外部との責任を明確にする一方、内部構造については隠蔽しておくのが望ましいとされる(責任を果たす限り内部の詳細を問題にしない)。これはモジュール間のコミュニケーションコストを最小限にして、大量のモジュールが柔軟に連携出来るようにするための考え方であり、これをそのままサービス開発に応用するとすれば、アプリケーション開発者は、そのアプリケーションが外部に果たす役割を明確にする限り、他の開発者はその内部のことを(とりあえずは)気にしなくて済む。

サービスの数が増えていくのと同時に、技術の進歩に伴って、サービスの粒度が小さくなってきている。サービスが Microservice化すると、一つのリポジトリを複数の人間が管理するよりも、一人の人間が複数のリポジトリを管理するケースの方が増えてくる。その意味でも、コミュニケーションコストを抑える方法を考えておかなければならない。

個々のサービス開発者に課せられる責任は、オープンソースプロジェクトの開発に要求されることに似ている。そのプロジェクトを誰かに使って欲しければ、それが外部に提供する価値について、そしてその利用方法について、第三者が分かる形で説明する必要がある。ここで何を説明するべきかという最小限のラインを考えることが垂直型の勘所ではないかと思う。

ソフトウェアアーキテクチャと組織の構造

Martin Fowler氏が記事 Microservices の中で言及しているように、コンウェイの法則によれば、ある組織が開発するソフトウェアのアーキテクチャはその組織の構造をそっくりそのまま反映した形になる。今回の話で言えば、水平型のアーキテクチャを生み出す組織は職能で分けられた水平分業組織であり、垂直型の場合は cross-functional な垂直統合チーム型組織になる。

既に述べたように、いずれも一長一短があるため、どちらが良いとは一概には言えない。たまチームではたまチーム独自の事情があって、今回は垂直型への移行を試みることになった。この移行によって開発のやり方やアウトプットがどう変わったかはこのブログで逐一レポートする予定である。

最後に一つだけ垂直型への期待を述べるとすれば、現代は専門領域の境界がめまぐるしく変化する時代である。一つの専門領域に留まっていたら、あっという間に時代遅れになることも珍しく無い。このような時代においては、多様な領域に横断的に関わり、それらの融合をもって競争社会のアドバンテージとするべきであり、そういう意味ではチームだけでなく、個人にも cross-functional な能力が求められるのではないだろうか。