尝试发展(而不是重新实现)现有服务
对于版本控制,如果您尝试为不同的版本端点维护不同的静态类型,您将陷入困境。我们最初是沿着这条路线开始的,但是一旦您开始支持您的第一个版本,维护同一服务的多个版本的开发工作就会爆炸式增长,因为您将需要维护不同类型的手动映射,这很容易泄露到必须维护多个并行实现,每个都耦合到不同的版本类型 - 严重违反 DRY。这对于动态语言来说不是什么问题,因为相同的模型可以很容易地被不同的版本重用。
利用序列化程序中的内置版本控制
我的建议不是显式版本,而是利用序列化格式中的版本控制功能。
例如:您通常不需要担心 JSON 客户端的版本控制,因为JSON 和 JSV 序列化器的版本控制功能更具弹性。
防御性地增强您现有的服务
使用 XML 和 DataContract,您可以在不进行重大更改的情况下自由添加和删除字段。如果您将IExtensibleDataObject
DTO 添加到您的响应中,您也有可能访问未在 DTO 上定义的数据。我的版本控制方法是防御性编程,因此不会引入重大更改,您可以验证使用旧 DTO 进行集成测试的情况。以下是我遵循的一些提示:
- 永远不要更改现有属性的类型 - 如果您需要它是不同的类型,请添加另一个属性并使用旧的/现有的属性来确定版本
- 程序防御性地意识到旧客户不存在哪些属性,因此不要强制它们。
- 保留一个全局命名空间(仅与 XML/SOAP 端点相关)
我通过使用您的每个 DTO 项目的AssemblyInfo.cs中的 [assembly] 属性来做到这一点:
[assembly: ContractNamespace("http://schemas.servicestack.net/types",
ClrNamespace = "MyServiceModel.DtoTypes")]
程序集属性使您免于在每个 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;
}
但可能由于某种原因,Form/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)
}
}