问题
If using CQRS and creating an entity, and the values of some of its properties are generated part of the its constructor (e.g. a default active
value for the status
property, or the current datetime for createdAt
), how do you include that as part of your response if your command handlers can’t return values?
回答1:
You would need to create guid before creating an entity, then use this guid to query it. This way your command handlers always return void.
[HttpPost]
public ActionResult Add(string name)
{
Guid guid = Guid.NewGuid();
_bus.Send(new CreateInventoryItem(guid, name));
return RedirectToAction("Item", new { id = guid});
}
public ActionResult Item(Guid id)
{
ViewData.Model = _readmodel.GetInventoryItemDetailsByGuid(id);
return View();
}
回答2:
Strictly speaking, I don't believe CQRS has a precise hard and fast rule about command handlers not returning values. Greg Young even mentions Martin Fowler's stack.pop()
anecdote as a valid counter-example to the rule.
CQS - Command Query Separation, upon which CQRS is based - by Bertrand Meyer does have that rule, but it takes place in a different context and has exceptions, one of which can be interesting for the question at hand.
CQS reasons about objects and the kinds of instructions the routine (execution context) can give them. When issuing a command, there's no need for a return value because the routine already has a reference to the object and can query it whenever it likes as a follow-up to the command.
Still, an important distinction made by Meyer in CQS is the one between a command sent to a known existing object and an instruction that creates an object and returns it.
Functions that create objects
A technical point needs to be clarified before we examine further consequences of the Command-Query Separation principle: should we treat object creation as a side effect?
The answer is yes, as we have seen, if the target of the creation is an attribute a: in this case, the instruction !! a changes the value of an object’s field. The answer is no if the target is a local entity of the routine. But what if the target is the result of the function itself, as in !! Result or the more general form !! Result.make (...)?
Such a creation instruction need not be considered a side effect. It does not change any existing object and so does not endanger referential transparency (at least if we assume that there is enough memory to allocate all the objects we need). From a mathematical perspective we may pretend that all of the objects of interest, for all times past, present and future, are already inscribed in the Great Book of Objects; a creation instruction is just a way to obtain one of them, but it does not by itself change anything in the environment. It is common, and legitimate, for a function to create, initialize and return such an object.
(in Object Oriented Software Construction, p.754)
In other places in the book, Meyer defines this kind of functions as creator functions.
As CQRS is an extension of CQS and maintains the viewpoint that [Commands and Queries] should be pure, I would tend to say that exceptions that hold for CQS are also true in CQRS.
In addition, one of the main differences between CQS and CQRS is the reification of the Command and Query into objects of their own. In CQRS, there's an additional level of indirection, the "routine" doesn't have a direct reference to the domain object. Object lookup and modification are delegated to the command handler. It weakens, IMO, one of the original reasons that made the "Commands return nothing" precept possible, because the context now can't check the outcome of the operation on its own - it's basically left high and dry until some other object lets it know about the result.
回答3:
Some ideas:
- Let your command handlers return values. This is the simplest option - just return what was created inside the entity. There is some disagreement about whether this is 'allowed' in CQRS though.
- The preferred approach is to create your defaults (i.e.id) and pass them into your command - For example, https://github.com/gregoryyoung/m-r/blob/master/CQRSGui/Controllers/HomeController.cs in the Add method, a Guid is created and passed in to the CreateInventoryItem command - this could be returned in the response. This could get quite ugly if you have lots of things to pass in though.
- If you can't do 1 or 2, you could try having some async way of handling this, but you haven't said what your use case is so it's difficult to advise. If you're using some sort of socket technology you could do sync-over-async style where you return immediately, then push some value back to the client once the entity has been created. You can also have some sort of workflow where you accept the command then poll / query on the thing being created
回答4:
According to my understanding of CQRS, you cannot query the aggregate and the command handler could not return any value. The only permitted way of interogating the aggregate is by listening to the raised events. That you could do by simply querying the read model, if the changes are reflected synchronously from the events to the read model.
In the case the changes to the read model are asynchronous things get complicated but solutions exists.
Note: the "command handler" in my answer is the method on the Aggregate, not some Application layer service.
来源:https://stackoverflow.com/questions/41868157/in-cqrs-how-do-you-build-the-response-when-creating-an-entity