CQRS is an evolutionary step in Domain-Driven Design (DDD). Ncqrs is an open-source implementation for.NET.
Links: CQRS Wikipedia's entry for Command-query separation. Udi Dahan's explanation of Command-Query Responsibility Segregation. Greg Young's video presentation CQRS and Event Sourcing - the Business Perspective. Links: Ncqrs For information on Ncqrs, see ncqrs.org. To try out the Ncqrs walkthrough tutorial, go here. |
The goal of this blog post is to compare Ncqrs with regular Domain-Driven Design (particularly when used with Remote Facade), and to examine and understand the processes described in the Ncqrs tutorial.
In regular Domain-Driven Design across a web service boundary, both inputs (commands) and outputs (queries) share the same RemoteFacade and Application Service Layer, as shown in Fig. 1.
Fig.1 Regular DDD with Remote Facade
CQRS
In Command-Query Responsibility Segregation Pattern, inputs (commands) and outputs (queries) are separated and treated very differently. As Greg Young points out in the video presentation (above), using the RemoteFacade/DDD architecture for simple queries leads to an immense amount of busy work (mapping from the domain to DTOs, etc), when the real purpose of the domain model is to implement behavior/business logic--which only occurs based on input (commands).
Therefore:
CQRS in 50 words or less
1) Keep DDD and the domain model, but call it only from commands.
2) Add a new feature: TRACK each command as event history
3) For queries, SKIP the domain model. Do queries quick, dirty and denormalized.
How does Ncqrs implement this approach? Let's walk through the journey that a command follows, based on their tutorial. I've created the following diagram as a guide:
- In your backend behind the RemoteFacade, create a custom command class such as PostNewTweetCommand (which inherits from Ncqrs.Commanding.CommandBase).
- For any input values you wish to pass to the domain model, create properties in the custom command class. For example:
public string Message { get; set; }
public string Who { get; set; }
- In your WCF web service, create a method which accepts PostNewTweetCommand as input parameter.
- In your client, create the WCF proxy.
- In your client, create an action which instantiates PostNewTweetCommand, sets its property values, and passes it to the web service method.
- In your WCF web service method, get an ICommandService instance (from Ncqrs.Commanding.ServiceModel) and use it to execute the incoming PostNewTweetCommand input parameter.
- Create a PostNewTweetCommandExecutor class (which inherits from CommandExecutorBase<PostNewTweetCommand>) that--by convention--listens for executed instances of PostNewTweetCommand
- Have the command executor execute the incoming command, in the context of an IUnitOfWork, against a domain model class (typically an aggregate root)
- In the domain model class (eg. Tweet.cs), every public action (such as instantiation, or a method call) creates a domain event to process the incoming command, map over its input values, and then call ApplyEvent().
- In this case, the contextual IUnitOfWork writes the event (TweetPostedEvent) to the event store.
- As part of setting up the WCF service, three services have been bootstrapped in service of CQRS goals:
- ICommandService - already mentioned, this processes the incoming commands, and uses convention to call the correct command executor
- IEventStore - this processes each event transaction and writes it out to a separate data store (in the tutorial example, a SQL Server database)
- IEventBus - listens for new events, and for each one, makes a call out to the final piece of the ncqrs puzzle (described next)
- The event bus alerts classes (again, by convention) which inherit from IDenormalizer
where T is the particular event that has been saved. - The implementing class (in this case, TweetIndexItemDenormalizer is so named based on a new, separate database whose purpose is to store, simple, denormalized, read data.
- The TweetIndexItemDenormalizer class implements a Handle method, which takes the TweetPostedEvent as input param, and write the values stored in that event to the denormalized data table.
- Finally, the client can now (very simply) access data from this denormalized table (eg. as a simple Linq to SQL call to get a list of matching values.)
No comments:
Post a Comment