引用来源
- https://www.jianshu.com/p/21079ff15a1c
- https://www.jianshu.com/p/2eaa134a9fb8
- http://pmjones.io/adr/
- https://github.com/pmjones/adr
- https://blog.igevin.info/posts/restful-api-get-started-to-write/
- http://www.slimframework.com/docs/v3/cookbook/action-domain-responder.html
出现的原因
MVC 诞生于 1979 年,它诞生于使用 CLI 用户界面的桌面应用上下文中,它暗示如果用户外部因素导致数据库变化,那么 UI 就应该自动地变化。TA是一种展现模式,是一种将模型、领域与用户界面分离的方法。
![](https://img.haomeiwen.com/i2622710/62ba6d3da3475e79.png)
TA将代码拆分成了三个概念单元:
-
代表业务逻辑的 Model (模型);
-
代表 UI 控件的 View (视图):按钮、文本框等等;
-
在视图和模型之间居中协调的 Controller(控制器),这意味着:
- 它决定显示哪些视图以及哪些数据;
- 它将用户操作(例如点击按钮)转换成业务逻辑。
最初的 MVC 模式还有其它一些需要了解的的重要概念:
- View 直接使用 Model 数据对象来展示数据;
- 当 Model 发生变化时,会触发一个事件立即更新 View;
- 每一个 View 通常只关联一个 Controller;
- 每个界面可以包含多对 View 和 Controller;
- 每个Controller 可以对应多个 View。
现在大众所熟知的 HTTP 请求响应范式并没有使用最初的 MVC 风格。这是因为,按照原始的设想,数据从 View 流向 Controller,这和我熟悉的一样,但另一边,数据直接从 Model 流向 View,并没有经过 Controller。
而且在现在的请求响应范式中,当数据库中的数据发生变化时,并不会触发浏览器中展示 View 的更新(尽管可以用 Web Socket 实现)。要看到更新后的数据,用户需要发起一次新的请求,而更新的数据总是会通过 Controller 返回。
因为大多数 Web 应用不会用 UI 变化来作为服务端发生的变化的后果,它们总是从 UI 发起对服务端的调用来更新界面。
为了改进MVC模式在web应用中的缺陷,有一些业内的大拿提出了一些改良方案.
如 Paul James 在 2008 年提出 Resource-Method-Representation模式(简称 RMR),它将 MVC 模式应用到了 REST API 上下文之中。其主要包括:
Resource
它的思想是将实体建模成 REST 资源(Resource,模式名称中的第一个 R),它只有和 HTTP 方法对应的公有方法:
<?php
// taken from http://www.peej.co.uk/articles/rmr-architecture.html
class Resource {
private resourceData = [];
method constructor(request, dataSource) {
// load data from data source
}
method get(request) {
return new Response(200, getRepresentation(request.url, resourceData));
}
method put(request) {
return new Response(405);
}
method post(request) {
return new Response(405);
}
method delete(request) {
return new Response(405);
}
}
Method
当向 API 发出一个请求时,它被路由给这些业务对象(即资源)中的一个,然后对应这个请求 HTTP 方法的资源中的一个方法(Method)被调用。该业务对象方法接下来将负责返回一个包含状态码和标头的完整HTTP响应。
Representation
以 API 或发起请求的客户端选择的格式表示的资源就是展现(Representation),这些格式有,JSON、XML 等等....展现就是由方法创建并返回给客户端的响应的内容,如果有任何需要返回的内容的话。
改进
RMR 告诉我们如何设计我们的业务对象和领域对象。不仅如此,它还告诉我们领域实体应该体现传达机制:HTTP 方法。
这就意味着它不只是展现模式,而是架构模式,因为它要影响应用的所有层次。它还意味着以这种模式构建的应用并非以领域为中心,而是以 HTTP 为中心。我们的实体最终拥有的是反应传达机制而非领域操作的方法。
缺陷
它将自己与 HTTP 紧密地耦合在一起,以至于很难将其映射到 CLI 或 GUI 界面。
Action-Domain-Responder(ADR)
ADR 模式由 Paul M. Jones 于 2014 年提出,其思想和 RMR 一致,就是将 MVC 应用于 Web REST API 上下文。
![](https://img.haomeiwen.com/i2622710/5fff363eb3b4cee7.png)
Action
是连接 Domain 和 Responder 的逻辑。它使用收集自 HTTP 请求的输入调用 Domain,然后使用构建 HTTP 响应所需的数据调用 Responder 。
<?php
namespace Pmjones\Adr\Web\Blog\Add;
use Pmjones\Adr\Domain\Blog\BlogService;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class BlogAddAction
{
protected $domain;
protected $responder;
public function __construct(
BlogService $domain,
BlogAddResponder $responder
) {
$this->domain = $domain;
$this->responder = $responder;
}
public function __invoke(Request $request) : Response
{
$payload = $this->domain->newPost();
return $this->responder->__invoke($request, $payload);
}
}
Domain
是组成应用核心的领域逻辑的入口,它根据需要修改状态并保存。 它可能是事务脚本、服务层、应用服务或者其它类似的概念。
<?php
namespace Pmjones\Adr\Domain\Blog;
use Pmjones\Adr\DataSource\Blog\BlogMapper;
use Pmjones\Adr\Domain\ApplicationService;
use Pmjones\Adr\Domain\Payload;
use Exception;
class BlogService extends ApplicationService
{
protected $mapper;
protected $filter;
public function __construct(
BlogMapper $mapper,
BlogFilter $filter
) {
$this->mapper = $mapper;
$this->filter = $filter;
}
protected function fetchPage(int $page = 1, int $paging = 10) : Payload
{
$blogs = $this->mapper->selectAllByPage($page, $paging);
return $this->newPayload(Payload::STATUS_FOUND, [
'blogs' => $blogs,
]);
}
protected function fetchPost(int $id) : Payload
{
$blog = $this->mapper->selectOneById($id);
if ($blog === null) {
return $this->newPayload(Payload::STATUS_NOT_FOUND, [
'id' => $id
]);
}
return $this->newPayload(Payload::STATUS_FOUND, [
'blog' => $blog
]);
}
protected function newPost(array $data = []) : Payload
{
return $this->newPayload(Payload::STATUS_NEW, [
'blog' => $this->mapper->newRecord($data)
]);
}
protected function addPost(array $data) : Payload
{
// instantiate a new record
$blog = $this->mapper->newRecord($data);
// validate the record
if (! $this->filter->forInsert($blog)) {
return $this->newPayload(Payload::STATUS_NOT_VALID, [
'blog' => $blog,
'messages' => $this->filter->getMessages()
]);
}
// insert the record
$this->mapper->insert($blog);
return $this->newPayload(Payload::STATUS_CREATED, [
'blog' => $blog,
]);
}
protected function editPost(int $id, array $data) : Payload
{
// fetch the record
$blog = $this->mapper->selectOneById($id);
if ($blog === null) {
return $this->newPayload(Payload::STATUS_NOT_FOUND, [
'id' => $id
]);
}
// set data in the record; do not overwrite existing $id
unset($data['id']);
$blog->setData($data);
// validate the record
if (! $this->filter->forUpdate($blog)) {
return $this->newPayload(Payload::STATUS_NOT_VALID, [
'blog' => $blog,
'messages' => $this->filter->getMessages()
]);
}
// update the record
$this->mapper->update($blog);
return $this->newPayload(Payload::STATUS_UPDATED, [
'blog' => $blog,
]);
}
protected function deletePost(int $id) : Payload
{
// fetch the record
$blog = $this->mapper->selectOneById($id);
if (! $blog) {
return $this->newPayload(Payload::STATUS_NOT_FOUND, [
'id' => $id
]);
}
// delete the record
$this->mapper->delete($blog);
return $this->newPayload(Payload::STATUS_DELETED, [
'blog' => $blog,
]);
}
}
Responder
是基于自 Action 接收的数据创建 HTTP 响应的展现逻辑。它处理状态码、标头与 Cookie、内容、格式与转换、模板与视图,等等。
<?php
namespace Pmjones\Adr\Web\Blog\Add;
use Pmjones\Adr\Web\Blog\BlogResponder;
class BlogAddResponder extends BlogResponder
{
protected function new() : void
{
$this->renderTemplate('add');
}
}
工作机制
- Web 处理程序收到 HTTP 请求并派发给 Action;
- Action 调用 Domain,从 HTTP 请求里收集任何需要的输入给 Domain;
- 然后 Action 使用创建 HTTP 响应所需的数据(通常是 HTTP 请求和 Domain结果,如果有的话)调用 Responder;
- Responder 使用 Action 提供给它的数据构造 HTTP 响应;
- Action 将 HTTP 响应返回给发送 HTTP 响应的 Web 处理程序。
Responder 基于对领域响应的解析和理解来构造 HTTP 响应,而领域响应又依赖操作方法的用例。这意味着每个操作方法都需要一个特定的 Responder。
如果我们将所有资源方法放到同一个控制器中,我们就需要实例化全部 Responder 并注入到控制器中,而我们在一次 HTTP 请求中只会使用一个 Responder,这显然不是最优的方案。解决方法是每个控制器只有一个方法,这种控制器和它唯一的操作方法就是 ADR 所说的 Action。
既然 Action 只有一个方法,方法名就可以使用通用的 run、execute、或是 PHP 中的 __invoke,让这个类变成可以调用的。然而,由于其思想是将 MVC 模式应用到 HTTP REST API 上下文,Action(控制器)名称会被映射为 HTTP 请求方法,因此我们将得到名为 Get、Post、Put、Delete...的 Action,清楚地表明了每个 HTTP 请求类型调用的控制器。作为一种组织形式,一个资源的所有 Action 应该被一起放以该资源命名的文件夹下。
与 RMR 的差异
有些人认为 ADR 与 RAR 是同样的模式,只是细节有所调整, 然而并非如此:
-
“Resource==Domain”
RMR 中的 Resource 并非 Domain,而是领域对象,是领域实体,但 ADR 中的 Domain 与全部领域对象有关,所有的实体和它们的关系作为一个整体; -
“Representation==Responder”
RMR 中的 Representation 是发回给客户端的响应,但 ADR 中的 Responder 是一个对象,它的职责是基于给定内容和给定模板构造响应。 -
“它和 RMR 一样与 HTTP 耦合在一起,很难创建非 HTTP 界面”
既然 ADR 将 Domain 看作是一个整体而不是一个实体,Action 也不在领域对象内部,那么 Action 只会要求领域对象执行一些业务逻辑。所以 Domain 没有与 UI 耦合,我们可以创建一个CLI 命令,使用领域对象执行一些任务。
ADR 模式专为 REST API 设计,是 MVC 在 HTTP 请求/响应范式上的最佳应用,因为它清晰地将 HTTP 请求和响应对应到了 Domain 请求和响应,同时仍然保持了 Domain 和展现层之间完全的解耦。
HTTP 请求方法(期望对资源进行的操作)被明确地连接到接收 HTTP 请求的代码,因为每个 HTTP 方法都直接映射到一个控制方法的名字。
这样做还有一个额外的好处,那就是产生了清晰、明确和可预测的代码组织结构,而不是具有大量操作的控制器,这些操作通常是不相关的,命名糟糕,不可预测,而且常常执行非常类似的操作。换句话说,它避免了混乱的意大利面式的控制器和操作。
还有,它也非常好地解耦了交互自身的代码(调用领域)和理解交互结果(领域响应)并转换给客户端的代码。
因为TA是专为REST API 而设计的,那"如何将其完全应用于 HTML 界面"呢?
我们可以模拟一些 REST API 没有的额外的 HTTP 方法,专门处理 HTML 请求。例如,我们可以在一个 REST API 中使用 PUT 或 POST 来创建和/或更新资源,而这就是该资源所需的全部方法,可是对于 HTML 界面来说,我们在发送 PUT 或 POST 之前需要一个表单,但是没有 HTTP 方法专门供客户端请求创建资源或编辑的表单。我们可以使用一个带有 create 或 edit 标头的 GET 请求来模拟它,前端控制器可以解析该请求并转发给对应的名为 Create 或 Edit 的操作,然后这些操作将回复对应的 HTML 表单。
对比MVC/ADR
![](https://img.haomeiwen.com/i2622710/8d359cf8ffd0dbc6.png)
- ADR明确了行为顺序,禁止了Model对View的直接修改。而转为由Action代理给Responser
- Action在拿到数据以后直接把template和raw数据转给responder,不做数据展现加工,这部分由独立与action一一对应的responder完成
- Controller有可以包含多个行为,action只能有一个,比如BlogAdd,BlogDel,BlogMod,这就必须是三个action,三个responder,而不是传统定一个BlogController,路由到三个方法来解决。
- 关于数据组织,明确Action和Domain的交互,与Controller与Model的交互一直。那就意味着,Action也可以和多个Domain进行交互,以获取raw data
网友评论