BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル サーバレスシステムのデザインパターン

サーバレスシステムのデザインパターン

ブックマーク

キーポイント

  • We can define different categories of design patterns, including OOP design patterns, organizational patterns, and so on.
  • A number of design patterns are specifically suited to the serverless paradigm.
  • The Pipes and Filters design pattern can be used to decouple a serveless system into  simple functional units interconnected in a chain.
  • The AWS EventBridge, Simple Queue Service, and Lambda layer are useful abstractions to build event-based serverless systems.
     

原文(投稿日:2021/12/01)へのリンク

ソフトウェアアーキテクチャとアプリケーションデザインのランドスケープでは、デザインパターンが基本的な構成要素の1つです。デザインパターンの概念は、1970年代後半に Christopher Alexander 氏によって紹介されました。「The Timeless Way of Building (時を超えた建設の道)、1979年、A Pattern Language—Towns, Buildings, Construction (パタン・ランゲージ 環境設計の手引)、1977年」:

それぞれのパターンは、私たちの環境で何度も発生する問題を説明し、次にその問題の解決策のコアを説明します。これにより、同じ方法を2回行うことなく、この解決策を何百万回も使用できるようになります。- Alexander 氏他

後に、この概念はソフトウェアコミュニティによって採用され、ソフトウェアデザイン分野に適用されるさまざまな種類のデザインパターンが作られました。

オブジェクト指向のデザインパターンは、OOP アプローチに従ってコードレベルの構成要素を設計するための抽象的なツールです。Erich Gamma 氏、Richard Helm 氏、Ralph Johnson 氏、および John Vlissides 氏 (ギャング・オブ・フォー、GoF) による「Design Patterns Elements of Reusable Object-Oriented Software (オブジェクト指向における再利用のためのデザインパターン)」という本は、開発者向けのオブジェクト指向設計分野の一種のガイドブックです。この本は1994年に最初に出版され、それ以来、デザインパターンはソフトウェアデザインの不可欠な部分です。

デザインパターンは組織にも適用されます。大規模な組織は、確かに大量の機械のようなものです。ギア、パイプ、フィルタ、モーターなどがたくさんあります。人間の脳をデジタル化しようとしているデジタル時代では、企業の機械をデジタルにすることは特別なことではありません。企業の一部またはセクションをデジタル化するだけでは不十分です。実際、企業を運営するには、さまざまな部分をすべて統合する必要があります。エンタープライズとソリューションアーキテクトは、パターンを使って日常の統合シナリオを解決しようとしています。プロセスは本当にアジャイルです。毎日、世界の隅々から、思想家は、問題を解決し新しい種類の企業統合パターンを発明しています。ここで、この分野の2人の指導者、Martin Fowler 氏と Gregor Hohpe 氏について言及したいと思います。

トップマネジメントは常にデジタル製品の新しいバリエーションが毎日登場する新しいテクノロジーのトレンドを追いかけています。ビジネスの人々は、このデジタルオーシャンから最大限の利益を得ることに力を注いでいます。したがって、レガシーシステムの最新化、別名デジタルトランスフォーメーションをする必要があります。この分野でも、Ian Cartwright 氏、Rob Horn 氏、James Lewis 氏などの研究者は、最近の記事「Patterns of Legacy Displacement」で、長年のマイグレーションの体験に基づくいくつかのパターンを提案しました。

このしなやかな (limber) 時代の中、アジリティが成功への鍵です。回復力 (resiliency)、継続的デリバリー、市場投入までの時間の短縮、効率的な開発などが、マイクロサービスへの移行を推進する力の一部です。ただし、同時に、すべてのシナリオがマイクロサービスに適しているわけではありません。この境界線がどこにあるかを理解するために「Microservices patterns (マイクロサービスパターン)」の著者である Chris Richardson 氏は、さまざまなユースケースに対応する多くのマイクロサービスパターンを提案しました。

上述したこれらより多くのパターンのカテゴリがあります。実際、エンタープライズシステムアーキテクチャソフトウェアのパターンに関する文献が豊富にあります。つまり、アーキテクトは要件を満たす方法を賢明に選択する必要があるということです。

サーバレスワールド入門

ここまで、さまざまな種類の要件とアーキテクチャに対応するさまざまなジャンルのパターンについて説明してきましたが、重要なケースの1つであるサーバレスシステムを除外しました。現在の技術分野では、サーバレスは、特に IaaS とクラウドのランドスケープにおいて、最も重要で活気のあるアプローチの1つです。

サーバレスプラットフォームは、サービスとしての関数 (FaaS) とサービスとしてのバックエンド (BaaS) の2つの大きなカテゴリに分類できます。FaaS パラダイムにより、顧客は、基盤のインフラストラクチャを管理することなく、アプリケーションをビルド、デプロイ、実行、および管理できます。代わりに、BaaS は、認証、ストレージ管理、通知、メッセージングなど、クラウド上で特定のタスクを処理するためのオンラインサービスを提供します。

すべてのサーバレス計算指向サービスは FaaS のカテゴリに分類され (AWS LambdaGoogle Cloud FunctionGoogle RunApache OpenWhisk など)、残りのサーバレスサービスはサーバレスストレージ (AWS DynamoDBAWS S3Google Cloud Storage)、サーバレスワークフロー (AWS Step Function)、サーバレスメッセージング (AWS SNSAWS SQSGoogle PubSub) などのように BaaS に分類できます。

サーバレスという用語は非常に魅力的ですが、少し誤解を招く可能性があります。サーバなしで実際に存在できるサービスはありますか? クラウドプロバイダが提供するすべてのサーバレスコンポーネントの背後には、単純な魔法があります: それらすべてに対して、カーテンの後ろにはサーバがあります。クラウドプロバイダは、物理やバーチャルサーバのスケーラビリティ (オートスケーリング)、呼び出し可能性、同時実行性、ネットワークなどを管理し、エンドユーザがカスタムランタイムや環境変数、バージョン、セキュリティボールト、同時実行性、読取/書込キャパシティなどを構成するためのインターフェイスを提供する責任があります。

サーバレスアプローチを使用してアーキテクチャを実装することにフォーカスすると、いくつかの基本的なハイレベルの質問が出てくる可能性があります。

  • サーバレスの構成要素を使用してシステムを設計するために使用する好ましいアーキテクチャスタイルは何でしょうか?
  • アプリケーションは純粋にサーバレスになるのでしょうか、それともハイブリッドアプローチを採用するのでしょうか?
  • サーバレスアプローチを採用すべきユースケースは何でしょうか?
  • サーバレスアプリケーションを実装するための再利用可能なアーキテクチャの構成要素またはパターンはありますか?

この記事の残りの部分で、4番目の質問に対する回答を詳しく説明しようと思います。

サーバレスパターン

サーバレスパラダイムは、テクノロジーのランドスケープでは比較的新しく急速に進化しています。メカニズム、適用性、ユースケース、使用パターン、実装パターンなど、さまざまな側面が新しいステップごとに変化しています。それだけではありません。クラウドベンダーは新しいサーバレス製品を発明しているため、同じサーバレスパターンをさまざまな価格とパフォーマンスで複数の方法で実装できます。世界中で、ソフトウェアエンジニアは異なる視点から異なる方法で考えています。結果として、現在のところ、サーバレスシステムを構築するための一般的なアプローチはありません。

API Days Australia カンファレンスで、AWS ソリューションアーキテクトの Cassandra Bonner 氏は、Lambda サーバレスサービスの5つの主要な使用パターンを発表しました。彼女は、要件の観点からこれらの5つのパターンを次のように定義しました:

  1. イベント駆動のデータ処理。
  2. WEB アプリケーション。
  3. モバイルと Internet-of-Things アプリケーション。
  4. アプリケーションエコシステム。
  5. イベントワークフロー。

Peter Sbarski 氏は、彼の著書「Serverless Architectures on AWS (AWSによるサーバーレスアーキテクチャ)」で、サーバレスアーキテクチャの一般的な設計問題を解決するための5つのパターンをリストしました。次のとおりです:

  1. コマンド
  2. メッセージング
  3. 優先キュー
  4. ファンアウト
  5. パイプとフィルタ

これらのパターンは、サーバレスアーキテクチャに限定されたものではありません。実際、これらは分散システムのパターンのサブセットです。たとえば、Gregor Hohpe 氏と Bobby Woolf 氏によって整理された「65 のメッセージングパターン」は、このようなパターンの最も広範なコレクションを表しています。

この記事で私の意図は、サーバレスアプローチに従って AWS クラウド環境にパイプとフィルタのパターンを実装することです。いくつかの代替実装とそれぞれの長所と短所について説明します。再利用性は、実装時に検討する特定の側面の1つです。

サーバレスアーキテクチャのパイプとフィルタパターン

アジャイルプログラミングは、マイクロサービスに適した環境であると共に、設計とコーディングの一般的なアプローチがモノリスの時代から変わりました。すべてのロジックを単一の機能ユニットに詰め込む代わりに、アジャイルおよびマイクロサービスの開発者は、単一責任の原則 (SRP) に従うよりきめ細かいサービスまたはタスクを好みます。このことを念頭に置いて、開発者は複雑な機能を一連の管理可能な個別のタスクに分解できます。それぞれのタスクは、クライアントから入力を取得し、その入力を使って特定の責務を実行し、出力を生成します。出力は、次のタスクに転送されます。この原則に従って、複数のタスクで一連のタスクを構成します。それぞれのタスクは、入力データを要求される出力に変換します。これは、次のタスクの入力になります。これらの変換器は伝統的にフィルタと呼ばれ、あるフィルタから別のフィルタにデータを渡すためのコネクターはパイプと呼ばれます。

パイプとフィルタパターンの非常に一般的な使用法は次のとおりです: クライアントリクエストがサーバに到着すると、リクエストのペイロードはフィルタリングと認証のプロセスを経る必要があります。リクエストが処理されている間、新しいトラフィックが着信する可能性があり、システムは、ビジネスロジックを実行する前に、リクエストのペイロードの復号化、認証、検証、重複メッセージやイベントの削除などの一般的なタスクを実行する必要があります。

別のシナリオでは、e-コマースアプリのカートに商品を追加するプロセスです。この場合、一連のタスクには次のタスクのようになります: 提供可能かの確認、価格の計算、割引の適用、カートの合計の更新など。これらの各ステップについて、フィルタを作成し、パイプを使用してすべてを結合できます。

このパターンの実装で最も簡単な方法は、lambda 関数を使用することです。ご存知のとおり、AWS サービスを呼び出すには、同期または非同期の2つの方法があります。同期の場合、lambda は関数を実行し、呼び出し元の lambda は呼び出された lambda からレスポンスを受信するまで待機します。非同期の場合は待機しません。AWS は、レスポンスを非同期で受信するためのコールバック (callback) メソッドと future オブジェクトをサポートしています。ここで、パイプの役割は内部ネットワーク接続によって果たされます。

この直接の lambda から lambda への呼び出しでは、同期と非同期の両方で、スロットルが発生する可能性があります。リクエストの流入が関数のスケールアップキャパシティよりも速く、関数が最大同時実行レベル (デフォルトは1000) にある場合、または lambda インスタンス数が構成された予約済み同時実行制限に達した場合、追加のリクエストはスロットルエラー (429 ステータスコード) で失敗します。これを処理するには、すぐには処理できないリクエストを一時的に保存する2つの lambda 呼び出しの間の中間ストレージと、lambda インスタンスが使用可能になったときにメッセージを取得して処理を開始するスロットルメッセージの再試行メカニズムが必要です。

下の図に示したように、AWS Simple Queue Service (SQS) を使用してこれを実現できます。各 lambda フィルタはイベントを処理し、それをキューにプッシュします。この設計では、lambda は SQS から複数のイベントをポーリングし、それらをバッチとして処理することもできます。これにより、パフォーマンスが向上し、コストを削減できます。

このアプローチでは、スロットルのリスクを減らすこともできますが、完全に回避することはできません。スロットルのバランスをとることができる構成可能なパラメータはほとんどありません。さらに、lambda の Dead Letter Queue (DLQ) を実装して、抑制されたイベント/メッセージに対処し、それらのメッセージが失われるのを防ぐことができます。「データプロジェクトで SQS と Lambda を組み合わせることから学んだ教訓」は、この問題に対処するための主要なパラメータを理解するための良い読み物です。

次のセクションでは、サーバレスイベント処理用の別の有望な AWS コンポーネントである Amazon EventBridge を使用して、パイプとフィルタのデザインパターンを実装するための一般的な再利用可能なソリューションの構築を試みます。

サーバレスアーキテクチャによるパイプとフィルタパターンの実装

Amazon EventBridge はサーバレスイベントバスであり、AWS サービスと統合された Software-as-a-Service (SaaS) アプリケーションから生成されたイベントを使用する、イベント駆動アプリケーションの大規模な構築を容易にします。

 

これがどのように機能するかを確認する前に、AWS EventBridge に関連して使用されるいくつかの基本的な用語を理解する必要があります。

イベントバスは、EventBridge の重要なコンポーネントの1つです。イベントバスは、さまざまなソースからイベント/メッセージを受信し、それらを一連の定義済みルールと照合します。EventBridge にはデフォルトのイベントバスがありますが、ユーザは独自のイベントバスを作成することもできます。私は、この POC のために「パイプ」という名前のイベントバスを作成しました。

Rules (ルール) は特定のイベントバスに関連付ける必要があります。この POC では、次の図に示すように、3つの異なるフィルタに対して3つのルールを作成しました。

Event (イベント) パターンTarget (ターゲット) は、各ルールの2つの非常に基本的な構成です。

Event パターンは条件です。一致するイベントと同じ構造です。着信イベントに一致するパターンがある場合、ルールがアクティブになり、着信イベントを渡すことで Target (宛先) を呼び出します。Target は、EventBridge がイベントを送信するリソースまたはエンドポイントです。特定のパターンに複数の Target を設定できます。

この例では、パターン内で Lambda 名を detail.target として設定し、Lambda 名が一致すると、ターゲットの Lambda がトリガされます。

注意: detail.target は json フィールドです。Target は、構成可能なイベントのエンドポイント/宛先です。

イベントフロー中に実行できるさまざまな手順を以下に示します:

  1. ソースはイベントを生成します (これは、イベントソースジェネレーターとイベントブリッジルールクリエイターによって定義されたパターンに従う必要があります)

実装をテストするために、次のイベントを使用しました:

  1. テストイベントの特定の detail.target 値が、ルールが一致すると実行されます。この場合、これによりイベント/メッセージがルールに関連付けられたターゲット の lambda、filter1_lambda にルーティングされます。

  2. ターゲット lambda のタスクが完了し、イベントターゲット (detail.target) を detail.filterlist json リストの次の lambda (filter2_lambda) に置き換えます。

  3. 次にターゲット lambda は、lambda レイヤのユーティリティ関数 next_filter() を呼び出します。

  4. next_filter() 関数は、最後のイベントを作成し、それをイベントブリッジに配置する役割を果たします。

  5. 新しいターゲット値 (つまり、filter2_lambda) に基づいて、別のルールが一致し、別のフィルタ lambda が呼び出されます。

{
  “Detail”: {
    “target”: [“filter2_lambda”]
  }
}
  1. すべてのタスクを完了した後、最後のフィルタは単にメッセージを次のフィルタ以外の宛先に送信します。この POC では、終了フィルタは filter3_lambda です。next_filter 関数を呼び出す代わりに、この lambda は DynamoDb API を呼び出して、データを DynamoDb テーブルに保存できます。

これまでに示したように、EventBridge のパターンマッチングルーティング機能を利用すると、単一のイベントバスでパイプとフィルタのパターンを実装できます。これにより、一連の各ステージは、後続が前のものを処理中でも、次のイベントの処理を自由に開始できます。したがって全体的な効率を改善します。

上の図が示すように、クライアントイベントの detail.target は、ターゲットが filter1_lambdafilter-rule1 イベントパターンと一致するため、イベントは最初に filter1_lambda に移動します。完了後、filter1_lambda はイベントの detail.target を次の lambda (つまり、filter2_lambda) に設定し、変更されたイベントをイベントバス mypipe に送り返します。detail.target の値が filter2_lambda であるため、filter-rule2 がトリガされ、さらに続きます。この再帰的なプロセスを使用して、すべてのフィルタが実行されます。最後のものは、next_filter() ユーティリティレイヤの代わりに他のリソースを呼び出すことができます。

上記の実装では、すべての lambda に共通する重要なタスクの1つは、filterlist の次の lamdba でイベントターゲット ((detail.target)) を変更することです。これを実現するために、lambda レイヤを使用しました。

lambda レイヤは、開発者が lambda コードから一般的な機能またはライブラリを抽出してレイヤに配置するのに役立つ lambda の機能です。このレイヤはユーティリティブロックとして使用でき、実際の lambda コードはレイヤの上で実行できます。Lambda は、必要に応じて、レイヤの一般的な機能やライブラリを再利用できます。AWS のドキュメントによると

Lambda レイヤは、ライブラリ、依存関係、さらにはカスタムランタイムなどの追加コードを含むアーカイブです。

この POC のために、next_filter 関数をエクスポートするユーティリティレイヤを作成しました。Lambda フィルタはこの関数を使用して、フィルタリストから次のフィルタ名を推測します。関連するコードスニペットは、この記事の最後の付録に記載しています。

POC コード全体と AWS Cloud Development Kit (CDK) インフラストラクチャコードは、この github リポジトリにあります。

まとめ

パターンは、ソフトウェア設計の分野で最も有用で効果的なツールの1つです。一般的な設計の問題に標準的な方法で対処するために、適切なデザインパターンを適用できます。パターンはデザインプラグインのようなものです。サーバレスは技術のランドスケープで急速に成長している分野であり、すべてのクラウドベンダーが定期的に新しいマネージドサーバレスサービスをローンチしています。そのため、適切なサーバレスマネージドサービススタックを決定することは困難です。この記事では、さまざまな AWS マネージドサーバレスサービスを使用してサーバレス方式で1つのこうしたデザインパターンのさまざまな実装アプローチについて説明しました。

付録

next_filter コードスニペット:

module.exports.next_filter = (async function (event) {
   var i = event.detail.filterlist.indexOf(event.detail.target);
   if (event.detail.filterlist.length === i + 1) {
       return null;
   } else {
       event.detail.target = event.detail.filterlist[i + 1];
       var finalEvent = {
           "Source": event.source,
           "EventBusName": "mypipe",
           "DetailType": event["detail-type"],
           "Time": new Date(),
           "Detail": JSON.stringify(event.detail, null, 2)
       }

       var Entries = [];
       Entries.push(finalEvent);
       var entry = { "Entries": Entries };
       var result = await eventbridge.putEvents(entry).promise();
       return result;
   }
});

リファレンス

著者について

Tridib Bolar 氏はインドのコルカタに拠点を置き、IT 企業のクラウドソリューションアーキテクトとして働いています。彼は18年以上プログラミング業界で働いています。主に AWS プラットフォームに携わっており、サイドプロジェクトとして GCP も探索しています。クラウドサーバレスパラダイムのファンであることに加えて、彼は IoT テクノロジーの愛好家でもあります。


 

この記事に星をつける

おすすめ度
スタイル

BT