サービス間の通信

ヒント

このコンテンツは eBook の「Azure 向けクラウド ネイティブ .NET アプリケーションの設計」からの抜粋です。.NET Docs で閲覧できるほか、PDF として無料ダウンロードすると、オンラインで閲覧できます。

Cloud Native .NET apps for Azure eBook cover thumbnail.

フロントエンド クライアントから離れて、ここではバックエンド マイクロサービス同士の通信について説明します。

クラウドネイティブ アプリケーションを構築するときは、バックエンド サービスが相互に通信する方法について意識する必要があります。 理想的には、サービス間の通信が少ないほど好ましいということです。 ただし、バックエンド サービスは相互に依存して操作を完了することが多いため、常に回避できるとは限りません。

サービス間通信を実装するために広く受け入れられているアプローチがいくつかあります。 多くの場合、"通信の相互作用の種類" によって、最適なアプローチが決まります。

次の相互作用の種類について考えてみます。

  • クエリ – 呼び出し元のマイクロサービスが、呼び出し先のマイクロサービスからの応答を必要とする場合 (たとえば、"特定の顧客 ID の購入者情報をください")。

  • コマンド – 呼び出し元のマイクロサービスが、別のマイクロサービスによるアクションの実行を必要としているが、応答は必要ない場合 (たとえば、"この注文を発送してください")。

  • イベント – パブリッシャーと呼ばれるマイクロサービスが、イベントを発生させて、状態が変更したかアクションが発生した場合。 関心を持つ、サブスクライバーと呼ばれる他のマイクロサービスは、適切にイベントに反応できます。 パブリッシャーとサブスクライバーは互いに認識していません。

マイクロサービス システムは、通常、これらの相互作用の種類を組み合わせて、サービス間の相互作用を必要とする操作を実行します。 それぞれの方法と、それらを実装する方法について詳しく見ていきましょう。

クエリ

多くの場合、1 つのマイクロサービスが別のマイクロサービスに "クエリ" を実行するときは、操作を完了するために即時の応答が必要です。 買い物かごマイクロサービスは、かごに商品を追加するために、商品情報と料金が必要になります。 クエリ操作を実装するには、さまざまなアプローチがあります。

要求/応答メッセージング

このシナリオを実装するための 1 つのオプションは、図 4-8 に示すように、呼び出し元のバックエンド マイクロサービスが、クエリ先のマイクロサービスに HTTP 要求を直接行うことです。

Direct HTTP communication

図 4-8 直接的な HTTP 通信

マイクロサービス間の直接的な HTTP 呼び出しを実装するのは比較的簡単ですが、この方法は最小限に抑えるように注意してください。 まず、これらの呼び出しは常に "同期" され、結果が返されるか要求がタイムアウトになるまで、操作をブロックします。 かつて自己完結型で独立していたサービスが、個別に進化して、頻繁に配置できるようになり、現在は互いに結び付いています。 マイクロサービス間の結合が増えるにつれ、アーキテクチャ上のメリットは減少します。

別のマイクロサービスに対して 1 つの直接的な HTTP 呼び出しを行う要求をたまに実行するだけなら、一部のシステムでは許容されるかもしれません。 ただし、複数のマイクロサービスに対する直接的な HTTP 呼び出しを引き起こす大量の呼び出しは推奨されません。 待機時間が長くなり、システムのパフォーマンス、スケーラビリティ、可用性に悪影響を及ぼす可能性があります。 さらに悪いのは、図 4-9 に示すように、直接的な HTTP 通信が長く続くことで、同期マイクロサービス呼び出しのチェーンが深く複雑になる可能性があることです。

Chaining HTTP queries

図 4-9 HTTP クエリのチェーン

前の図に示されている設計のリスクが想像できるはずです。 ステップ #3 が失敗するとどうなるでしょうか。 または、ステップ #8 が失敗したら。 どのように回復しますか。 基になるサービスがビジー状態であるためにステップ #6 が遅れるとどうなるでしょうか。 続行するにはどうすればよいですか。 すべてが正しく処理したとしても、この呼び出しで発生する待機時間を考慮してください。これは各ステップの待機時間の合計です。

前の図に示した大規模な結合から、サービスが最適にモデル化されていないことがわかります。 チームは設計を見直すべきです。

具体化されたビュー パターン

マイクロサービスの結合を取り除くための一般的なオプションは、具体化されたビュー パターンです。 このパターンでは、マイクロサービスは、データ (他のサービスによって所有されている) の非正規化されたコピーを独自にローカルに格納します。 Shopping Basket マイクロサービスは Product Catalog マイクロサービスと Pricing マイクロサービスをクエリする代わりに、そのデータのローカル コピーを独自に保持します。 このパターンによって、不要な結合が排除され、信頼性と応答時間が向上します。 処理全体が 1 つのプロセス内で実行されます。 このパターンやその他のデータの問題については第 5 章で説明します。

サービス アグリゲーター パターン

マイクロサービス間の結合を排除するもう 1 つのオプションは、図 4-10 に紫色で示すアグリゲーター マイクロサービスです。

Aggregator service

図 4-10 アグリゲーター マイクロサービス

このパターンでは、複数のバックエンド マイクロサービスへの呼び出しを行う操作を分離して、そのロジックを専用のマイクロサービスに一元化します。 前の図で紫色のチェックアウト アグリゲーター マイクロサービスによって、チェックアウト操作のワークフローが調整されます。 これには、いくつかのバックエンド マイクロサービスへの順序付けられた呼び出しが含まれます。 ワークフローのデータが集計され、呼び出し元に返されます。 直接的な HTTP 呼び出しがまだ実装されていますが、アグリゲーター マイクロサービスによってバックエンド マイクロサービス間の直接的な依存関係が減少しています。

要求/応答パターン

同期 HTTP メッセージを分離するもう 1 つのアプローチは、キュー通信を使用する 要求/応答パターンです。 キューを使用した通信は、常に、プロデューサーがメッセージを送信してコンシューマーがメッセージを受信する一方向チャネルです。 このパターンでは、図 4-11 に示すように、要求キューと応答キューの両方が実装されます。

Request-reply pattern

図 4-11 要求/応答パターン

ここでは、メッセージ プロデューサーが、一意の関連付け ID を含むクエリベースのメッセージを作成し、それを要求キューに配置します。 コンシューマー サービスがメッセージをデキューし、それを処理して、同じ関連付け ID を使用して応答を応答キューに配置します。 プロデューサー サービスはメッセージをデキューし、関連付け ID と照合して、処理を続行します。 キューについては次のセクションで詳しく説明します。

コマンド

通信の相互作用のもう 1 つの種類は "コマンド" です。 マイクロサービスは、アクションを実行するために別のマイクロサービスが必要になる場合があります。 Ordering マイクロサービスは、承認された注文の発送を作成するために Shipping マイクロサービスが必要になる場合があります。 図 4-12 では、プロデューサーと呼ばれる 1 つのマイクロサービスが、別のマイクロサービス (コンシューマー) にメッセージを送信し、何かを行うように指示しています。

Command interaction with a queue

図 4-12. キューを使用したコマンドの相互作用

ほとんどの場合、プロデューサーは応答を必要としません。メッセージを "ファイアアンドフォーゲット" することができます。 応答が必要な場合、コンシューマーはプロデューサーにもう 1 つのチャネルで別のメッセージを送ります。 コマンド メッセージの送信は、 軽量のライトウェイト メッセージ ブローカーでサポートされるメッセージ キューを使用して非同期で行うことをお勧めします。 前の図で、キューによって両方のサービスが分けられて切り離されていることに注意してください。

メッセージ キューは、プロデューサーとコンシューマーがメッセージを渡すための中間構造です。 キューによって、非同期のポイントツーポイント メッセージング パターンが実装されます。 プロデューサーは、コマンドの送信先を認識し、適切にルーティングします。 キューによって、チャネルを読み取るコンシューマー インスタンスの 1 つだけがメッセージを処理することが保証されます。 このシナリオでは、プロデューサー サービスまたはコンシューマー サービスは、もう一方に影響を与えずにスケールアウトできます。 同様に、それぞれでテクノロジがまったく異なることも可能です。つまり、Java マイクロサービスから Golang マイクロサービスを呼び出すこともできます。

第 1 章では、"補助的サービス" について説明しました。 補助的サービスは、クラウドネイティブ システムが依存する補助的なリソースです。 メッセージ キューは補助的サービスです。 Azure クラウドでは、クラウドネイティブ システムがコマンド メッセージングを実装するために使用できる 2 種類のメッセージ キューをサポートしています。Azure Storage キューと Azure Service Bus キューです。

Azure Storage キュー

Azure Storage キューでは、高速かつ低コストであり、Azure ストレージ アカウントによってサポートされる単純なキュー インフラストラクチャが提供されます。

Azure Storage キューでは、信頼性が高く永続性があるメッセージングに対応する REST ベースのキュー メカニズムが提供されます。 提供されるのは最小限の機能セットですが、低コストであり、数百万のメッセージを格納できます。 容量は最大 500 TB に及びます。 1 メッセージの最大サイズは 64 KB です。

メッセージには、HTTP または HTTPS を使用し、認証された呼び出しを介して世界中のどこからでもアクセスできます。 ストレージ キューは、多数の同時実行クライアントにスケールアウトして、トラフィックの急増に対応できます。

ただし、サービスには制限があります。

  • メッセージの順序は保証されません。

  • メッセージが保持されるのは 7 日間のみであり、その後、自動的に削除されます。

  • 状態管理、重複検出、またはトランザクションのサポートは使用できません。

図 4-13 は、Azure Storage キューの階層を示しています。

Storage queue hierarchy

図 4-13. ストレージ キューの階層

前の図で、ストレージ キューによって、基になる Azure Storage アカウントにメッセージがどのように格納されているかを確認してください。

Microsoft では、開発者向けに、ストレージ キュー処理用のクライアント側とサーバー側のライブラリをいくつか提供しています。 .NET、Java、JavaScript、Ruby、Python、Go など、ほとんどの主要なプラットフォームがサポートされています。 開発者は、これらのライブラリと直接通信することはできません。 そうすると、マイクロサービス コードと Azure Storage Queue サービスが密接に結合されます。 API の実装の詳細を分離することをお勧めします。 汎用操作を公開して具象ライブラリをカプセル化する、中間層つまり中間 API を導入してください。 このような疎結合により、メインライン サービス コードを変更することなく、1 つのキュー サービスから別のキュー サービスに切り替えることができます。

Azure Storage キューは、クラウドネイティブ アプリケーションにコマンド メッセージングを実装するための低コストのオプションです。 特に、キューのサイズが 80 GB を超える場合や、単純な機能セットが許容される場合です。 メッセージの格納に対してのみ課金され、時間単位の固定料金はありません。

Azure Service Bus キュー

さらに複雑なメッセージング要件では、Azure Service Bus キューを検討してください。

堅牢なメッセージ インフラストラクチャ上の Azure Service Bus では、"ブローカー メッセージング モデル" がサポートされます。 メッセージは、コンシューマーによって受信されるまで、ブローカー (キュー) に確実に格納されます。 このキューでは、メッセージがキューに追加された順序を優先する先入れ/先出し (FIFO) のメッセージ配信が保証されます。

メッセージのサイズはかなり大きくなり、最大 256 KB が可能です。 メッセージは無期限にキューに保持されます。 Service Bus では、HTTP ベースの呼び出しがサポートされるだけではなく、AMQP プロトコルが全面的にサポートされます。 AMQP は、バイナリ プロトコルと高い信頼性をサポートする、ベンダーにまたがるオープン標準です。

Service Bus には、トランザクション サポート重複検出機能など、豊富な機能セットが用意されています。 このキューでは、メッセージごとに "最大 1 回の配信" が保証されます。 既に送信済みのメッセージは自動的に破棄されます。 プロデューサーに確信がない場合は同じメッセージを再送信できますが、Service Bus によって 1 コピーのみの処理が保証されます。 重複検出機能により、追加のインフラストラクチャ プラミングを構築する必要がなくなります。

さらに、パーティション分割とセッションという 2 つのエンタープライズ機能があります。 従来の Service Bus キューは、単一のメッセージ ブローカーで処理されて 1 つのメッセージ ストアに格納されます。 ただし、Service Bus パーティション分割では、複数のメッセージ ブローカーとメッセージ ストアにキューが分散されます。 全体のスループットが、単一のメッセージ ブローカーまたはメッセージング ストアのパフォーマンスによって制限されなくなります。 1 つのメッセージング ストアが一時的に停止しても、パーティション分割されたキューは使用することができます。

Service Bus セッションでは、グループに関連するメッセージのための方法が提供されます。 メッセージをまとめて処理する必要があり、操作が最後に完了するワークフロー シナリオを想像してみてください。 利用するには、セッションがキューに対して明示的に有効になっていることと、関連する各メッセージに同じセッション ID が含まれることが必要です。

ただし、いくつか重要な注意事項があります。Service Bus キューのサイズは 80 GB に制限され、これはストア キューで使用可能なものよりもかなり小さいということです。 さらに、Service Bus キューでは、基本コストと操作ごとの料金が発生します。

図 4-14 は、Service Bus キューのアーキテクチャの概要を示しています。

Service Bus queue

図 4-14. Service Bus キュー

前の図で、ポイントツーポイントの関係に注意してください。 同じプロバイダーの 2 つのインスタンスは、メッセージを 1 つの Service Bus キューにエンキューします。 各メッセージは、右側にある 3 つのコンシューマー インスタンスのうちの 1 つのみによって使用されます。 次に、さまざまなコンシューマーが同じメッセージに関心を持つ可能性がある場合のメッセージングの実装方法について説明します。

events

メッセージ キューは、プロデューサーがコンシューマーにメッセージを非同期に送信できる通信を実装するための効果的な方法です。 ただし、"多数の異なるコンシューマー" が同じメッセージに関心を持つ場合はどうなるでしょうか。 各コンシューマーとの専用メッセージ キューは適切にスケールせず、管理が困難になります。

このシナリオに対処するために、3 番目のメッセージ相互作用の種類である "イベント" に移ります。 1 つのマイクロサービスによってアクションが発生したことが通知されます。 他のマイクロサービス (関心がある場合) は、アクションすなわちイベントに反応します。 これは、イベント駆動型のアーキテクチャ スタイルとも呼ばれます。

イベントは 2 つのステップからなるプロセスです。 特定の状態の変化に対して、あるマイクロサービスがイベントをメッセージ ブローカーにパブリッシュし、関心がある他のマイクロサービスが使用できるようにします。 関心を持つマイクロサービスは、通知されるためにメッセージ ブローカーでイベントをサブスクライブします。 パブリッシュ/サブスクライブ パターンを使用して、イベントベースの通信を実装します。

図 4-15 は、イベントをパブリッシュする買い物かごマイクロサービスと、それをサブスクライブする他の 2 つのマイクロサービスを示しています。

Event-Driven messaging

図 4-15. イベント駆動型メッセージング

通信チャネルの中央にある "イベント バス" コンポーネントに注意してください。 これは、メッセージ ブローカーをカプセル化して、基になるアプリケーションから分離するカスタム クラスです。 注文と在庫のマイクロサービスは、個別にイベントを処理し、互いについて、または買い物かごマイクロサービスについて認識していません。 登録したイベントがイベント バスにパブリッシュされたときに、それらが反応します。

イベントでは、キュー テクノロジから "トピック" に移ります。 トピックはキューに似ていますが、1 対多のメッセージング パターンがサポートされます。 1 つのマイクロサービスがメッセージをパブリッシュします。 サブスクライブしている複数のマイクロサービスは、そのメッセージを受信して反応するかどうかを選択できます。 図4-16 に、トピックのアーキテクチャを示します。

Topic architecture

図 4-16. トピックのアーキテクチャ

前の図では、パブリッシャーがトピックにメッセージを送信しています。 最後に、サブスクライバーがサブスクリプションからメッセージを受信します。 間では、濃い青色の枠内のルールのセットに基づいて、トピックがメッセージをサブスクリプションに転送します。 ルールは、特定のメッセージをサブスクリプションに転送するフィルターとして機能します。 ここでは、"GetPrice" イベントが価格とロギングのサブスクリプションに送信されます。ロギング サブスクリプションはすべてのメッセージの受信を選択しているためです。 "GetInformation" イベントは、情報とロギングのサブスクリプションに送信されます。

Azure クラウドでは、Azure Service Bus トピックと Azure EventGrid という 2 つの異なるトピック サービスがサポートされています。

Azure Service Bus トピック

Azure Service Bus キューと同じ堅牢なブローカー メッセージ モデル上にあるのが、Azure Service Bus トピックです。 1 つのトピックは、複数の独立したパブリッシャーからメッセージを受信し、最大 2,000 のサブスクライバーにメッセージを送信できます。 サブスクリプションは、システムを停止したり、トピックを再作成したりせずに、実行時に動的に追加または削除できます。

重複検出トランザクション サポートなど、Azure Service Bus キューの高度な機能の多くはトピックでも使用できます。 既定では、Service Bus トピックは、単一のメッセージ ブローカーで処理されて 1 つのメッセージ ストアに格納されます。 ただし、Service Bus パーティション分割によって、多くのメッセージ ブローカーおよびメッセージ ストアに分散することでトピックがスケールされます。

スケジュール設定されたメッセージ配信では、処理のために特定の時刻がメッセージにタグ付けされます。 このメッセージは、その時刻まではトピックに現れません。 メッセージ遅延では、後でメッセージを取得するように遅らせることができます。 どちらも、特定の順序で操作が処理されるワークフロー処理シナリオでよく使用されます。 前の作業が完了するまで、受信したメッセージの処理を延期できます。

Service Bus トピックは、実証された堅牢なテクノロジであり、これによってクラウドネイティブ システムでのパブリッシュ/サブスクライブ通信が実現します。

Azure Event Grid

Azure Service Bus は豊富なエンタープライズ機能のセットを備えた、実績があるメッセージ ブローカーですが、Azure Event Grid は新しい製品です。

一見すると、Event Grid はもう 1 つのトピックベースのメッセージング システムのようです。 ただし、多くの点で異なります。 イベント駆動型ワークロードに重点を置くことで、リアルタイム イベント処理、Azure との深い統合、およびオープンプラットフォームが、すべてサーバーレス インフラストラクチャ上で実現します。 最新のクラウドネイティブおよびサーバーレス アプリケーション向けに設計されています。

一元管理された "イベント バックプレーン" すなわちパイプとして、Event Grid は、Azure リソース内のイベントや独自のサービスのイベントに反応します。

イベント通知が Event Grid トピックにパブリッシュされ、さらに、各イベントがサブスクリプションにルーティングされます。 サブスクライバーはサブスクリプションにマップして、イベントを使用します。 Service Bus と同様に、Event Grid では "フィルター処理されたサブスクライバー モデル" がサポートされます (サブスクリプションによってイベントの受信先に関するルールが設定されます)。 Event Grid では、1 秒あたり 1000 万件のイベントを保証する高速スループットが提供され、ほぼリアルタイムでの配信が実現します。これは、Azure Service Bus で生成されるものを大きく上回ります。

Event Grid の優れた点は、Azure インフラストラクチャのファブリックに深く統合されていることです。 Cosmos DB などの Azure リソースは、組み込みイベントを他の関心がある Azure リソースに直接パブリッシュでき、カスタム コードは必要ありません。 Event Grid は、Azure サブスクリプション、リソース グループ、またはサービスからイベントをパブリッシュでき、開発者がクラウド リソースのライフサイクルをきめ細かく制御できるようになります。 ただし、Event Grid は Azure に限定されません。 これは、アプリケーションまたはサードパーティ サービスからパブリッシュされたカスタム HTTP イベントを使用して、外部サブスクライバーにイベントをルーティングできるオープン プラットフォームです。

Azure リソースに対してネイティブ イベントをパブリッシュおよびサブスクライブするとき、コーディングは必要ありません。 単純な構成で、トピックとサブスクリプションの組み込みプラミングを活用して、Azure リソースのイベントを別のリソースに統合できます。 図 4-17 は、Event Grid の構造を示しています。

Event Grid anatomy

図 4-17. Event Grid の構造

EventGrid と Service Bus の主な違いは、基になる "メッセージ交換パターン" です。

Service Bus で実装される古いスタイルの "プル モデル" では、ダウンストリーム サブスクライバーが新しいメッセージについてトピック サブスクリプションをアクティブにポーリングします。 よい点としては、このアプローチではサブスクライバーがメッセージを処理するペースを完全に制御できます。 どの時点でも、メッセージをいつ処理するか、いくつ処理するかを制御できます。 未読のメッセージは、処理されるまでサブスクリプションに残ります。 重大な欠点は、イベントが生成されてから、そのメッセージが処理のためにサブスクライバーにプルされポーリング操作までの待機時間です。 また、次のイベントに関して常にポーリングするオーバーヘッドのために、リソースとコストが消費されます。

ただし、EventGrid は異なります。 これによって実装される "プッシュ モデル" では、イベントが受信時に EventHandler に送信され、ほぼリアルタイムのイベント配信が実現します。 また、サービスがトリガーされるのはイベントを使用する必要がある場合のみであり、ポーリングのように継続的ではないため、コストが削減されます。 ただし、イベント ハンドラーは受信負荷を処理し、自らが過負荷にならないように調整メカニズムを用意する必要があります。 これらのイベントを使用する多くの Azure サービス (Azure Functions や Logic Apps など) では、負荷の増加に対処する自動オートスケール機能が提供されます。

Event Grid は、完全に管理されたサーバーレス クラウド サービスです。 これは、トラフィックに基づいて動的にスケールし、実際の使用量のみをユーザーに課金します。容量の事前購入ではありません。 毎月 10 万回の操作まで無料です (操作としては、イベント イングレス (受信イベント通知)、サブスクリプション配信試行、管理呼び出し、サブジェクト別のフィルター処理が定義されています)。 EventGrid の可用性は 99.99% であり、24 時間以内のイベントの配信が保証されます。また、配信に失敗した場合に備える組み込み再試行機能があります。 未配信メッセージは、解決のために "配信不能" キューに移動されることがあります。 Azure Service Bus とは異なり、Event Grid は高速パフォーマンスを実現するように調整されており、順序付けられたメッセージング、トランザクション、セッションといった機能はサポートされません。

Azure クラウドでのメッセージのストリーミング

Azure Service Bus と Event Grid では、新しいドキュメントの Cosmos DB への挿入のような 1 つの独立したイベントを公開するアプリケーションのために優れたサポートが提供されます。 しかし、クラウドネイティブ システムで "関連イベントのストリーム" を処理する必要がある場合はどうでしょうか。 イベント ストリームはさらに複雑です。 通常は、時間順で相互に関係があり、グループとして処理する必要があります。

Azure イベント ハブは、イベントの収集、変換、格納を行う、データ ストリーミング プラットフォームおよびイベント インジェスト サービスです。 ストリーミング データ (テレメトリ コンテキストから生成される継続的なイベント通知など) をキャプチャするように微調整されています。 このサービスはきわめてスケーラブルであり、1 秒あたり数百万のイベントを処理して格納できます。 図 4-18 に示すように、これは多くの場合、イベント パイプラインの入口として、インジェスト ストリームとイベントの使用を切り離します。

Azure Event Hub

図 4-18. Azure Event Hub

イベント ハブでは、短い待機時間と構成可能な保持期間がサポートされます。 キューやトピックとは異なり、Event Hubs ではコンシューマーが読んだ後もイベント データが保持されます。 この機能によって、内部および外部の他のデータ分析サービスで、分析のためにデータを再生できるようになります。 イベント ハブに格納されているイベントは、保持期間の期限が切れたときにのみ削除されます。既定では 1 日ですが、構成することができます。

イベント ハブでは、HTTPS や AMQP などの一般的なイベント発行プロトコルがサポートされています。 Kafka 1.0 もサポートされています。 既存の Kafka アプリケーションはイベント ハブと通信できますが、Kafka プロトコルを使用して、大規模な Kafka クラスターの管理に対する代替方法を提供します。 オープンソースのクラウドネイティブ システムの多くでは Kafka が採用されています。

Event Hubs は、パーティション化されたコンシューマー モデルを使用してメッセージ ストリーミングを実装します。このモデルでは、各コンシューマーはメッセージ ストリームの特定のサブセット (またはパーティション) のみを読み取ります。 このパターンでは、イベント処理の横の倍率を大きくすることができ、キューおよびトピックで使用できないその他のストリームに重点を置いた機能を提供します。 パーティションは、イベント ハブで保持される順序付けされた一連のイベントです。 新しいイベントが到着すると、このシーケンスの末尾に追加されます。 図 4-19 は、イベント ハブのパーティション分割を示しています。

Event Hub partitioning

図 4-19 イベント ハブのパーティション分割

各コンシューマー グループは、同じリソースから読み取るのではなく、メッセージ ストリームのサブセットまたはパーティションに対して読み取ります。

大量のイベントをストリーミングする必要があるクラウドネイティブ アプリケーションにとって、Azure イベント ハブは堅牢で低コストのソリューションです。