SOAの基本的原則についてまとめてみた
はじめに
RESTful APIと同時に、SOAという概念に出会ったので、この概念の原則についてまとめていきます。
SOAとは、サービス指向アーキテクチャのことです。モノリシック時代では、すべてのサービスが1つのシステムに入っていました。しかし、SOAを追従することで、
柔軟性: サービス単位での修正・更新が可能
拡張性: 必要なサービスだけスケールアップ
再利用性: 同じサービスを複数システムで活用
技術の多様性: 各サービスで最適な技術選択
組織的な独立: チームごとにサービスを管理
というような恩恵を得られることができるようになりました。
現在のAWSはこの概念を基盤になっています。また、Netflixも、500以上のマイクロサービスのアーキテクチャにすることで、1日4000回のデプロイができ、99.99%の可用性を可能にしています。
SOAの10原則について、以下でそれぞれまとめていきます。
明示的な境界
サービスが機能を提供するのに必要なすべてのものは、そのサービスが呼び出されたときに受け渡しされる必要があります。
サービスへのアクセスは、必ずパブリックに公開されたインターフェースを経由します。
そのサービスを呼び出すために暗黙の想定の存在が必要であってはなりません。
これは、サービスの呼び出しが共通のコンテキストに依存するべきではないという意味で、ステートレスとしてモデル化する必要があります、
また、サービスの利用および提供は、できるだけ簡単にする必要があり、サービスと対話が行われているという事実を隠しすぎることは好まれないです。
サービスをやり取りする
- メッセージ
- サービスコントラクト
- サービスそのもの
これらは
例えば、使用されるプログラミングモデルおよびツールは、少なくともそのAPIをサービスプログラマに提供する必要があります。
つまり、サービスは、内部をカプセル化した明示的なインターフェースを介して機能を公開します。
サービスとの対話は、提供者と利用者間のメッセージの受け渡しに依存する明示的な行為です。
クラスではなく共通のコントラクトとスキーマ
サービスの記述(コントラクト)を始めとして、サービスの利用者も提供者も、サービスの提供または利用に必要なものはすべて備えている必要があります。
疎結合という原則に従えば、サービス提供者は、サービス利用者が自身の環境にコードを備え、それを使用するということを前提にはできません。
結果としては、他のプログラムやランタイム環境を使用するとしても、それに依存することはできません。
この原則のために、SOAにて交換できるデータの種類には厳しい制約が課せられています。データの交換は、複数のスキーマで検証可能なXMLドキュメントとして行うのが理想です。
このドキュメントであれば、現在考えられるすべてのプログラミング環境でサポートされるためです。
ポリシードリブン
サービスと対話するには、以下の要件の両方を満たす必要があります。
- 提供者の機能性、構文、および動作が利用者の要件に適合する必要がある
- 技術的能力および要求が一致する必要がある
例えばサービス提供者は、利用者が必要とする適切なサービスを提供できるとします。しかし、利用者がHTTPしか使用できないのに、提供者はJMSで提供している場合があります。
また、提供者がXMLの暗号化標準によるメッセージレベルの暗号化を要求していても、使用者はSSLを使用したトランスポートレベルでのセキュリティしかサポートできない場合などもあります。
両者が必要な能力を備えている場合でも、それらを"有効化"する必要があります。例えば、提供者は、利用者それぞれの要求に合わせたアルゴリズムを使用して応答メッセージを暗号化します。
様々な装備および能力を持つ、できるだけ多くの利用者がサービスにアクセスできるようにするために、SOAツールセットの一部として、ポリシーメカニズムが取り入れられています。
機能的な面はサービスインターフェースに記述する一方で、非機能的な能力および要求はポリシーを使用して指定します。
自律性
サービスと外の世界との関係は、SOAの観点からすれば、インターフェースを経由した関係のみであるため、サービスは自律的であると言えます。
特に、サービスの実行環境を変更することが可能である必要があります。
例えば、軽量なプロトタイプ実装から、複数のコンポーネントが連携する本格的なアプリケーションサーバーベースの集合体に変更しても、利用者に影響を与えることがあっては行けません。
サービスは、それぞれ個別に、変更、配置、バージョンアップ、および管理できる必要があります。サービス提供者は、利用者がサービスの新バージョンにすぐに適応すると想定することもできません。
利用者によっては、サービスインターフェースの新バージョンに適応できない、または適応を望まない場合があります。
プログラミング言語のAPIではなく通信形式
サービスは、サポートされる必要のある特定の通信形式を使用して公開されます。
この原則は、最初の2つの原則(明示的な境界,クラスではなく共通のコントラクトとスキーマ)と深く関係しますが、新たな視点から考えてみると
「最大のアクセス可能性、および長期的な使用可能性を確保するために、サービスインターフェースに準拠したメッセージ交換をサポートするすべてのプラットフォームは、その対話が、サービスに定義されたポリシーに従っている限り、サービスにアクセスできなければならない」
ということで、次のような基準が満たされる必要があります。
- すべてのメッセージ形式が、**オープンスタンダードを使用(XMLやJSON等)**して、または人が読める形で記述されている
- このようなスキーマに従ったメッセージを、特定のプログラマのライブラリを必要とすることなく、合理的な労力で作成できる
- 正常な通信に必要な追加情報(セキュリティや信頼性といった目的のためのヘッダー等)の意味および構文が、パブリックな仕様または標準に従っている。
- サービスとの対話に使用されるトランスポートプロトコル(TCP/ UDP等)のうち、少なくとも1つは標準ネットワークプロトコルであり、標準ネットワークプロトコルを介してアクセス可能である
例えば、プログラミング言語APIである場合、
# Pythonライブラリ
inventory.check_stock(product_id=123) # Python専用
# Javaライブラリ
inventoryService.checkStock(123); // Java専用
# C#ライブラリ
InventoryService.CheckStock(123); // C#専用
と、各言語ごとに別々のライブラリが必要となります。
通信形式では、 同じHTTP/JSONにすることによって、全言語から利用可能にすることができます。
ドキュメント指向
サービスと対話するには、データをドキュメントとして渡す必要があります。
ドキュメントとは、データのコンテナであり、明示的にモデル化され、階層構造を持ちます。
**自己記述性(データが自分自身を説明すること)**は、ドキュメント指向の重要な要素の1つです。
ドキュメントのモデル化は、購入注文書、請求書、または取引明細書など、実世界のドキュメントに従って行われるのが理想的です。
ドキュメントは、対象の分野のコンテキストにおいて有用であるように設計される必要があります。
これは、ドキュメントが1つまたは複数のサービスの提供に使用される可能性を示唆しています。
実世界の書類と同様に、サービスで交換されるドキュメントには冗長な情報も含まれます。
例えば、顧客IDと一緒に、顧客の住所情報が含まれることもあります。
このような冗長性は、積極的に受け入れられています。これは、サービスの利用者および提供者のどちらの基盤となっているデータモデルからも、サービスインターフェースを分離するのに役立つためです。
ドキュメント指向パターンを適用することにより、サービス呼び出しは、コンテキストに関係しないRPC呼び出しではなく、意味のある**ビジネスメッセージの交換(受信側は文書だけで内容を理解可能)**となります。ドキュメントの形式および構文としては、絶対ではありませんが、通常、XMLの使用が想定されます。
また、ドキュメント指向では、完結した文書を送るだけで、**1回の送信で完結(自己完結性)**することができる可能性があり、疎結合、自律性としてのメリットもあります。
SOAの参加者間で受け渡しされるメッセージは、それぞれ別個に改訂されるさまざまなシステムを接続しています。
疎結合の原則を尊重するには、共通の認識への依存を可能な限り少なくすることが必要です。メッセージが分散オブジェクト、またはRPCインフラストラクチャに送信される場合、クライアントおよびサーバーは、プロキシクラスの集合(スタブとスケルトン)が同じインターフェース記述ドキュメントから生成されることを前提になります。
この前提が成立しない場合は、コンストラクトが両者の対話をサポートしていないということになり、通信が停止します。
このため、RPCスタイルのインフラストラクチャでは、クライアントおよびサーバーのプログラムコードが同時に改定されることが必要となります。
例として、
2006-03-1347113
2006-03-13
4711
3
2つめのメッセージは人が読める形式ですが、最初のメッセージはそうではないです。また、2つ目のメッセージを使用する場合、構文が固定されることを前提とする参加者に比べて、互換性のある小さな改訂であれば影響を受けないで済む可能性が高くなることもわかります。
逆に言えば、スタブとスケルトンの生成などのRPCパターンを使用できるにも関わらず、XMLなどの自己記述型のメッセージ形式を使用することは、帯域幅の無駄遣いであるというXMLへの批判を助長するだけです。
XMLを使用する場合は、その利点が活かされている必要があるます。
(補足)RPCスタイル(関数呼び出し型)
// 細かい操作を何度も呼び出す
customerService.getName(customerId);
customerService.getAddress(customerId);
orderService.createOrder(customerId, productId);
これは、多数の細かい呼び出し・クライアント・サーバー間の強い結合という問題がある。
疎結合
SOAの提供者は、多くの場合、疎結合が重要な概念であることに賛成しています。しかし、どのような特性があればそのシステムが疎結合であると言えるのでしょうか。
システムの疎結合、密結合には、要件およびコンテキストによって複数の次元があります。
疎結合であるというシステムが、別の次元では密結合であるとみなされることもあります。次のような次元があります。
- 時間
時間的に疎結合である場合、1人の参加者がメッセージを他の参加者に送る場合、送信者は、メッセージをすぐに戻ってくることを前提とせずに処理を続行します。
これは、同期通信における時間的結合(Temporal Coupling)の問題と、その解決策としての非同期メッセージングの重要性を説明しています。
このメリットとしては、
耐障害性: 受信側が一時的にダウンしてもメッセージは失われない
スケーラビリティ: 処理能力に応じて受信側を増減可能
柔軟性: 送信側と受信側が独立して動作
負荷分散: メッセージをバッファリングして処理を平準化
というメリットがあります。
- 場所
通信先のアドレスが変更しても、通信パターンを変更したり再始動したりする必要がないようにします。
これは、サービスエンドポイントのアドレスを保管するディレクトリやアドレスを使用する検索プロセスのようなものを意味します。DNS(ドメインネームシステム)がこれを実現する類似サービスに当たります。
- 型
プログラミングにおいて静的と動的、あるいは厳密な型指定とそうでない型指定という対立する概念が存在するのと同じように、SOA参加者は、処理を実行するためにドキュメント構造の全体に依存することも、一部のみに依存することもできます。
-
バージョン
参加者は、サービスインターフェースの特定のバージョンに依存する場合と、ある程度の変更に耐えられる場合とがあります。バージョンの一致が厳密に求められるほど、この次元においては疎結合ではないと言えます。ポステルの法則に従うことをお勧めします。
つまり、サービス提供者は、可能な限り多数のバージョンに対応できるように実装し、対応の可、不可に不公平がないようにします。一方、サービスの利用者は可能な限り正確な文法およびドキュメント型に従うようにします。これにより、システム全体の安定性および柔軟性が向上します。
-
濃度
サービスの提供者と利用者の間には、1対1の関係が成立する場合があります。特に、要求と応答の対話が行われる場合、または明示的なメッセージキューが使用される場合はこのような関係が成立します。しかし、サービス利用者(イベントソース)が、メッセージ受信者の数を認識、考慮しない場合もあります。1対1の要求-応答型としては、REST API、RPCなどがあり、送信者は特定の受信者を認識し、同期的な通信する特徴があります。
また、1対1のメッセージキュー型では、非同期だが、1つのメッセージは1つの受信者が処理します。この場合、送信者はキューを介することにより具体的な受信者を知らないことになります。
最後に、 1対多のパブリッシュ・サブスクライブ型は、送信者は受信者の数を意識せず、0人でも100人でも動作するという特徴があります。これは、イベント駆動アーキテクチャの基盤となっています。
これを意識する理由としては、設計上の利点があるます。
疎結合: 送信者が受信者を知らない → 依存関係が少ない
拡張性: 新しい受信者を追加しても送信側の変更不要
柔軟性: 受信者数が動的に変化しても問題ないこの概念を理解することで、スケーラブルで保守しやすいシステム設計が可能になります。
-
検索
サービスを呼び出す参加者は、通信するサービス提供者の名前に依存することもできますが、一連の能力の記述を使用して最初に検索操作を実行することもできます。
これは、利用者の要求と提供者の能力とをマッチングさせることができる、登録およびリポジトリを意味します。例)Uberのような配車サービス:
利用者は「特定のドライバー」を指名するのではない。「現在地から5分以内に来れるドライバー」という能力で検索し、システムが条件に合うドライバーを動的にマッチングこのアプローチのメリットとして、
柔軟性: サービス提供者が変わっても利用者側の変更不要
最適化: その時点で最も条件に合うサービスを選択
疎結合: 利用者と提供者が直接的に依存しない
スケーラビリティ: 新しいサービス提供者を簡単に追加可能
できるというメリットがあります。
-
インターフェース
参加者は、サービス固有のインターフェースに従う必要がある場合と、汎用インターフェースをサポートする場合とがあります。汎用インターフェースが使用される場合は、その汎用インターフェースを利用するすべての参加者が、それを提供するすべての参加者と対話できます。
サービス固有のインターフェースの例としては、在庫サービス: checkInventory(productId, quantity) 注文サービス: createOrder(customerId, items[]) 配送サービス: scheduleDelivery(orderId, address)のようなものがありますが、これは
- 各サービスごとに異なるAPI仕様を学習する必要
- クライアントは各サービスの詳細を知る必要がある
という意味で問題があります。これを# 全サービス共通: GET /resources/{id} - リソース取得 POST /resources - リソース作成 PUT /resources/{id} - リソース更新 DELETE /resources/{id} - リソース削除と統一インターフェース(RESTful)とすることで、
スケーラビリティ: 新サービス追加が容易
相互運用性: どのクライアントも同じ方法でアクセス
保守性: インターフェースの一貫性
エコシステム: 共通ツール・ライブラリの発展といった恩恵が受けられます。
ここに挙げたすべての次元において疎結合であるシステムを作成することは、必ずしも現実的ではありません。また、そのようなシステムが望ましいというわけでもありません。サービスの種類に応じて、トレードオフが必要になります。
標準への準拠
SOAアプローチを取るときに中心的概念となるのは、独自のAPIや形式への信頼ではなく、標準への信頼です。標準は、データ形式、メタデータ、トランスポートおよびトランスファーのプロトコルなどの技術的部分にも、ドキュメントの型などのビジネスレベルの生成物にも存在します。
ベンダへの非依存性
アーキテクチャの原則は、特定のベンダ製品に依存しないものとする必要があります。抽象概念から具体的な稼動システムを検討する際、商用ソフトウェアにせよ、無償のオープンソースソフトウェアにせよ、特定の製品を決定することは避けられません。どのような決定をしても、アーキテクチャレベルでは特に違いはありません。しかしこの決定では、合理的に可能であるかということ以外に、相互運用性および移植性の両方の標準への依存度が高いかという点を重視します。
このような判断を行うことにより、サービスの提供者や利用者を、ベンダのロードマップに制限されることなく、適切な標準をサポートする何らかのテクノロジを使用して構築することが可能になります。
メタデータドリブン
メタデータは、他のデータを説明・記述する情報のことです。データの「取扱説明書」のようなものと考えると分かりやすいでしょう。
例えば、写真のメタデータは
- 撮影日時: 2024-01-15 14:30
- 撮影場所: 東京タワー(GPS座標)
- カメラ機種: iPhone 15 Pro
- 解像度: 4032×3024
- ファイルサイズ: 3.5MB
あたりです。
Webサービスでのメタデータを使うことによって、可能な限りコードの生成または解釈が自動化される必要があります。また、サービスおよび参加者のライフサイクルの一部となる必要があります。
メタデータは単なる「説明情報」ではなく、システムが自動的に処理し、サービスの誕生から廃止まで一貫して管理される「生きた情報」として機能する必要があります。
例えばECサイトの在庫確認サービスで考えると、
従来(単なる説明情報):
# ドキュメントに書いてあるだけ
service_name: 在庫確認API
url: https://api.shop.com/inventory
version: 2.0
これを 開発者が読んで、手動でコードを書くことが必要でした。
一方でSOA参加者は、サービス誕生時、運用中(自動処理)、自動スケーリング、障害時(自動フェイルオーバー)、バージョンアップ時、廃止時にメタデータを活用します。これによって、
動的適応性: サービスの変更に自動対応、クライアント修正不要
自動発見: 「何ができるか」でサービスを検索・選択
障害回復: 異常検知で自動フェイルオーバー、サーキットブレーカー
自動スケーリング: 負荷に応じてインスタンス数を自動調整
ガバナンス: セキュリティ・コンプライアンス要件を自動適用
開発効率: メタデータからSDK・ドキュメント自動生成
バージョン管理: 新旧バージョンの共存、段階的移行
コスト最適化: 使用率に基づくリソースの自動調整
という恩恵を受けることができます。
まとめ
SAOの10原則についてまとめましたが、一方でデメリットもあります。例えば、
複雑性の増加: サービス間通信の管理が必要
パフォーマンス: ネットワーク遅延の考慮
データ整合性: 分散トランザクションの難しさ
運用負荷: 監視・ログ収集の複雑化
というような部分があり、これらの原則に完全には賛成できないという方も事実います。ただ、少なくとも、すべてについて反対というわけではないだろうと思っており、設計思想の1つの概念として頭に入れておいて損はないと思います。
Discussion