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