運用するWebアプリの数が増えてくると、それらの間で何らかの連携を取らなければならないケースが増えてくる。
例えば、二つのWebアプリ間での連携を考えてみる。最も典型的なのは、下図のように同じデータベースを共有するやり方だろう。
異なる立場の人が同じ情報にアクセスする必要がある場合に、このような形でアプリケーションを分ける事はよくある。例えば、エンドユーザーとシステム管理者が使うアプリケーションを分けておきたいケースは多いと思う。
二つのアプリケーションが、同じデータベースに対して等しく関心を持つのならこれで何の問題も無いように思えるが、同じデータベースを扱うので、少なからずデータアクセスの処理やビジネスロジックが重複する可能性が高い。ただ、この段階では、コミュニケーションコストを考えて若干の重複には目をつぶるか、共有ライブラリを作るぐらいの解決策が適当で、Web APIを作って共有するのは大げさに感じられるかもしれない。
Webアプリの横恋慕的依存関係
では、以下のケースだとどうだろうか?
二つのWebアプリは、それぞれに関心を持つ別々のデータベースにアクセスしており、一見それぞれ独立しているように見えるが、例外的に「Web app 1」の方は「Web app 2」で管理している情報の一部にアクセスする必要が出てきた。果たして、「Web app 1」は直接「Database 2」にアクセスすべきだろうか?
というのが、たまチームが最近直面した問題であった。
サービス全体が小規模、あるいは二つのWebアプリが両方とも同じ人間によって開発されているのなら、手っ取り早く「Web app 1」から直接「Database 2」にアクセスしてしまうのもアリかもしれない。しかし、Webアプリごとに別々の担当者がいるようなケースでは、自分の担当するアプリケーションのデータベーススキーマを変更する必要が出てきた時に、そこに依存している他のアプリケーションの動作に影響を与えないよう、細心の注意を払わなければならなくなる。
あるいは、このような例外を際限なく認めると、どのWebアプリがどのデータベースにアクセスしているかを把握するのが難しくなってしまうという問題もある。
共有ライブラリ
例えば、最初のケースで挙げたように「Database 2」についての共有ライブラリを作って、それを「Web app 1」から利用するという手がある。これによってデータベースの変更をライブラリの背後に隠蔽出来るようになり、より安全に変更を行えるようになる。
しかし、ライブラリ方式にも問題はある。まず一つは配布の問題である。一度作って配布したら終わりというわけにはいかないので、変更がある度に利用者に知らせて、利用者はライブラリを更新し、アプリケーションをビルドし直す必要がある。あるいは、ライブラリ方式は言語やフレームワークに依存するというのが一番大きな問題かもしれない。全ての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 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にアクセス出来てしまう。
APIに認証を追加するという方法も考えられるが、セキュアな認証の仕組みを導入すること自体が大仕事である上に、本来の目的がアプリ間連携であることを考えると、もっとより簡単で安全な方法があるような気もする。
そこで思い付いたのが、元から設定されているエンドユーザー向けのTCPポート(80番)の他に、新たにAPI専用のポート(8091番)でもリクエストを受け付けるようにして、そのポートに対してファイヤーウォール(たまチームではAWSのセキュリティグループ)でアクセス制御するという方法である。
たまチームの「Web app 2」はTomcat上で動くWebアプリだったので、API用のHTTPポートを追加するためには、設定ファイル conf/server.xml
に以下の一行を追加するだけでよかった。
<Connector port="8091" protocol="HTTP/1.1" connectionTimeout="20000" />
ポートを追加するだけだと、依然として既存のポートでもAPIにアクセス出来てしまうので、API内部ではアクセス元のポートを調べて、専用ポート(8091)経由の場合にのみアクセスを許可するようにする。
たまチームのケースでは、Webアプリはクラスタリング(冗長化)構成になっていてロードバランサーが間に入るため、実際の構成は以下のようになった。