版本化REST API的目录/命名空间结构

时间:2016-06-20 08:46:40

标签: php api rest domain-driven-design versioning

我已经阅读了许多关于RESTful API的URI版本化的最佳实践,例如(http://api.example.com/v1/users - > http://api.example.com/v2/users以及HATEOAS),但对目录或我的代码库中的命名空间结构(PHP + silex框架)。 我的代码库现在可以做什么:代码库本身支持多个版本的API,通过路由或Accept头识别版本,并可以根据识别的API版本调用不同的控制器/类/方法(例如在v1 UserController::listUsers()中,在第2节:UserControllerV2::getListUsers())。 随着时间的推移,API会有越来越多的版本,但在某些时候,应该从代码库中删除旧版本。

所以问题是

  • 应该对哪些类进行版本化? (控制器,模型,视图等......)
  • 如果涉及捆绑域驱动设计,如何做到这一点? (版本完整的捆绑目录或仅在捆绑包内?)
  • 他们应该怎么版本? (类继承(How?),目录结构......)
    • 代码重复较少,
    • 轻松删除旧版本
    • 如果您修复了版本之间共享的内容,则会减少副作用

当前的src目录结构是例如(publicvendor是一个级别):

   .
   └── TestNext
       ├── ApiV1
       │   └── Route
       │       └── ApiV1RoutesProvider.php
       ├── Configuration
       │   ├── Controller
       │   ├── Loader
       │   │   └── YamlConfigLoader.php
       │   ├── Model
       │   └── Service
       │       └── SymfonyConfigServiceProvider.php
       ├── Security
       │   └── Authenticator
       │       └── TokenAuthenticator.php
       ├── Shared
       │   └── Controller
       │       └── BaseController.php
       ├── User
       │   ├── Controller
       │   │   └── UserController.php
       │   ├── Model
       │   └── Service
       ├── Bootstrap.php
       ├── Console.php
       └── Constants.php

1 个答案:

答案 0 :(得分:2)

首先,值得分开概念。您拥有自己的域名,并且拥有API层。在分层之后,您的API层应该位于您的域之上(并且"分开"),并且您的域应该完全不知道API的存在。它有助于围绕这个进行结构化,一种方法就是如下:

src/Acme/Api/
src/Acme/Core/

API中的所有内容都处理HTTP级别的通信;路由,请求和响应映射,状态代码等

Core中的所有内容都处理与业务相关的操作。遵循CQRS风格方法,您可以使用以下内容结束:

src/Acme/Api/Controller
src/Acme/Api/DTO/Request/
src/Acme/Api/DTO/Response/
src/Core/Domain/
src/Core/Command/
src/Core/CommandHandler/
src/Core/Infrastructure/
src/Core/ReadModel/

但实际上,布局&命名将变得灵活,它在某种程度上取决于您正在应用的架构模式。在DDD环境中,关键点在于放置聚合,模型,值对象和放大器。存储库在一些公共名称空间(Domain)下一起存放。

解决您的个人问题:

  

应该对哪些类进行版本化? (控制器,模型,视图等......)

在我看来,版本模型没有意义。模型应该始终是业务的最新表示,并且维护较旧的规则似乎是不必要的。

您如何处理版本控制取决于您。您可以将其视为一种对API进行全面版本化的方式(路径+请求/响应有效负载),或者只是请求/响应有效负载。在其中对API进行版本控制可能是最灵活的,但是进行如此剧烈的改变是相对罕见的。您可能需要考虑在每个请求级别上将Accept / Content-Type标头用于版本。您甚至可以使用两者的组合(URL中的主要版本凹凸来重新定义路径并强制使用特定的Content-Type版本。)

从理论上讲,您可以更进一步,并对您的JSON模式进行版本控制,这些模式定义了您的请求/响应有效负载。举个例子:

GET /1.0/user/317684e2-3704-11e6-8172-b0bea8105888/payment/2caad76e-3705-11e6-8172-b0bea8105888
Accept: application/vnd+acme+json; schema=payment.out.v1.json

HTTP/1.1 200 OK
Content-Type: application/vnd+acme+json; schema=payment.out.v1.json;     charset=UTF-8

{
  "payee": "Bob Dylan",
  // snip
}
```

如果要向响应有效负载引入非向后兼容的更改,则可以允许客户端针对" v2"付款的JSON架构:

GET /1.0/user/317684e2-3704-11e6-8172-b0bea8105888/payment/2caad76e-3705-11e6-8172-b0bea8105888
Accept: application/vnd+acme+json; schema=payment.out.v2.json

HTTP/1.1 200 OK
Content-Type: application/vnd+acme+json; schema=payment.out.v2.json; charset=UTF-8

{
  "payee": {
    "forename": "Bob",
    "surname": "Dylan"
  },
  // snip
}

主要API版本未更改,但响应有效负载不同。您可以及时弃用旧版本,或者确实提高主要API版本并在有效负载级别设置最小值。

通过利用Content-Type标题,可以对请求正文应用相同的技术。

  

如果涉及捆绑域驱动设计,如何做到这一点? (版本完整的捆绑目录或只是在捆绑包内?)   他们应该如何版本化? (类继承(How?),目录结构......)

也许它已经得到了解答,但是尽量不要考虑Symfony风格的捆绑。您的Core或其他任何您想要的内容都不应该包含任何Symfony / Silex。在Infrastructure中,您可能有Repository接口的实现,可能使用Doctrine,但是我们利用Doctrine作为库,而不是依赖Symfony / Silex作为框架。如果你做得好,理论上你可以换掉一个完全不同的框架的API层而不需要对Core进行任何更改。

但是有一些不可避免的事情:依赖注入&组态。使用Symfony,我在过去创建了CoreBundle来解决这个问题。这位于Core之外。

顺便说一句,在这样的项目中只包含一个有限的上下文可能是明智的,所以不要担心将事情进一步分类。

  

代码重复较少,

重复发生是不可避免的,但是这只应该是您的DTO的情况,因为您不会对定义行为的对象(您的模型)进行版本控制。 DTO中的重复并不是什么大不了的事。如果获得的清晰度比丢失的更清晰,那么只需为每个版本定义一个类。工具可能会告诉您代码已经过复制粘贴,但这些工具无法理解这些背景。

  

轻松删除旧版

如果您定义单独的DTO,那么这样的任务应该很简单。删除DTO,删除JSON模式,现在您的API应该拒绝指定旧版本的请求。

  如果您修复了版本之间共享的内容,那么副作用就会减少

如果要映射到API层中的请求/响应对象,那么它的映射代码可能会受到模型更改的影响。良好的测试覆盖率(通过API合同测试,进程内组件测试和单元测试)应验证对模型的更改不会导致API的所有版本表现不同。