我已经阅读了许多关于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会有越来越多的版本,但在某些时候,应该从代码库中删除旧版本。
所以问题是
当前的src
目录结构是例如(public
,vendor
是一个级别):
.
└── 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
答案 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的所有版本表现不同。