初期デプロイ後、および有効期間中に数回発生する可能性があるサービス (および公開するエンドポイント) は、ビジネス ニーズの変化、情報技術の要件、その他の問題への対処など、さまざまな理由で変更する必要がある場合があります。 各変更では、新しいバージョンのサービスが導入されます。 このトピックでは、Windows Communication Foundation (WCF) でのバージョン管理を検討する方法について説明します。
サービス変更の 4 つのカテゴリ
必要になる可能性があるサービスの変更は、次の 4 つのカテゴリに分類できます。
コントラクトの変更: たとえば、操作が追加されたり、メッセージ内のデータ要素が追加または変更されたりすることがあります。
アドレスの変更: たとえば、サービスは、エンドポイントに新しいアドレスがある別の場所に移動します。
バインドの変更: たとえば、セキュリティ メカニズムの変更や設定の変更などです。
実装の変更: たとえば、内部メソッドの実装が変更されたとき。
これらの変更の一部は "破壊的" と呼ばれ、他の変更は "非破壊的" と呼ばれます。以前のバージョンで正常に処理されたすべてのメッセージが新しいバージョンで正常に処理された場合、変更は 中断されません 。 その条件を満たしていない変更 は重大な 変更です。
サービスの向きとバージョン管理
サービスのオリエンテーションの教義の 1 つは、サービスとクライアントが自律的 (または独立) であるということです。 特に、これは、サービス開発者が、すべてのサービス クライアントを制御したり、知ったりすることを想定できないことを意味します。 これにより、サービスがバージョンを変更したときに、すべてのクライアントを再構築して再デプロイするオプションがなくなります。 このトピックでは、サービスがこのテネットに準拠していることを前提としているため、クライアントとは無関係に変更または "バージョン管理" する必要があります。
破壊的変更が予期せず、回避できない場合、アプリケーションはこのテネットを無視することを選択し、新しいバージョンのサービスを使用してクライアントを再構築して再デプロイする必要があります。
コントラクトのバージョン管理
クライアントによって使用されるコントラクトは、サービスで使用されるコントラクトと同じである必要はありません。互換性を持つ必要があるだけです。
サービス コントラクトの場合、互換性とは、サービスによって公開される新しい操作を追加できますが、既存の操作を削除したり、セマンティックに変更したりできないことを意味します。
データ コントラクトの場合、互換性とは、新しいスキーマ型定義を追加できることを意味しますが、既存のスキーマ型定義を破壊的な方法で変更することはできません。 重大な変更には、データ メンバーの削除やデータ型の変更が非互換性に含まれる場合があります。 この機能を使用すると、クライアントを中断することなくコントラクトのバージョンを変更する場合に、サービスの緯度をある程度変更できます。 次の 2 つのセクションでは、WCF データとサービス コントラクトに対して行うことができる、非破壊的変更と破壊的変更について説明します。
データ コントラクトのバージョン管理
このセクションでは、 DataContractSerializer クラスと DataContractAttribute クラスを使用する場合のデータのバージョン管理について説明します。
厳密なバージョン管理
バージョンの変更が問題になる多くのシナリオでは、サービス開発者はクライアントを制御できないため、メッセージ XML またはスキーマの変更にどのように対応するかを想定できません。 このような場合、次の 2 つの理由から、新しいメッセージが古いスキーマに対して検証されることを保証する必要があります。
古いクライアントは、スキーマが変更されないことを前提として開発されました。 設計されていないメッセージの処理に失敗する場合があります。
古いクライアントは、メッセージの処理を試みる前に、古いスキーマに対して実際のスキーマ検証を実行できます。
このようなシナリオで推奨される方法は、既存のデータ コントラクトを不変として扱い、一意の XML 修飾名を持つ新しいデータ コントラクトを作成することです。 サービス開発者は、既存のサービス コントラクトに新しいメソッドを追加するか、新しいデータ コントラクトを使用するメソッドを使用して新しいサービス コントラクトを作成します。
多くの場合、サービス開発者は、データ コントラクトのすべてのバージョンと、データ コントラクトの各バージョンのバージョン固有のビジネス コード内で実行する必要があるビジネス ロジックを記述する必要があります。 このトピックの最後の付録では、このニーズを満たすためにインターフェイスを使用する方法について説明します。
Lax のバージョン管理
他の多くのシナリオでは、サービス開発者は、データ コントラクトに新しい省略可能なメンバーを追加すると、既存のクライアントが中断されないことを前提にすることができます。 そのため、サービス開発者は、既存のクライアントがスキーマ検証を実行していないかどうか、および不明なデータ メンバーを無視するかどうかを調査する必要があります。 これらのシナリオでは、データ コントラクト機能を利用して、新しいメンバーを壊れない方法で追加できます。 サービス開発者は、バージョン管理のデータ コントラクト機能がサービスの最初のバージョンで既に使用されている場合、この想定を確実に行うことができます。
WCF、ASP.NET Web サービス、およびその他の多くの Web サービス スタックでは、 緩やかなバージョン管理がサポートされています。つまり、受信したデータの新しい不明なデータ メンバーに対する例外はスローされません。
新しいメンバーを追加しても既存のクライアントが壊れるわけではないと誤って考えることは簡単です。 すべてのクライアントが緩やかなバージョン管理を処理できるかわからない場合は、厳密なバージョン管理ガイドラインを使用し、データ コントラクトを不変として扱うことをお勧めします。
データ コントラクトの厳密なバージョン管理と緩やかなバージョン管理の両方の詳細なガイドラインについては、「 ベスト プラクティス: データ コントラクトのバージョン管理」を参照してください。
データ コントラクトと .NET 型の区別
.NET クラスまたは構造体は、 DataContractAttribute 属性をクラスに適用することで、データ コントラクトとして投影できます。 .NET 型とそのデータ コントラクトプロジェクションは、2 つの異なる問題です。 同じデータ コントラクト プロジェクションを持つ複数の .NET 型を持つことができます。 この区別は、予想されるデータ コントラクトを維持しながら .NET 型を変更できるため、厳密な意味でも既存のクライアントとの互換性を維持するのに特に役立ちます。 .NET 型とデータ コントラクトの違いを維持するには、常に次の 2 つのことを行う必要があります。
NameとNamespaceを指定します。 .NET 型の名前と名前空間がコントラクトで公開されないようにするには、データ コントラクトの名前と名前空間を常に指定する必要があります。 これにより、後で .NET 名前空間または型名を変更する場合、データ コントラクトは変わりません。
Name を指定します。 .NET メンバー名がコントラクトで公開されないようにするには、常にデータ メンバーの名前を指定する必要があります。 これにより、後でメンバーの .NET 名を変更する場合、データ コントラクトは変わりません。
メンバーの変更または削除
メンバーの名前またはデータ型の変更、またはデータ メンバーの削除は、緩やかなバージョン管理が許可されている場合でも重大な変更です。 これが必要な場合は、新しいデータ コントラクトを作成します。
サービスの互換性が非常に重要な場合は、コード内の未使用のデータ メンバーを無視し、そのままにしておくことを検討してください。 データ メンバーを複数のメンバーに分割する場合は、下位レベルのクライアント (最新バージョンにアップグレードされていないクライアント) に必要な分割と再集計を実行できるプロパティとして、既存のメンバーをそのまま使用することを検討してください。
同様に、データ コントラクトの名前または名前空間に対する変更も重大な変更です。
不明なデータの Round-Trips
一部のシナリオでは、新しいバージョンで追加されたメンバーからの不明なデータを "ラウンドトリップ" する必要があります。 たとえば、"versionNew" サービスは、新しく追加されたメンバーを含むデータを "versionOld" クライアントに送信します。 クライアントは、メッセージの処理中に新しく追加されたメンバーを無視しますが、新しく追加されたメンバーを含む同じデータを versionNew サービスに再送信します。 この一般的なシナリオは、サービスからデータが取得され、変更され、返されるデータ更新です。
特定の型に対してラウンドトリップを有効にするには、型で IExtensibleDataObject インターフェイスを実装する必要があります。 インターフェイスには、ExtensionDataObject型を返す 1 つのプロパティExtensionDataが含まれています。 このプロパティは、現在のバージョンに不明なデータ コントラクトの将来のバージョンのデータを格納するために使用されます。 このデータはクライアントに対して不透明ですが、インスタンスをシリアル化すると、 ExtensionData プロパティの内容は、残りのデータ コントラクト メンバーのデータと共に書き込まれます。
新しいメンバーと不明な将来のメンバーに対応するために、すべての型でこのインターフェイスを実装することをお勧めします。
データ コントラクト ライブラリ
コントラクトが中央リポジトリに発行されるデータ コントラクトのライブラリがあり、サービスと型の実装者がそのリポジトリからデータ コントラクトを実装して公開する場合があります。 その場合、データ コントラクトをリポジトリに発行する場合、データ コントラクトを実装する型を作成するユーザーを制御できなくなります。 したがって、発行されたコントラクトを変更して、実質的に不変にレンダリングすることはできません。
XmlSerializer を使用する場合
XmlSerializer クラスを使用する場合も、同じバージョン管理原則が適用されます。 厳密なバージョン管理が必要な場合は、データ コントラクトを不変として扱い、新しいバージョンの一意の修飾名を持つ新しいデータ コントラクトを作成します。 緩やかなバージョン管理を確実に使用できる場合は、新しいシリアル化可能なメンバーを新しいバージョンに追加できますが、既存のメンバーを変更または削除することはできません。
注
XmlSerializerは、XmlAnyElementAttribute属性とXmlAnyAttributeAttribute属性を使用して、不明なデータのラウンドトリップをサポートします。
メッセージ コントラクトのバージョン管理
メッセージ コントラクトのバージョン管理のガイドラインは、バージョン管理データ コントラクトとよく似ています。 厳密なバージョン管理が必要な場合は、メッセージ本文を変更するのではなく、一意の修飾名を持つ新しいメッセージ コントラクトを作成する必要があります。 緩やかなバージョン管理を使用できることがわかっている場合は、新しいメッセージ本文パーツを追加できますが、既存のメッセージ本文パーツを変更または削除することはできません。 このガイダンスは、ベア メッセージ コントラクトとラップされたメッセージ コントラクトの両方に適用されます。
厳密なバージョン管理が使用されている場合でも、メッセージ ヘッダーは常に追加できます。 MustUnderstand フラグは、バージョン管理に影響する可能性があります。 一般に、WCF のヘッダーのバージョン管理モデルは、SOAP 仕様で説明されているとおりです。
サービス コントラクトのバージョン管理
データ コントラクトのバージョン管理と同様に、サービス コントラクトのバージョン管理には、操作の追加、変更、削除も含まれます。
名前、名前空間、およびアクションの指定
既定では、サービス コントラクトの名前はインターフェイスの名前です。 既定の名前空間は http://tempuri.org
され、各操作のアクションは http://tempuri.org/contractname/methodname
。 サービス コントラクトの名前と名前空間を明示的に指定し、各操作に対してアクションを指定して、 http://tempuri.org
の使用を回避し、サービスのコントラクトでインターフェイスとメソッドの名前が公開されないようにすることをお勧めします。
パラメーターと操作の追加
サービスによって公開されるサービス操作の追加は、既存のクライアントがそれらの新しい操作について心配する必要がないため、壊れない変更です。
注
二重コールバック コントラクトに操作を追加することは、重大な変更です。
操作パラメーターまたは戻り値の型の変更
パラメーターまたは戻り値の型の変更は、通常、新しい型が古い型によって実装されたのと同じデータ コントラクトを実装しない限り、重大な変更です。 このような変更を行うには、サービス コントラクトに新しい操作を追加するか、新しいサービス コントラクトを定義します。
操作の削除
操作の削除も重大な変更です。 このような変更を行うには、新しいサービス コントラクトを定義し、新しいエンドポイントで公開します。
障害コントラクト
FaultContractAttribute属性を使用すると、サービス コントラクトの開発者は、コントラクトの操作から返すことができるエラーに関する情報を指定できます。
サービスのコントラクトで説明されているエラーの一覧は、網羅的とは見なされません。 操作は、コントラクトに記述されていないエラーをいつでも返すことができます。 したがって、コントラクトで説明されている一連の障害を変更することは、中断とは見なされません。 たとえば、 FaultContractAttribute を使用してコントラクトに新しい障害を追加したり、コントラクトから既存のエラーを削除したりします。
サービス コントラクト ライブラリ
組織には、コントラクトが中央リポジトリに発行され、サービス実装者がそのリポジトリからコントラクトを実装するコントラクトのライブラリがある場合があります。 この場合、サービス コントラクトをリポジトリに発行する場合、それを実装するサービスを作成するユーザーを制御できなくなります。 そのため、発行後にサービス コントラクトを変更して、実質的に変更不可にすることはできません。 WCF ではコントラクト継承がサポートされています。この継承を使用して、既存のコントラクトを拡張する新しいコントラクトを作成できます。 この機能を使用するには、古いサービス コントラクト インターフェイスから継承する新しいサービス コントラクト インターフェイスを定義し、新しいインターフェイスにメソッドを追加します。 次に、新しいコントラクトを実装するように古いコントラクトを実装するサービスを変更し、新しいコントラクトを使用するように "versionOld" エンドポイント定義を変更します。 "versionOld" クライアントでは、エンドポイントは引き続き "versionOld" コントラクトを公開しているように見えます。を "versionNew" クライアントに指定すると、エンドポイントは "versionNew" コントラクトを公開するように見えます。
アドレスとバインドのバージョン管理
クライアントが新しいエンドポイント アドレスまたはバインドを動的に検出できない限り、エンドポイント アドレスとバインドに対する変更は重大な変更です。 この機能を実装するメカニズムの 1 つは、ユニバーサル探索の説明と統合 (UDDI) レジストリと UDDI 呼び出しパターンを使用することです。このパターンでは、クライアントがエンドポイントとの通信を試み、失敗した場合は、現在のエンドポイント メタデータについて既知の UDDI レジストリを照会します。 その後、クライアントはこのメタデータのアドレスとバインディングを使用してエンドポイントと通信します。 この通信が成功すると、クライアントはアドレスとバインディング情報をキャッシュし、今後使用します。
ルーティング サービスとバージョン管理
サービスに加えられた変更が重大な変更であり、2 つ以上の異なるバージョンのサービスを同時に実行する必要がある場合は、WCF ルーティング サービスを使用して、適切なサービス インスタンスにメッセージをルーティングできます。 WCF ルーティング サービスは、コンテンツ ベースのルーティングを使用します。つまり、メッセージ内の情報を使用して、メッセージをルーティングする場所を決定します。 WCF ルーティング サービスの詳細については、「 ルーティング サービス」を参照してください。 サービスのバージョン管理に WCF ルーティング サービスを使用する方法の例については、「 方法: サービスのバージョン管理」を参照してください。
付録
厳密なバージョン管理が必要な場合の一般的なデータ コントラクトのバージョン管理ガイダンスは、データ コントラクトを不変として扱い、変更が必要な場合は新しいものを作成することです。 新しいデータ コントラクトごとに新しいクラスを作成する必要があるため、古いデータ コントラクト クラスの観点から記述された既存のコードを取得し、新しいデータ コントラクト クラスの観点から書き直す必要がないようにするためのメカニズムが必要です。
このようなメカニズムの 1 つは、インターフェイスを使用して各データ コントラクトのメンバーを定義し、インターフェイスを実装するデータ コントラクト クラスではなく、インターフェイスの観点から内部実装コードを記述することです。 サービスのバージョン 1 の次のコードは、 IPurchaseOrderV1
インターフェイスと PurchaseOrderV1
を示しています。
public interface IPurchaseOrderV1
{
string OrderId { get; set; }
string CustomerId { get; set; }
}
[DataContract(
Name = "PurchaseOrder",
Namespace = "http://examples.microsoft.com/WCF/2005/10/PurchaseOrder")]
public class PurchaseOrderV1 : IPurchaseOrderV1
{
[DataMember(...)]
public string OrderId {...}
[DataMember(...)]
public string CustomerId {...}
}
サービス コントラクトの操作は PurchaseOrderV1
の観点から記述されますが、実際のビジネス ロジックは IPurchaseOrderV1
の観点から記述されます。 その後、バージョン 2 では、次のコードに示すように、新しい IPurchaseOrderV2
インターフェイスと新しい PurchaseOrderV2
クラスが存在します。
public interface IPurchaseOrderV2
{
DateTime OrderDate { get; set; }
}
[DataContract(
Name = "PurchaseOrder",
Namespace = "http://examples.microsoft.com/WCF/2006/02/PurchaseOrder")]
public class PurchaseOrderV2 : IPurchaseOrderV1, IPurchaseOrderV2
{
[DataMember(...)]
public string OrderId {...}
[DataMember(...)]
public string CustomerId {...}
[DataMember(...)]
public DateTime OrderDate { ... }
}
サービス コントラクトは、 PurchaseOrderV2
の観点から記述された新しい操作を含むように更新されます。
IPurchaseOrderV1
に関して記述された既存のビジネス ロジックは引き続きPurchaseOrderV2
で機能し、OrderDate
プロパティを必要とする新しいビジネス ロジックは、IPurchaseOrderV2
の観点から記述されます。