Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/299.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 版本化REST API的目录/命名空间结构_Php_Api_Rest_Domain Driven Design_Versioning - Fatal编程技术网

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

Php 版本化REST API的目录/命名空间结构,php,api,rest,domain-driven-design,versioning,Php,Api,Rest,Domain Driven Design,Versioning,我已经阅读了很多关于RESTful API的URI版本控制的最佳实践,例如(http://api.example.com/v1/users->http://api.example.com/v2/users和),但对我的代码库(PHP+silex框架)中的目录或命名空间结构没有太多了解。 我的代码库现在可以做什么:代码库本身支持API的多个版本,通过路由或接受头识别版本,并可以根据识别的API版本调用不同的控制器/类/方法(例如,在v1UserController::listUsers()中,在v

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

所以问题是

  • 应该对哪些类进行版本控制?(控制器、模型、视图等)
  • 当涉及到领域驱动的设计时,如何做到这一点?(完整捆绑包目录的版本,还是仅在捆绑包中?)
  • 它们应该如何版本化?(类继承(如何?),目录结构…)
    • 代码重复更少
    • 容易删除旧版本
    • 如果您修复了版本间共享的内容,则会减少副作用
当前的
src
目录结构是(例如,
public
vendor
是一级结构):


首先,将概念分开是值得的。你有你的域,你有一个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进行版本控制可能是最灵活的,但进行如此剧烈的更改相对较少。您可能需要考虑使用“代码”>“接受/代码> /<代码>内容类型< /代码>标题,以便在每个请求级别上进行版本化。您甚至可以将两者结合使用(URL中的主版本凹凸用于重新定义路径,并强制特定的
内容类型
s版本)

从理论上讲,您可以更进一步,对定义请求/响应有效负载的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版本,并在负载级别设置一个最小值

通过利用
内容类型
头,同样的技术也可以应用于请求主体

当涉及到领域驱动的设计时,如何做到这一点?(完整捆绑包目录的版本,还是仅在捆绑包中?) 它们应该如何版本化?(类继承(如何?),目录结构…)

也许它已经得到了回答,但不要用Symfony风格的捆绑包来思考。您的
核心
,或者您想称之为的任何东西,都不应该包含任何特定于Symfony/Silex的内容。在
基础设施
中,您可能有
存储库
接口的实现,可能使用条令,但这是我们利用条令作为库,而不是依赖Symfony/Silex作为框架。如果你做得好,理论上你可以把API层换成一个完全不同的框架,而不需要对
Core
做任何更改

不过,有两件事是不可避免的:依赖项注入和配置。使用Symfony,我在过去创建了一个
CoreBundle
来解决这个问题。它位于
核心
之外

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

代码重复更少

复制是不可避免的,但当这种情况只适用于DTO时,因为您不会对定义行为(模型)的对象进行版本控制。DTO中的复制实际上并不是什么大问题。如果获得的比失去的更清晰,那么只需为每个版本定义一个类。工具可能会告诉您代码已被复制粘贴,但这些工具无法理解此类上下文

容易删除旧版本

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

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

如果要映射到API层中的请求/响应对象,那么模型中的更改会影响映射代码。良好的测试覆盖率(通过API契约测试、进程内组件测试和单元测试)应验证对模型的更改不会重新进行
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
}