质疑 DTO 与 Restful 服务的使用并从更新中提取行为
在 DDD 领域,我喜欢避免 getter 和 setter 来完全封装组件的想法,因此唯一允许的交互是通过行为构建的交互。将其与事件溯源相结合,我可以获得有关已执行操作以及何时执行组件的详细历史记录。
我一直在考虑的一件事是,当我想要创建一个通往底层服务的静态网关时。出于示例的目的,假设我有一个具有以下方法的 Task 对象,
ChangeDueDate(DateTime date)
ChangeDescription(string description)
AddTags(params string[ ] Tags)
Complete()
现在显然我将在该对象内包含实例变量,用于控制调用相关方法时将触发的状态和事件。
回到 REST 服务,我认为有 3 个选项:
- 创建 RPC 样式的 url,例如
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 的角度以及性能的角度来看,第二种选择将是一次非常好的体验,因为我可以将行为的变化合并到单个请求中,同时仍然跟踪变化。
第一个选项肯定是显式的,但如果需要调用许多行为,则会导致多个请求。
第三个选项听起来不错,但我意识到它需要一些思考来提供一个干净的实现,可以考虑不同的属性类型、嵌套等...
感谢您的帮助,通过分析瘫痪真的让我低下了头。只是想要一些关于其他人认为最好的选择方式的建议,或者我是否错过了一个技巧。
In the realm of DDD I like the idea of avoiding getters and setters to fully encapsulate a component, so the only interaction that is allowed is the interaction which has been built through behavior. Combining this with Event Sourcing I can get a nice history of what has been actioned and when to a component.
One thing I have been thinking about is when I want to create, for example, a restful gateway to the underlying service. For the purposes of example, lets say I have a Task object with the following methods,
ChangeDueDate(DateTime date)
ChangeDescription(string description)
AddTags(params string[] tags)
Complete()
Now obviously I will have instance variables inside this object for controlling state and events which will be fired when the relevant methods are invoked.
Going back to the REST Service, the way I see it there are 3 options:
- Make RPC style urls e.g.
http://127.0.0.1/api/tasks/{taskid}/changeduedate
- Allow for many commands to be sent to a single endpoint e.g.:
- URL:
http://127.0.0.1/api/tasks/{taskid}/commands
- This will accept a list of commands so I could send the following in the same request:
ChangeDueDate
commandChangeDescription
command
- URL:
- Make a truly RESTful verb available and I create domain logic to extract changes from a DTO and in turn translate into the relevant events required e.g.:
- URL:
http://127.0.0.1/api/tasks/{taskid}
- I would use the PUT verb to send a DTO representation of a task
- Once received I may give the DTO to the actual Task Domain Object through a method maybe called, UpdateStateFromDto
- This would then analyse the dto and compare the matching properties to its fields to find differences and could have the relevant event which needs to be fired when it finds a difference with a particular property is found.
- URL:
Looking at this now, I feel that the second option looks to be the best but I am wondering what other peoples thoughts on this are, if there is a known true restful way of dealing with this kind of problem. I know with the second option that it would be a really nice experience from a TDD point of view and also from a performance point of view as I could combine changes in behavior into a single request whilst still tracking change.
The first option would definitely be explicit but would result in more than 1 request if many behaviors needed to be invoked.
The third option does not sound bad to be but I realise it would require some thougth to come with a clean implementation that could account for different property types, nesting etc...
Thanks for your help in this, really bending my head through analysis paralysis. Would just like some advice on what others think would be the best way from the options or whether I am missing a trick.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我会说选项 1。如果您希望您的服务是 RESTful,那么选项 2 不是一个选项,您将通过隧道传输请求。
POST /api/tasks/{taskid}/changeduedate
很容易实现,但您也可以执行PUT /api/tasks/{taskid}/duedate
。如果您想将多个过程分组为一个,您可以创建控制器资源,例如
POST /api/tasks/{taskid}/doThisAndThat
,我会根据客户端使用模式来执行此操作。您真的需要提供在一个请求中调用任意数量的“行为”的能力吗? (顺序重要吗?)
如果您想使用选项 3,我会使用
PATCH /api/tasks/{taskid}
,这样客户端不需要在请求中包含所有成员,仅那些需要改变的。I would say option 1. If you want your service to be RESTful then option 2 is not an option, you'd be tunneling requests.
POST /api/tasks/{taskid}/changeduedate
is easy to implement, but you can also doPUT /api/tasks/{taskid}/duedate
.You can create controller resources if you want to group several procedures into one, e.g.
POST /api/tasks/{taskid}/doThisAndThat
, I would do that based on client usage patterns.Do you really need to provide the ability to call any number of "behaviors" in one request? (does order matter?)
If you want to go with option 3 I would use
PATCH /api/tasks/{taskid}
, that way the client doesn't need to include all members in the request, only the ones that need to change.让我们从领域的角度定义一个术语:
操作=命令或查询
,例如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 操作将资源映射到实体。
URI 结构没有任何意义。我们可以谈论语义,但 REST 与 RPC 有很大不同。 它有一些非常具体的限制,您必须在做任何事情之前阅读这些限制。
这与您的第一个答案有相同的问题。您必须将操作映射到 HTTP 方法和 URI。它们无法在邮件正文中传播。
这是一个好的开始,但您不想直接在实体上应用 REST 操作。您需要一个接口来将域逻辑与 REST 服务解耦。该界面可以包含命令和查询。因此,REST 请求可以转换为域逻辑可以处理的命令和查询。
Let's define a term:
operation = command or query
from a domain perspective, for exampleChangeTaskDueDate(int taskId, DateTime date)
is an operation.By REST you can map operations to resource and method pairs. So calling an operation means applying a method on a resource. The resources are identified by URIs and are described by nouns, like task or date, etc... The methods are defined in the HTTP standard and are verbs, like get, post, put, etc... The URI structure does not really mean anything to a REST client, since the client is concerned with machine readable stuff, but for developers it makes easier to implement the router, the link generation, and you can use it to verify whether you bound URIs to resources and not to operations like RPC does.
So by our current example
ChangeTaskDueDate(int taskId, DateTime date)
the verb will bechange
and the nouns aretask, due-date
. So you can use the following solutions:PUT /api{/tasks,id}/due-date "2014-12-20 00:00:00"
or you can usePATCH /api{/tasks,id} {"dueDate": "2014-12-20 00:00:00"}
.the difference that patch is for partial updates and it is not necessary idempotent.
Now this was a very easy example, because it is plain CRUD. By non CRUD operations you have to find the proper verb and probably define a new resource. This is why you can map resources to entities only by CRUD operations.
The URI structure does not mean anything. We can talk about semantics, but REST is very different from RPC. It has some very specific constraints, which you have to read before doing anything.
This has the same problem as your first answer. You have to map operations to HTTP methods and URIs. They cannot travel in the message body.
This is a good beginning, but you don't want to apply REST operations on your entities directly. You need an interface to decouple the domain logic from the REST service. That interface can consist of commands and queries. So REST requests can be transformed into those commands and queries which can be handled by the domain logic.