既存のサービスを進化させる (再実装しない) ようにする
バージョン管理の場合、異なるバージョン エンドポイントに対して異なる静的型を維持しようとすると、大変なことになります。最初はこのルートで開始しましたが、最初のバージョンのサポートを開始するとすぐに、同じサービスの複数のバージョンを維持するための開発作業が爆発的に増加します。さまざまなタイプの手動マッピングを維持する必要があるため、簡単に複数のバージョンを維持する必要が生じます。それぞれが異なるバージョン タイプに結合された並列実装 - DRY の大規模な違反。これは、同じモデルを異なるバージョンで簡単に再利用できる動的言語ではあまり問題になりません。
シリアライザーに組み込まれているバージョン管理を利用する
私の推奨事項は、明示的にバージョンを指定するのではなく、シリアル化形式内のバージョン管理機能を利用することです。
例: 通常、 JSON および JSV シリアライザーのバージョン管理機能ははるかに回復力があるため、JSON クライアントのバージョン管理について心配する必要はありません。
既存のサービスを防御的に強化する
XML と DataContract を使用すると、重大な変更を加えることなく、フィールドを自由に追加および削除できます。応答 DTOに追加IExtensibleDataObject
すると、DTO で定義されていないデータにアクセスする可能性もあります。バージョニングに対する私のアプローチは、破壊的な変更を導入しないように防御的にプログラムすることです。古い DTO を使用した統合テストでこれが当てはまることを確認できます。ここに私が従ういくつかのヒントがあります:
- 既存のプロパティのタイプを変更しない - 別のタイプにする必要がある場合は、別のプロパティを追加し、古い/既存のプロパティを使用してバージョンを決定します
- プログラムは、古いクライアントには存在しないプロパティを防御的に認識するため、それらを必須にしないでください。
- 単一のグローバル名前空間を維持する (XML/SOAP エンドポイントにのみ関連)
これを行うには、各 DTO プロジェクトのAssemblyInfo.csで[assembly] 属性を使用します。
[assembly: ContractNamespace("http://schemas.servicestack.net/types",
ClrNamespace = "MyServiceModel.DtoTypes")]
assembly 属性を使用すると、各 DTO で明示的な名前空間を手動で指定する必要がなくなります。つまり、次のようになります。
namespace MyServiceModel.DtoTypes {
[DataContract(Namespace="http://schemas.servicestack.net/types")]
public class Foo { .. }
}
上記のデフォルトとは異なる XML 名前空間を使用する場合は、次のように登録する必要があります。
SetConfig(new EndpointHostConfig {
WsdlServiceNamespace = "http://schemas.my.org/types"
});
DTO にバージョニングを埋め込む
ほとんどの場合、防御的にプログラミングし、サービスを適切に進化させる場合、特定のクライアントが使用しているバージョンを正確に知る必要はありません。入力されたデータから推測できるからです。ただし、まれに、クライアントの特定のバージョンに基づいてサービスの動作を微調整する必要がある場合は、DTO にバージョン情報を埋め込むことができます。
公開する DTO の最初のリリースでは、バージョン管理を考えなくても問題なく作成できます。
class Foo {
string Name;
}
しかし、何らかの理由でフォーム/UI が変更され、クライアントがあいまいなName変数を使用する必要がなくなり、クライアントが使用していた特定のバージョンを追跡したい場合もあります。
class Foo {
Foo() {
Version = 1;
}
int Version;
string Name;
string DisplayName;
int Age;
}
後でチーム ミーティングで議論されましたが、DisplayName は十分ではなく、別のフィールドに分割する必要があります。
class Foo {
Foo() {
Version = 2;
}
int Version;
string Name;
string DisplayName;
string FirstName;
string LastName;
DateTime? DateOfBirth;
}
したがって、現在の状態では、3 つの異なるクライアント バージョンがあり、既存の呼び出しは次のようになります。
v1 リリース:
client.Post(new Foo { Name = "Foo Bar" });
v2 リリース:
client.Post(new Foo { Name="Bar", DisplayName="Foo Bar", Age=18 });
v3 リリース:
client.Post(new Foo { FirstName = "Foo", LastName = "Bar",
DateOfBirth = new DateTime(1994, 01, 01) });
これらの異なるバージョンを同じ実装 (最新の v3 バージョンの DTO を使用する) で引き続き処理できます。例:
class FooService : Service {
public object Post(Foo request) {
//v1:
request.Version == 0
request.Name == "Foo"
request.DisplayName == null
request.Age = 0
request.DateOfBirth = null
//v2:
request.Version == 2
request.Name == null
request.DisplayName == "Foo Bar"
request.Age = 18
request.DateOfBirth = null
//v3:
request.Version == 3
request.Name == null
request.DisplayName == null
request.FirstName == "Foo"
request.LastName == "Bar"
request.Age = 0
request.DateOfBirth = new DateTime(1994, 01, 01)
}
}