质疑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 个选项:

  1. 制作 RPC 样式的网址,例如 http://127.0.0.1/api/tasks/{taskid}/changeduedate
  2. 允许将许多命令发送到单个端点,例如:
    • 网址: http://127.0.0.1/api/tasks/{taskid}/commands
    • 这将接受命令列表,以便我可以在同一请求中发送以下内容:
      • ChangeDueDate命令
      • ChangeDescription命令
  3. 使一个真正的 RESTful 动词可用,我创建域逻辑以从 DTO 中提取更改,进而转换为所需的相关事件,例如:
    • 网址: http://127.0.0.1/api/tasks/{taskid}
    • 我会使用 PUT 动词来发送任务的 DTO 表示
    • 收到后,我可以通过一种可能称为 UpdateStateFromDto 的方法将 DTO 提供给实际的任务域对象。
    • 然后,这将分析 dto 并将匹配属性与其字段进行比较以查找差异,并且可能会在发现与特定属性的差异时触发相关事件。

现在来看这个,我觉得第二种选择似乎是最好的,但我想知道其他人对此有何看法,是否有一种已知的真正宁静的方式来处理这种问题。 我知道使用第二个选项,从TDD的角度来看,从性能的角度来看,这将是一个非常好的体验,因为我可以将行为更改合并到单个请求中,同时仍然跟踪更改。

第一个选项肯定是显式的,但如果需要调用许多行为,则会导致 1 个以上的请求。

第三个选项听起来还不错,但我意识到它需要一些干净的实现,可以解释不同的属性类型、嵌套等......

感谢您在这方面的帮助,真的在分析瘫痪中弯曲了我的头。 只是想得到一些建议,了解其他人认为选项中的最佳方法是什么,或者我是否错过了一个技巧。

质疑DTO的使用与休息的服务并从更新中提取行为

我会说选项 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 个选项:

  1. 制作 RPC 样式 url,例如 http://example.com/api/tasks/{taskid}/changeduedate
  2. 允许将许多命令发送到单个端点,例如:
    • URL: http://example.com/api/tasks/{taskid}/commands
    • 这将接受命令列表,以便我可以在同一请求中发送以下内容:
      • 更改到期日期命令
      • "更改说明"命令
  3. 使一个真正宁静的动词可用,我创建域逻辑以从 dto 中提取更改,进而翻译成相关的 所需事件,例如:
    • 网址: http://example.com/api/tasks/{任务标识符}
    • 我会使用 PUT 动词来发送任务的 DTO 表示
    • 收到后,我可以通过一种可能称为 UpdateStateFromDto 的方法将 DTO 提供给实际的任务域对象。
    • 然后,这将分析 dto 并将匹配属性与其字段进行比较以查找差异,并且可以具有 发现与 找到特定属性。
  1. URI 结构没有任何意义。我们可以谈论语义,但REST与RPC有很大不同。它有一些非常具体的约束,您必须在执行任何操作之前阅读这些约束。

  2. 这与您的第一个答案有相同的问题。您必须将操作映射到 HTTP 方法和 URI。它们不能在邮件正文中传输。

  3. 这是一个良好的开端,但您不希望直接在实体上应用 REST 操作。您需要一个接口来将域逻辑与 REST 服务分离。该接口可以包含命令和查询。因此,REST 请求可以转换为那些可以由域逻辑处理的命令和查询。