ServiceStack:REST风格的资源版本(ServiceStack: RESTful Res

2019-06-18 04:05发布

我已经采取了读到基于消息的Web服务的优势的文章,我想知道是否有有一个推荐款式/实践ServiceStack到版本RESTful资源? 不同版本可能呈现不同的反应,或在请求DTO不同的输入参数。

我倾向于一个URL类型的版本(即/ V1 / / {ID}电影),但我所看到的是,在HTTP头中设置的版本等做法(即内容类型:应用程序/ vnd.company.myapp-V2 )。

渲染路线,当我为我注意到简单地使用文件夹结构希望的方式与元数据页的作品,但没有这么多的要求/命名空间正常工作。

例如(这并不正确呈现在元数据页,但执行正确,如果你知道直接路由/ URL)

  • / V1 /电影/ {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)
        {
        ...
        }
    }

Answer 1:

尽量进化(不重新实现)现有服务

对于版本,你会如果你试图保持不同的静态类型不同版本的端点是在伤害的世界。 最初,我们开始沿着这条路线,但只要你开始支持你的第一个版本的开发努力保持相同的服务爆炸的多个版本,你要么需要保持不同类型的手工映射这就容易漏出到维护多个并行实现中,每个耦合到一个不同的版本键入 - 大规模违反DRY的。 这是不太对动态语言的一个问题,即同样的模型可以很容易地被重新使用不同的版本。

采取串行内置版本的优点

我的建议是不明确的版本,但采取的序列化格式内部版本功能的优势。

比如:你一般不需要担心使用JSON的客户作为的版本功能版本JSON和JSV序列化器是更加有弹性 。

提升现有服务的防守

随着XML和DataContract的,你可以自由添加和删除字段而不进行重大更改。 如果添加IExtensibleDataObject您的回复DTO的你也有访问,这不是对DTO定义的数据的可能性。 我对版本控制方法是编程防守,这样不会引入重大更改,您可以验证这与使用旧的DTO的集成测试的情况。 这里有一些提示我如下:

  • 不要改变现有属性的类型 - 如果你需要的是一个不同的类型添加另一个属性并使用旧/现有一个确定版本
  • 防守计划实现什么样的属性不与旧客户端存在,所以不要让他们强制性的。
  • 保持一个全局命名空间(仅适用于XML / SOAP端点相关)

我这样做是通过使用每个DTO项目的AssemblyInfo.cs中的[组装]属性:

[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;
}

但也许由于某些原因,表/界面被改变,而你不再想客户端使用模棱两可的名称变量,而您也想追踪客户使用特定版本:

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

后来人们在团队会议上讨论,显示名称还不够好,你应该出来分成不同的领域:

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


Answer 2:

取景问题

该API是公开其表达系统的一部分。 它定义的概念,并在您的域通信的语义。 当你想改变什么可以表达或者它是如何表达的问题就来了。

可以有表达的两种方法,什么是被表达的差异。 第一个问题往往是在令牌(名和姓,而不是名)的差异。 第二个问题是表达不同的东西(重命名自己的能力)。

一项长期的解决方案版本将需要解决这两个难题。

不断发展的API

通过改变资源类型演进服务是一种隐含的版本的。 它使用对象的构造,以确定行为。 当有只表达的方法(如名字)的微小变化的效果最好。 它不用于更复杂的变化,以表达或变更的,以表现力的变化的方法会奏效。 代码往往是整个散射。

具体版本

当变化变得更加复杂的是要保持逻辑的每个版本不同的是很重要的。 即使在mythz例如,他分离出了各个版本的代码。 但是,代码仍然是在同样的方法混合在一起。 这是很容易的代码,不同的版本开始崩于对方,很容易传播出去。 对于以前的版本摆脱的支持是很困难的。

此外,您将需要保持你的旧代码同步到其依赖的任何变化。 如果数据库的变化,支持老型号的代码也需要改变。

更好的方法

我发现最好的办法是直接解决问题的表达。 每个API的新版本发布的时候,它会在新层之上实现。 这通常是容易的,因为变化很小。

它真正的亮点在两个方面:首先是所有的代码来处理的映射是在一个地方,所以很容易理解或在以后删除而第二次作为新的API开发(俄罗斯公仔模型),它不需要维护。

问题是,当新的API比旧的API较少表现。 这是需要无论方案是什么保留旧版本周围需要解决的问题。 它只是变得清晰,有一个问题,什么该问题的解决方案是。

从mythz的例子在这种风格的例子是:

namespace APIv3 {
    class FooService : RestServiceBase<Foo> {
        public object OnPost(Foo request) {
            var data = repository.getData()
            request.FirstName == data.firstName
            request.LastName == data.lastName
            request.DateOfBirth = data.dateOfBirth
        }
    }
}
namespace APIv2 {
    class FooService : RestServiceBase<Foo> {
        public object OnPost(Foo request) {
            var v3Request = APIv3.FooService.OnPost(request)
            request.DisplayName == v3Request.FirstName + " " + v3Request.LastName
            request.Age = (new DateTime() - v3Request.DateOfBirth).years
        }
    }
}
namespace APIv1 {
    class FooService : RestServiceBase<Foo> {
        public object OnPost(Foo request) {
            var v2Request = APIv2.FooService.OnPost(request)
            request.Name == v2Request.DisplayName
        }
    }
}

每个暴露对象是明确的。 同样映射代码仍然需要在这两种风格的写入,但在分离式的,需要编写只相关类型的映射。 有没有必要明确地映射不适用(这是错误的另一种潜在来源)代码。 当您添加未来的API或更改API层的依赖以前的API的依赖关系是静态的。 例如,如果数据源的变化则只有最近的API(第3版)需要在这种风格的改变。 在组合的风格,你需要为每个支持的API代码的变化。

在评论一个问题是增加的类型的代码库。 这不是一个问题,因为这些类型的暴露在外。 代码库提供了类型明确,所以很容易发现和测试隔离。 这是更好的可维护性予以明确。 另一个好处是,该方法不会产生额外的逻辑,但只增加了额外的类型。



Answer 3:

我也想配备了一个解决方案并打算做类似下面的。 (上很多Googlling和StackOverflow上查询所以这是建立在许多人的肩膀上的基础。)

首先,我不想争论如果版本应该在URI或请求头。 有优点/两种方法的缺点,所以我想我们每个人都需要使用哪些符合我们的要求最好的。

这是关于如何设计/架构的Java消息对象和资源实现类。

因此,让我们开始吧。

我将分两步处理这个。 小的变化(例如,1.0〜1.1)和主要的变化(例如1.1至2.0)

对于小的变化方法

所以我们可以说,我们去通过@mythz使用相同的例子类

起初,我们有

class Foo {   string Name; }

我们提供访问该资源作为/V1.0/fooresource/{id}

在我的使用情况下,我使用JAX-RS,

@Path("/{versionid}/fooresource")
public class FooResource {

    @GET
    @Path( "/{id}" )
    public Foo getFoo (@PathParam("versionid") String versionid, (@PathParam("id") String fooId) 
    {
      Foo foo = new Foo();
     //setters, load data from persistence, handle business logic etc                   
     Return foo;
    }
}

现在,让我们说,我们添加2个附加属性,以富。

class Foo { 
    string Name;   
    string DisplayName;   
    int Age; 
}

我在这一点上做的是用@版本注解注释的属性

class Foo { 
    @Version(“V1.0")string Name;   
    @Version(“V1.1")string DisplayName;   
    @Version(“V1.1")int Age; 
}

然后我会根据所请求的版本响应滤波器,后返回给用户只匹配该版本的属性。 请注意,为了方便,如果有应该适用于所有版本返回的属性,那么你就是不标注它和过滤器将不考虑返回它的请求的版本

这有点像一个中介层。 我已经解释了是一个简单的版本,它可以变得很复杂,但希望你的想法。

重大版本的方法

现在,这个可以当有一个很大的变化已经完成从一个版本到另一个变得相当复杂。 也就是说,当我们需要移动到第二个选项。

选项2本质上是分支代码库,然后做代码库的变化,主办不同的情况下这两个版本。 在这一点上,我们可能需要重构代码库位,除去版本调解方法的一个介绍的复杂性(即让代码更清洁),这可能主要是由过滤器。

请注意,这只是想我想和还没有实现它尚未与不知道这是一个好主意。

此外,如果有很好的调解引擎/ ESB的,可以做到这类型的转换,而无需使用过滤器,但没有看到任何这是因为使用过滤器一样简单我想知道。 也许我没有足够的搜索。

想知道别人的想法,如果这个解决方案将解决原来的问题。



文章来源: ServiceStack: RESTful Resource Versioning