ServiceStack:RESTful 资源版本控制 [英] ServiceStack: RESTful Resource Versioning

查看:50
本文介绍了ServiceStack:RESTful 资源版本控制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've taken a read to the Advantages of message based web services article and am wondering if there is there a recommended style/practice to versioning Restful resources in ServiceStack? The different versions could render different responses or have different input parameters in the Request DTO.

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

I'm leaning toward a URL type versioning (i.e /v1/movies/{Id}), but I have seen other practices that set the version in the HTTP headers (i.e Content-Type: application/vnd.company.myapp-v2).

我希望有一种可以与元数据页面一起使用的方法,但不是必需的,因为我注意到在渲染路由时仅使用文件夹结构/命名空间就可以正常工作.

I'm hoping a way that works with the metadata page but not so much a requirement as I've noticed simply using folder structure/ namespacing works fine when rendering routes.

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

For example (this doesn't render right in the metadata page but performs properly if you know the direct route/url)

  • /v1/movies/{id}
  • /v1.1/movies/{id}

代码

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.对于动态语言来说,这不是什么问题,因为相同的模型可以很容易地被不同版本重用.

Try to evolve (not re-implement) existing services

For versioning, you are going to be in for a world of hurt if you try to maintain different static types for different version endpoints. We initially started down this route but as soon as you start to support your first version the development effort to maintain multiple versions of the same service explodes as you will need to either maintain manual mapping of different types which easily leaks out into having to maintain multiple parallel implementations, each coupled to a different versions type - a massive violation of DRY. This is less of an issue for dynamic languages where the same models can easily be re-used by different versions.

我的建议不是明确版本,而是利用序列化格式中的版本控制功能.

My recommendation is not to explicitly version but take advantage of the versioning capabilities inside the serialization formats.

例如:您通常不需要担心使用 JSON 客户端进行版本控制,因为 JSON 和JSV 序列化程序更具弹性.

E.g: you generally don't need to worry about versioning with JSON clients as the versioning capabilities of the JSON and JSV Serializers are much more resilient.

使用 XML 和 DataContract,您可以自由添加和删除字段,而无需进行重大更改.如果您将 IExtensibleDataObject 添加到您的响应 DTO,您还有可能访问未在 DTO 上定义的数据.我的版本控制方法是防御性编程,因此不会引入破坏性更改,您可以使用旧 DTO 验证集成测试的情况.以下是我遵循的一些提示:

With XML and DataContract's you can freely add and remove fields without making a breaking change. If you add IExtensibleDataObject to your response DTO's you also have a potential to access data that's not defined on the DTO. My approach to versioning is to program defensively so not to introduce a breaking change, you can verify this is the case with Integration tests using old DTOs. Here are some tips I follow:

  • 永远不要更改现有属性的类型 - 如果您需要将其设为不同类型,请添加另一个属性并使用旧/现有属性来确定版本
  • 程序会防御性地意识到旧客户端不存在哪些属性,因此不要强制使用它们.
  • 保留一个全局命名空间(仅与 XML/SOAP 端点相关)

我通过在每个 DTO 项目的 AssemblyInfo.cs 中使用 [assembly] 属性来做到这一点:

I do this by using the [assembly] attribute in the AssemblyInfo.cs of each of your DTO projects:

[assembly: ContractNamespace("http://schemas.servicestack.net/types", 
    ClrNamespace = "MyServiceModel.DtoTypes")]

程序集属性使您无需在每个 DTO 上手动指定显式命名空间,即:

The assembly attribute saves you from manually specifying explicit namespaces on each DTO, i.e:

namespace MyServiceModel.DtoTypes {
    [DataContract(Namespace="http://schemas.servicestack.net/types")]
    public class Foo { .. }
}

如果您想使用与上述默认名称不同的 XML 命名空间,您需要注册:

If you want to use a different XML namespace than the default above you need to register it with:

SetConfig(new EndpointHostConfig {
    WsdlServiceNamespace = "http://schemas.my.org/types"
});

在 DTO 中嵌入版本控制

大多数情况下,如果您进行防御性编程并优雅地发展您的服务,您将不需要确切知道特定客户端使用的版本,因为您可以从填充的数据中推断出来.但在极少数情况下,您的服务需要根据客户端的特定版本调整行为,您可以在 DTO 中嵌入版本信息.

Embedding Versioning in DTOs

Most of the time, if you program defensively and evolve your services gracefully you wont need to know exactly what version a specific client is using as you can infer it from the data that is populated. But in the rare cases your services needs to tweak the behavior based on the specific version of the client, you can embed version information in your DTOs.

随着您发布的 DTO 的第一个版本,您可以愉快地创建它们,而无需考虑版本控制.

With the first release of your DTOs you publish, you can happily create them without any thought of versioning.

class Foo {
  string Name;
}

但也许由于某种原因,表单/UI 发生了变化,您不再希望客户端使用不明确的 Name 变量,并且您还想跟踪客户端使用的特定版本:

But maybe for some reason the Form/UI was changed and you no longer wanted the Client to use the ambiguous Name variable and you also wanted to track the specific version the client was using:

class Foo {
  Foo() {
     Version = 1;
  }
  int Version;
  string Name;
  string DisplayName;
  int Age;
}

后来在一次团队会议上讨论过,DisplayName 不够好,您应该将它们拆分为不同的字段:

Later it was discussed in a Team meeting, DisplayName wasn't good enough and you should split them out into different fields:

class Foo {
  Foo() {
     Version = 2;
  }
  int Version;
  string Name;
  string DisplayName;
  string FirstName;
  string LastName;  
  DateTime? DateOfBirth;
}

所以当前状态是您有 3 个不同的客户端版本,现有调用如下所示:

So the current state is that you have 3 different client versions out, with existing calls that look like:

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 版本)例如:

You can continue to handle these different versions in the same implementation (which will be using the latest v3 version of the DTOs) e.g:

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)
    }
}

这篇关于ServiceStack:RESTful 资源版本控制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆