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开发者_如何学Python 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.
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 do PUT /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.
Let's define a term: operation = command or query
from a domain perspective, for example ChangeTaskDueDate(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 be change
and the nouns are task, 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.
Going back to the REST Service, the way I see it there are 3 options:
- Make RPC style urls e.g. http://example.com/api/tasks/{taskid}/changeduedate
- Allow for many commands to be sent to a single endpoint e.g.:
- URL: http://example.com/api/tasks/{taskid}/commands
- This will accept a list of commands so I could send the following in the same request:
- ChangeDueDate command
- ChangeDescription command
- 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://example.com/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.
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.
精彩评论