6
votes

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.

3

3 Answers

6
votes

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.

5
votes

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.

2
votes

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.