Amazon Web Services ブログ
サーバーレス LAMP スタック – Part 6: MVC からサーバーレスマイクロサービスへ
本投稿は AWS サーバーレス アプリケーションのシニアデベロッパーアドボケートである Benjamin Smith による寄稿です。
本シリーズの他のパートは以下のリンクからアクセスできます。また、関連するサンプルコードはこちらの GitHub リポジトリにあります。
- パート1:サーバーレス LAMP スタックの紹介
- パート2:リレーショナルデータベース
- パート3:Webサーバーの置き換え
- パート4:サーバーレス Laravel アプリの構築
- パート5:CDK コンストラクトライブラリ
この投稿では、マイクロサービスを使用してサーバーレス PHP アプリケーションを構築する方法をご理解いただけます。これまでご紹介してきた MVC フレームワークを利用した単一の Lambda 関数によるスケーラブルな Web ホスティングから、分離されたマイクロサービスモデルに移行する方法を示します。なお、このブログ投稿に付随するコード例は、この GitHub リポジトリにあります。
MVC アーキテクチャパターン
従来の LAMP スタックでは、多くの場合、Model-View-Controller(MVC)アーキテクチャを使って実装しています。これは、アプリケーションロジックをモデル、ビュー、およびコントローラの3つの部分に分離して実装する、確立された手法です。
- モデル:この部分はアプリケーションのデータを管理する役割を果たします。その役割は、データベースから生データを取得したり、コントローラからユーザー入力を受け取ることです。
- ビュー:このコンポーネントは表示に焦点を当てています。モデルから受け取ったデータをユーザーに提示します。ユーザーからの応答が認識され、コントローラコンポーネントに送信されます。
- コントローラ:この部分はアプリケーションロジックを担当します。ユーザー入力に応答し、データモデルオブジェクトに対して相互作用を実行します。
データ、ロジック、およびプレゼンテーション層を分離するという MVC の原則により、1つの層での変更が他の層に与える影響を最小限に抑えることが可能です。これにより、開発プロセスがスピードアップし、レイアウトの更新、ビジネスルールの変更、および新機能の追加が容易になります。コンポーネント化によって、再利用とリファクタリングの適用性があがり、同時/並行開発がしやすくなります。
サーバーレス LAMP スタックアーキテクチャについては、この投稿で説明しています。そこでは Webアプリケーションが 2つのコンポーネントに分割されています。アプリケーションにおける MVC フレームワークの役割をはたす単一の AWS Lambda 関数と、各応答を同期的に返す Amazon API Gateway です。このアーキテクチャは、従来の LAMP スタックアプリケーションでよく見られるスケーラビリティの課題に対処できます。管理されたインフラストラクチャと従量制の課金モデルによって、自動的にスケールします。加えて、サーバーレスパラダイムにおいて、分離性と再利用性のという MVC 原則をさらに高度に適用することも可能です。
「Lambda-lith: ラムダ-リス」
前述のアーキテクチャは、サーバーレスモノリスまたは「Lambda-lith(ラムダ-リス)」となっています。つまり、単一の Lambda 関数に MVC フレームワーク内のビジネスロジック全体が含まれています。この実装は、レガシー MVC からサーバーレスアプリケーションに「リフト & シフト」するのには良い選択です。単純なアプリケーションもこの方法で開始されることがよくありますが、アプリケーションが時間の経過とともに複雑になるにつれて、新しい課題が発生する可能性があります。
Lambda-lith 型アプリケーションは、多くの場合、アプリケーションロジック全体を含む単一のリポジトリでメンテナンスされます。これは、モノリポジトリと呼ばれることもあります。
モノリポジトリの場合、開発チーム間で所有権の責任を分離することが困難です。その結果、モノリポジトリ内のプロジェクトは相互に依存する傾向があり、緊密な結合となっていきます。相互接続されたすべてのモジュールと緊密に結合されたコードベースでは、定期的なリリースのリズムを維持するのが困難になってきます。小さな修正でも、コードベースの他の部分とともに更新することが必要になるため、アプリケーション全体を破壊することなくメンテナンスしていくというチャレンジに直面することになります。新しいメンバーがこのチームに開発者として加わっても、コードベースとすべての相互依存関係を学び、理解するのに時間がかかる可能性があります。
次の原則を適用することで、Lambda-lith な MVC アプリケーションを分離されたサーバーレスマイクロサービスにリファクタリングできます。
ある程度の規模のビジネスロジックを持つ、独立した Lambda 関数に分割
次の図は、すべてのビジネスロジックとルーティングロジックが単一の Lambda 関数に格納されているLambda-lithを示しています。すべてのリクエストは API Gateway 経由でこの関数にルーティングされます。関数コードベースには、リクエストを正しいモデル、ビュー、またはコントローラに送信するための `router.php` ファイルが含まれています。
これは、Apache や NGINX などの Webサーバーがすべてのリクエストを単一の index.php 関数にルーティングする従来の LAMP スタック実装に似ています。ただし、多くの場合、アプリケーションを複数の機能またはサービスに分割する方が実用的です。
次の図では、Lambda 関数は、各 CRUD 操作(Create, Read, Update, Delete)に基づいて複数の関数に分割されています。これで、内部ルーティングロジックがビジネスロジックから切り離されました。API Gateway サービスは、ルールを使用してリクエストを正しい Lambda 関数にルーティングします。これにより、各関数を個別にスケーリングでき、別の関数に影響を与えることなく1つの関数を更新できます。
すべての人またはサービスの厳密な検証を実施するためのマイクロ境界を構築
従来の MVC アプリケーションは、多くの場合、「城と堀」のセキュリティモデルを使用しています。これは、アプリケーション全体の周囲に境界を配置してセキュリティを確保し、悪意のある攻撃者からアプリケーションを保護しようとするものです。これは、入口または出口でリクエストとユーザーIDを検証することにより、アプリケーションまたはネットワークを保護します。
これは通常、ファイアウォール、プロキシサーバー、ハニーポット、およびその他の侵入防止ツールを使用して実現されます。そして、境界内でのアクティビティが安全であることを前提としています。ただし、いったんネットワークの脆弱性が見つかると、内部のすべてにアクセスできる可能性があります。
マイクロサービスベースのアプリケーションでは、開発者は「ゼロトラスト」セキュリティモデルを適用することがあります。これは、開発者が各リソースの周囲にマイクロ境界を構築するという考え方です。最小特権の原則と呼ばれることもあります。これにより、各リクエスト、サービス、またはユーザーが、その正当な目的に必要なデータまたはリソースにのみアクセスできるようになります。脆弱性がある場合でも、その影響範囲(爆風半径)はそのマイクロ境界内のサービスにのみ制限されます。
AWS アイデンティティおよびアクセス管理(IAM)のリソースポリシーと実行ロールを使用して、ビジネスロジックとセキュリティ体制を分離できます。Lambda リソースポリシーを使うと、関数の呼び出しを許可するイベントとサービスを定義できます。Lambda 実行ロールは、Lambda関数がアクセスできるリソースまたはサービスに制約を課します。リソースポリシーと実行ロールを定義するときは、最小限の権限セットから始めて、必要に応じて追加の権限を付与するようにしましょう。
共通の機能のためのビルディングブロックの作成
各コンポーネントはそれぞれが単一のビルディングブロックで、他のブロックと一緒にアプリケーションを構成します。これらのブロックは、特定のドメインで一連の機能を提供するマイクロサービスとなります。これにより、残りのマイクロサービスコンポーネントに影響を与えることなく、変更、アップグレード、機能入れ替えが容易になります。また、リポジトリの整理に役立つ自然な所有権の境界が作成されます。
その後、開発チームは、個々のマイクロサービスリポジトリに所有権を簡単に割り当てることができます。このブログ投稿で説明されているように、AWSサーバーレスアプリケーションモデル(AWS SAM)を使用すると、マイクロサービスを複数のコードリポジトリに整理できます。
メッセージを使用した、マイクロサービス間の接続および通信
従来の MVC アプリケーションでは、アプリケーション内の機能はメソッド呼び出しを使用して他の機能と通信します。サーバーレスマイクロサービスを使用するケースでは、コードは、短期生存型のステートレスな機能およびサービスに分散化された実装になります。これらの機能/サービス間の通信は、非同期メッセージまたは同期 HTTP リクエストを使用します。
同期通信
この方式では、サービスは他サービスの API を呼び出し、その応答を待ってからその後の処理を続行します。API Gateway を使用すると、バックエンドマイクロサービスへのフロントドア(入口となるインターフェース)を作成できます。API Gateway は、RESTful API と WebSocket API を作成および管理するためのフルマネージドサービスです。
API Gateway を使用してデータを転送すると、認証、APIトークン、アクセス制御、コードからのレート制限などの一般的な懸念事項に対処してくれるため、コードの複雑さを軽減するのに役立ちます。API Gateway は、サービスが明確に分離されている、厳密な認証要件がある、またはアカウント間で展開されているなどの条件下にある同期型の内部マイクロサービス通信にも使用できます。
次のアーキテクチャは、2つのアカウントに展開されるアプリケーションを示しています。予約サービスは、 API Gateway を介して、ロイヤリティ管理(ポイント管理)用のアカウントに存在するポイント管理機能を呼び出しています。
非同期通信
このパターンでは、サービスは他サービスの呼び出し時にメッセージを送信しますが、応答を待ちません。送信したメッセージに対応して 1つ以上のサービスが非同期的に処理を開始できます。ここで登場するサービスは相互に直接通信しないことが重要です。代わりに、呼び出し側のサービスは Amazon Simple Queue Service(SQS)や Amazon EventBridge などのブローカーにメッセージを送信します。受信側サービスは、ブローカーから関心のあるトピックをサブスクライブする形にします。これにより、ビジネスロジックとデータ転送処理が分離され、コードの複雑さを軽減できます。
できる限り、コードの代わりにサービスを使用
サービスファーストの考え方は、サーバーレスアプリケーション開発の重要な部分です。作成するコード行が多ければ多いほど、変更に対するプロジェクトの対応能力を制限し、チームに新しいメンバーが入った際のプロジェクト認識に対するオーバーヘッドを増加させます。ドメイン(メッセージング、ストレージ、オーケストレーション)ごとに適切な AWS サービスを使用すると、構築を高速化できます。この考え方を採用することで、開発者は、顧客に最大の価値をもたらすためにビジネス上の固有の課題の解決に集中できます。
これらの原則を適用してMVC 型の Lambda-lith をリファクタリングすることにより、次のような CRUD API 型のマイクロサービスを構築しました。このサンプルアプリケーションは、この GitHub リポジトリからデプロイできます。AWSサーバーレスアプリケーションモデル(AWS SAM)テンプレートを使用して、HTTP API、5つの Lambda 関数、Amazon DynamoDB テーブル、および必要なすべての IAM ロールを定義できます。
すべてのルーティングロジックと認証は、Amazon API Gateway によって管理されます。いまや、各 Lambda 関数は限定されたスコープと最小限のビジネスロジックとなっています。そして、この投稿で説明されているように、軽量のカスタムビルド PHP ランタイムを使用しており、各 Lambda 関数は、AWS PHP SDK を使用して DynamoDB テーブルとやり取りします。このアーキテクチャは、Web サイトバックエンドのサーバーレスマイクロサービスとして適していると言えるでしょう。
まとめ
この投稿では、単一の Lambda 関数を使用した MVC 型のスケーラブルな Web ホスティングから、分離されたマイクロサービスモデルに思考を変えることについて述べました。MVC アプリケーションをマイクロサービスの集まりに変えるために適用できる原則を説明し、そうすることの利点を示しました。CRUD 操作で分離されたサーバーレス PHP マイクロサービスのコード例を、デプロイ可能な AWS SAM テンプレートで提供しました。
PHP 開発チームは、Lambda-lith な MVC アプリケーションではなく機能分離されたマイクロサービスモデルに思考変換できます。これにより、インフラストラクチャを管理することなく、コードに集中して顧客要件に対応することができます。
なお、サーバレスアプリケーションを構築するにあたっての、より多くの情報はこちらで見つけることができます。
原文はこちらです。