ServiceStack:RESTful 资源版本控制

2023-11-29

我读过基于消息的 Web 服务的优点文章,我想知道是否有推荐的样式/实践来对 ServiceStack 中的 Restful 资源进行版本控制?不同的版本可能会呈现不同的响应或在请求 DTO 中具有不同的输入参数。

我倾向于 URL 类型版本控制(即 /v1/movies/{Id}),但我看到了在 HTTP 标头中设置版本的其他做法(即 Content-Type: application/vnd.company.myapp-v2 )。

我希望有一种与元数据页面一起使用的方法,但没有那么多的要求,因为我注意到在渲染路由时简单地使用文件夹结构/命名空间就可以很好地工作。

例如(这不会在元数据页面中正确呈现,但如果您知道直接路由/url,则可以正常执行)

  • /v1/电影/{id}
  • /v1.1/电影/{id}

Code

namespace Samples.Movies.Operations.v1_1
{
    [Route("/v1.1/Movies", "GET")]
    public class Movies
    {
       ...
    } 
}
namespace Samples.Movies.Operations.v1
{
    [Route("/v1/Movies", "GET")]
    public class Movies
    {
       ...
    }   
}

以及相应的服务...

public class MovieService: ServiceBase<Samples.Movies.Operations.v1.Movies>
{
    protected override object Run(Samples.Movies.Operations.v1.Movies request)
    {
    ...
    }
}

public class MovieService: ServiceBase<Samples.Movies.Operations.v1_1.Movies>
    {
        protected override object Run(Samples.Movies.Operations.v1_1.Movies request)
        {
        ...
        }
    }

尝试发展(而不是重新实施)现有服务

对于版本控制,如果您尝试为不同版本端点维护不同的静态类型,您将陷入痛苦的境地。我们最初是沿着这条路线开始的,但是一旦您开始支持您的第一个版本,维护同一服务的多个版本的开发工作就会爆炸,因为您将需要维护不同类型的手动映射,这很容易导致必须维护多个版本并行实现,每个都耦合到不同的版本类型 - 严重违反了 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)
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ServiceStack:RESTful 资源版本控制 的相关文章

随机推荐