质疑DTO的使用与休息的服务并从更新中提取行为
本文关键字:更新 提取 DTO 质疑 服务 | 更新日期: 2023-09-27 18:05:32
在DDD领域,我喜欢避免getter和setters完全封装组件的想法,因此唯一允许的交互是通过行为构建的交互。 将其与事件溯源相结合,我可以获得有关已操作的内容以及何时对组件执行的良好历史记录。
我一直在考虑的一件事是,例如,当我想要创建一个通往底层服务的 restful 网关时。 出于示例的目的,假设我有一个具有以下方法的 Task 对象,
-
ChangeDueDate(DateTime date)
-
ChangeDescription(string description)
-
AddTags(params string[] tags)
-
Complete()
现在显然,我将在此对象中包含实例变量,用于控制调用相关方法时将触发的状态和事件。
回到 REST 服务,在我看来有 3 个选项:
- 制作 RPC 样式的网址,例如
http://127.0.0.1/api/tasks/{taskid}/changeduedate
- 允许将许多命令发送到单个端点,例如:
- 网址:
http://127.0.0.1/api/tasks/{taskid}/commands
- 这将接受命令列表,以便我可以在同一请求中发送以下内容:
-
ChangeDueDate
命令 -
ChangeDescription
命令
-
- 网址:
- 使一个真正的 RESTful 动词可用,我创建域逻辑以从 DTO 中提取更改,进而转换为所需的相关事件,例如:
- 网址:
http://127.0.0.1/api/tasks/{taskid}
- 我会使用 PUT 动词来发送任务的 DTO 表示
- 收到后,我可以通过一种可能称为 UpdateStateFromDto 的方法将 DTO 提供给实际的任务域对象。
- 然后,这将分析 dto 并将匹配属性与其字段进行比较以查找差异,并且可能会在发现与特定属性的差异时触发相关事件。
- 网址:
现在来看这个,我觉得第二种选择似乎是最好的,但我想知道其他人对此有何看法,是否有一种已知的真正宁静的方式来处理这种问题。 我知道使用第二个选项,从TDD的角度来看,从性能的角度来看,这将是一个非常好的体验,因为我可以将行为更改合并到单个请求中,同时仍然跟踪更改。
第一个选项肯定是显式的,但如果需要调用许多行为,则会导致 1 个以上的请求。
第三个选项听起来还不错,但我意识到它需要一些干净的实现,可以解释不同的属性类型、嵌套等......
感谢您在这方面的帮助,真的在分析瘫痪中弯曲了我的头。 只是想得到一些建议,了解其他人认为选项中的最佳方法是什么,或者我是否错过了一个技巧。
我会说选项 1。如果您希望您的服务是 RESTful 的,那么选项 2 不是一个选项,您将通过隧道请求。
POST /api/tasks/{taskid}/changeduedate
很容易实现,但您也可以PUT /api/tasks/{taskid}/duedate
.
将多个过程组合为一个,则可以创建控制器资源,例如 POST /api/tasks/{taskid}/doThisAndThat
,我会根据客户端使用模式来做到这一点。
你真的需要提供一个在一个请求中调用任意数量的"行为"的能力吗?(顺序重要吗?
如果您想使用选项 3,我会使用 PATCH /api/tasks/{taskid}
,这样客户端就不需要在请求中包含所有成员,只需要需要更改的成员。
让我们定义一个术语:operation = command or query
从域的角度来看,例如ChangeTaskDueDate(int taskId, DateTime date)
是一个操作。
通过 REST,您可以将操作映射到资源和方法对。因此,调用操作意味着对资源应用方法。资源由 URI 标识,并由名词描述,如任务或日期等......这些方法是在HTTP标准中定义的,并且是动词,如get,post,put等。URI 结构对 REST 客户端来说并不真正意味着什么,因为客户端关注的是机器可读的东西,但对于开发人员来说,它更容易实现路由器、链接生成,您可以使用它来验证是否将 URI 绑定到资源而不是像 RPC 那样绑定到操作。
因此,通过我们当前的示例ChangeTaskDueDate(int taskId, DateTime date)
动词将被change
,名词将被task, due-date
。因此,您可以使用以下解决方案:
-
PUT /api{/tasks,id}/due-date "2014-12-20 00:00:00"
或者您可以使用 -
PATCH /api{/tasks,id} {"dueDate": "2014-12-20 00:00:00"}
.
补丁的区别是用于部分更新,它不是必需的幂等的。
这是一个非常简单的例子,因为它是普通的CRUD。通过非 CRUD 操作,您必须找到正确的动词,并可能定义一个新资源。这就是只能通过 CRUD 操作将资源映射到实体的原因。
回到 REST 服务,在我看来有 3 个选项:
- 制作 RPC 样式 url,例如 http://example.com/api/tasks/{taskid}/changeduedate
- 允许将许多命令发送到单个端点,例如:
- URL: http://example.com/api/tasks/{taskid}/commands
- 这将接受命令列表,以便我可以在同一请求中发送以下内容:
- 更改到期日期命令
- "更改说明"命令
- 使一个真正宁静的动词可用,我创建域逻辑以从 dto 中提取更改,进而翻译成相关的 所需事件,例如:
- 网址: http://example.com/api/tasks/{任务标识符}
- 我会使用 PUT 动词来发送任务的 DTO 表示
- 收到后,我可以通过一种可能称为 UpdateStateFromDto 的方法将 DTO 提供给实际的任务域对象。
- 然后,这将分析 dto 并将匹配属性与其字段进行比较以查找差异,并且可以具有 发现与 找到特定属性。
-
URI 结构没有任何意义。我们可以谈论语义,但REST与RPC有很大不同。它有一些非常具体的约束,您必须在执行任何操作之前阅读这些约束。
-
这与您的第一个答案有相同的问题。您必须将操作映射到 HTTP 方法和 URI。它们不能在邮件正文中传输。
-
这是一个良好的开端,但您不希望直接在实体上应用 REST 操作。您需要一个接口来将域逻辑与 REST 服务分离。该接口可以包含命令和查询。因此,REST 请求可以转换为那些可以由域逻辑处理的命令和查询。