Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CQRS - Should a Command try to create a "complex" master-detail entity?

I've been reading Greg Young and Udi Dahan's thoughts on Command Query Responsibilty Separation and a lot of what I read strikes a chord with me. My domain (we track vehicles which are doing deliveries) has the concept of a Route which contains one or more Stops. I need my customers to be able to set these up in our system by calling a webservice, and then be able to retrieve information about a Route and how the vehicle is progressing.

In the past I would have "cut-down" DTO classes which closely resemble my domain classes, and the customer would create a RouteDto with an array of StopDto(s), and call our CreateRoute webmethod, passing in the RouteDto. When they query our system by calling the GetRouteDetails method, I would return exactly the same objects to them. One of the appealing aspects of CQRS is that the RouteDto might have all manner of properties that the customer wants to query, but have no business setting when they create a Route. So I create a separate CreateRouteRequest class which is passed in when calling the CreateRoute "command", and a Route DTO class which gets returned as a query result.

class Route{
    string Reference;
    List<Stop> Stops;
}

But I need my customer to provide me with Route AND Stop details when they create a route. As I see it I could either...

Give my CreateRouteRequest class a Stops(s) property which is an array of "something" representing the data they need to provide about each stop - but what do I call this class? It's not a Stop as that's what I'm calling the list of DTO inside my Route DTO, but I don't like "CreateStopRequest". I also wonder if I'm stuck in a CRUD mindset here thinking in terms of master-detail information and asking the customer to think like that too.

class CreateRouteRequest{
    string Reference;
    ...
    List<CreateStopRequest> Stops;
}

or

They call CreateRoute, and then make a number of calls to an AddStopToRoute method. This feels a bit more "behavioural" but I'm going to lose the ability to treat creating a route including its stops as a single atomic command. If they create a Route and then try to add a Stop which fails due to some validation problem they're going to have a partially correct Route.

The fact that I can't come up with a good name for the list of "StopCreationData" objects I'd be working with in option 1, makes me wonder if there's something I'm missing.

like image 467
Simon Crabtree Avatar asked Jun 16 '10 15:06

Simon Crabtree


3 Answers

I realize this is a really old post, but I have been grappling with some similar patterns lately and feel the need to contribute to this thread. I think one thing that was causing the OPs feeling of disconnect was that they were shoehorning the domain terminology into their own operational language instead of conforming their design to the domain.

The tip off is in the use of "create" as a verb. "Create," I have found, is the same as "insert" in the dev's mind (think "CRUD"), and we often resort to that verb when we first start trying on DDD, because it seems less technical. The routes being "created" here already exist, though. They are merely being recorded in the system. Along the same lines, the stops on the route already exist, but are being recorded as well. A simple change in perception and wording, perhaps by using RecordRouteCommand with an optional accompanying collection of RecordStopOnRouteCommand, would probably have resolved the confusion a bit. Allowing the stop recording commands to be sent independently as well would provide more flexibility in construction and strengthen the API.

I also agree with Szymon about request vs. command. That wording, too, leads to thinking contrary to the cqrs approach. If there is one thing DDD has taught me, it is that the words we use on our projects are not just important, they are of paramount importance.

like image 180
theta-fish Avatar answered Sep 21 '22 02:09

theta-fish


I don't think you're missing anything.

class CreateRouteRequest{
    string Reference;
    ...
    List<CreateStopRequest> Stops;
}

Looks fine for me. The alternative of using AddStopToRoute is, in my opinion, not a good idea as it creates too 'chatty' interface to be efficiently invoked remotely.

like image 37
Szymon Pobiega Avatar answered Sep 22 '22 02:09

Szymon Pobiega


However, your use of a CreateRoute*Request* seems to indicate that you are using a Request/Response pattern.

If you are truly sending commands to the server, the server should not return a Response object/message. You can just have your service expose an ExecuteCommand method, and call that, passing your CreateRouteCommand.

Request/Response is not proper CQRS IMHO.

like image 27
Roy Dictus Avatar answered Sep 21 '22 02:09

Roy Dictus