Pattern to enable a service to be highly performant and horizontally scalable by separating its command interface/implementation from its query interface/implementation so that commands and queries can scale independently of each other.
3. Command-Query Separation Architecture Pattern
1 4/2/2020
1 Intent
Enable a service to be highly performant and horizontally scalable by separating its command interface/implementation
from its query interface/implementation so that commands and queries can scale independently of each other.
2 Also Known As
Command Query Responsibility Segregation
3 Motivation
Suppose you are implementing a service that manages a set of entities—the prices for products in an e-commerce system
for example. This service has a potentially large number of clients that will query the service to retrieve selected entities.
These “query” clients may also be distributed across multiple geographic regions. In contrast to the query use-case, the
service has a small number of clients that create, update, and delete the entities. These “command” clients are usually
concentrated in a single location. This service is critical to your business and must process requests and provide replies
consistently and quickly no matter the volume of requests at any given time and no matter the geographic region.
A simple seeming solution is to implement all operations: create, update, delete, and get in a single service that reads and
writes to a persistent datastore such as a relational database or a no-sql datastore. Instances of the service are then
horizontally scaled as needed. This approach is well suited to domains where there are a relatively small and evenly
distributed number of command clients and query clients, but the solution does not scale to a very large number of query
clients from multiple geographic regions. The single datastore becomes a bottleneck that cannot handle the load placed on
it by the vast number of query requests. Vertically scaling the datastore will enable some increased capacity but the query
request load will eventually overwhelm the datastore as more query clients make use of the service. In addition to query
requests slowing down dramatically as the number of requests increase, command requests will often be queued up waiting
to be processed which will cause the system’s entities to be out of date as well.
An alternative approach that sidesteps the bottleneck of a single datastore being overwhelmed by query requests is to
separate the service into multiple parts that handle different aspects of the supported functionality. The single service is
separated into:
• a Command Host – that provides a service for creating, updating, and deleting entities (i.e. create price, update price,
delete price) and emits events for each change in entity state (i.e. price created event, price updated event, price deleted
event) that contain a representation of the changed entity state
• an Update Host – that consumes entity events and writes the entity changes to a high speed datastore in a format that
is ready to be consumed for queries
• and a Query Host – that provides a service for querying entity data from the same high speed datastore that is updated
by the Update Host.
These separate host applications can be scaled independently of each other as needed and the Update and Query Hosts and
their shared high speed datastore can have multiple instances distributed across different geographic regions to enable
horizontal scaling for queries that is only limited by your operations budget.
A concrete example of this approach can be applied to our price service which is implemented as three kinds of applications:
• Price.CommandHost – an application that hosts the service functionality for creating, updating, and deleting product
prices and emits price events on each price state change (e.g. price created, price updated, and price deleted)
• Price.UpdateHost – an application that hosts the functionality of consuming price events and writing these state
changes to a high speed datastore in an optimized data format that does not require extensive (or any) transformation
when the data is retrieved when processing a query request
• Price.QueryHost – an application that hosts the service functionality for retrieving one or more prices (in an optimized
data format) from the high speed datastore that has been updated by Price.UpdateHost
4. Command-Query Separation Architecture Pattern
2 4/2/2020
A message topic:
• Price.Events – a message topic to which price events are sent by Price.CommandHost and from which price events are
received by Price.UpdateHosts
And two kinds of datastores:
• Price.CommandDatastore – a datastore that contains price entities in a format that is easy to manage price creation,
price updates, and price deletion
• Price.QueryDatastore – a high speed datastore that contains price (data) in a format that requires little or no
transformations when returned as the replies to query requests
The following diagram illustrates the application level components described above and their primary relationships.
4 Applicability
Use the Command-Query Separation pattern when:
• You have a service that has a small number of clients that make command requests (create, update, delete) and a very
large number of clients—possibly in different geographical regions—that make query requests.
• You must horizontally scale queries to meet internet level loads (i.e. hundreds of thousands of clients or greater, each
making multiple query requests).
• Query requests must execute consistently and quickly (in a specified time interval) no matter the overall number of
requests at any single point in time.
• You have a service that is tolerant of eventual consistency [1].
- Eventual consistency is a characteristic of distributed computing for achieving high availability that informally
guarantees that, if no new updates are made to a given data item, eventually all accesses to that item will return the
last updated value.
- This means that there is an acceptable window—the shorter the better—where a query request for specific entities
may return a value that is inconsistent with the value set by the latest command request on those entities. Ideally,
only a very small percentage of requests will fall into this inconsistency window.
Do not use the Command-Query Separation pattern when:
• You have a service that has strict transactional requirements (i.e. must conform to ACID constraints: atomic, consistent,
isolated, durable) [2]
5. Command-Query Separation Architecture Pattern
3 4/2/2020
5 Application Level
5.1 Structure
5.2 Participants
Participant Responsibilities
Entity.CommandHost (Price.CommandHost) • Exposes the EntityCommandService interface that provides create,
update, and delete operations
• Hosts the EntityCommandService implementation
• Sends EntityEvents to Entity.Events topic
• Maintains Entity state by creating, updating, and deleting Entities in
the Entity.CommandDatastore
Entity.UpdateHost (Price.UpdateHost) • Receives EntityEvents from Entity.Events topic
• Hosts the EntityEventProcessor that processes incoming
EntityEvents by creating, updating, and deleting EntityData in the
EntityData.QueryDatastore
Entity.QueryHost (Price.QueryHost) • Exposes the EntityQueryService interface that provides get operations
• Hosts the EntityQueryService implementation that retrieves
EntityData from the EntityData.QueryDatastore
Entity.Events (Price.Events) • Publish-subscribe channel for EntityEvents
Entity.CommandDatastore (Price.CommandDatastore) • Storage for Entities that are created and updated by
Entity.CommandHost
• Contains Entities in a persistent format
Entity.QueryDatastore (Price.QueryDatastore) • High speed storage for EntityData that are created and updated by
Entity.UpdateHost and retrieved by Entity.QueryHost
• Contains EntityData in a format optimized for retrieval and return to
clients
6. Command-Query Separation Architecture Pattern
4 4/2/2020
5.3 Collaborations
The Command-Query Separation pattern has the following collaborations:
• Creating Entities
- A command-client sends a CreateEntityRequest to the Entity.CommandHost through the
EntityCommandService’s createEntity(request) API
- The Entity.CommandHost processes the request and inserts the newly created Entity into the
Entity.CommandDatastore
- The Entity.CommandHost creates an EntityEvent (of event type CREATED and containing an EntityData
transfer object mapped from the source Entity) and sends it to the Entity.Events topic
- The Entity.UpdateHost receive the EntityEvent containing the created EntityData transfer object and inserts it
into the Entity.QueryDatastore
• Updating Entities
- A command-client sends an UpdateEntityRequest to the Entity.CommandHost through the
EntityCommandService’s updateEntity(request) API
- The Entity.CommandHost processes the request and updates the modified Entity in the
Entity.CommandDatastore
- The Entity.CommandHost creates an EntityEvent (of event type Updated and containing an EntityData transfer
7. Command-Query Separation Architecture Pattern
5 4/2/2020
object mapped from the source Entity) and sends it to the Entity.Events topic
- The Entity.UpdateHost receive the EntityEvent containing the modified EntityData transfer object and updates
it in the Entity.QueryDatastore
• Getting Entities
- A query-client sends a GetEntityRequest or GetEntitiesRequest to the Entity.QueryHost through the
EntityQueryService’s getEntity(request) or getEntities(request) APIs.
- The Entity.QueryHost processes the request by retrieving one or more EntityData objects from the
Entity.QueryDatastore and returning them to the query-client
• Deleting Entities
- A command-client sends a DeleteEntityRequest to the Entity.CommandHost through the
EntityCommandService’s deleteEntity(request) API
- The Entity.CommandHost processes the request and deletes the specified Entity from the
Entity.CommandDatastore
- The Entity.CommandHost creates an EntityEvent (of event type DELETED and containing an EntityData
transfer object mapped from the source Entity) and sends it to the Entity.Events topic
- The Entity.UpdateHost receive the EntityEvent containing the deleted EntityData transfer object and deletes its
instance from the Entity.QueryDatastore
6 Component Level
Applications are composed by linking in components providing various elements of functionality:
• Entity.CommandHost application links in the following components:
- Entity.CommandService
- Entity.CommandServer
- Entity.Transfer
• Entity.UpdateHost application links in the following components:
- Entity.CommandService
- Entity.CommandClient
- Entity.QueryServer
- Entity.Transfer
• Entity.QueryHost application links in the following components:
- Entity.QueryService
- Entity.QueryServer
- Entity.Transfer
8. Command-Query Separation Architecture Pattern
6 4/2/2020
6.1 Structure
6.2 Participants
Participant Responsibilities
Entity.CommandService (Price.CommandService) • Contains modules providing service interfaces for Entity-related events and
command type requests and replies (create, update, delete)
Entity.CommandClient (Price.CommandClient) • Contains modules that implement the service interfaces and abstract classes
declared in Entity.CommandService as client-side proxies [3] that can be
used to the call the services implemented in Entity.CommandServer or to
receive events emitted from the Entity.CommandHost application
Entity.CommandServer (Price.CommandServer) • Contains modules that implement the interfaces declared in
Entity.CommandService with the necessary business logic and other
mechanisms such as internal application entities, repositories, and domain
events to fully realize the Entity.CommandService microservice
Entity.QueryService (Price.QueryService) • Contains modules providing service interfaces for Entity-related query type
requests and replies (get)
Entity.QueryClient (Price.QueryClient) • Contains modules that implement the service interfaces and abstract classes
declared in Entity.QueryService as client-side proxies [3] that can be used
to the call the services implemented in Entity.QueryServer
Entity.QueryServer (Price.QueryServer) • Contains modules that implement the interfaces declared in
Entity.QueryService with the necessary business logic and other
mechanisms such as internal application entities, repositories, and domain
events to fully realize the Entity.QueryService microservice
Entity.Transfer (Price.Transfer) • Contains modules that implement the shared transfer objects [4] used to
represent entities in requests, replies, and events.
9. Command-Query Separation Architecture Pattern
7 4/2/2020
7 Module Level
The Entity.CommandHost, Entity.UpdateHost, and Entity.QueryHost applications defined by the Command-Query
Separation pattern are organized into layered architectures by applying the Layers architecture pattern [5] [6]. The specific
layers of these applications are:
• Client or Presentation Layer (implied) – consumer of the services and data provided by the Service Layer.
• Service Layer – provides the contracts that represent the services offered by the system.
• Application Layer – implements the contracts from the Service Layer by coordinating application activity of elements
from the Service Layer and the Domain Layer.
• Domain Layer – contains information about the domain. This is the heart of the business software. The state of
business objects is held here. Persistence of the business objects and possibly their state is delegated to the Platform
Layer.
• Platform Layer – Provides a supporting library for all the other layers.
- On server side – provides communication between layers, implements persistence for business objects, integration
mechanisms such as HTTP invokers that receiving incoming requests and invoker the appropriate methods on
service implementations.
- On client-side – implements the contract from the Service Layer as proxies for client side use and provides
supporting libraries for Presentation Layer
A layer is represented by a component or one or more modules in a component. These components and modules are
composed into larger networks of functionality. Modules are connected through interfaces to reduce coupling. Each module
exports a set of provided interfaces and imports a set of required interfaces. The benefit of the modular architecture is that
these components can be assembled into multiple configurations increasing software reuse and because the components are
highly decoupled it is much easier to test them independently.
7.1 Entity.CommandHost Modules
The following table lists the components of the Entity.CommandHost application and their constituent modules:
Component Module Responsibilities
Entity.CommandService RequestReply • Represents the “Service” layer of the Command Host application
• Provides the EntityCommandService interface
• Provides request/reply transfer objects [4] used in EntityCommandService
- CreateEntityRequest/CreateEntityReply
- UpdateEntityRequest/UpdateEntityReply
- DeleteEntityRequest/DeleteEntityReply
Event • Provides the EntityEventSender interface [7]
Entity.Transfer Shared • Provides the EntityData transfer object used for transferring the Entity’s data
Event • Provides the EntityEvent transfer object that represents entity events
Entity.CommandServer Application • Represents the “Application” layer of the Command Host application
• Provides the EntityCommandServiceImpl class
• Provides the Mapper interface [8]
Domain • Represents the “Domain” layer of the Command Host application
• Provides the Entity domain class [9]
10. Command-Query Separation Architecture Pattern
8 4/2/2020
• Provides the EntityRepository interface [10] [11]
• Provides the EntityRepositoryImpl class
Platform • Represents the “Platform” layer of the Command Host application
• Provides the EntityCommandServiceEndpoint class
• Provides the PlatformSpecificMapper class [8]
• Provides the PlatformSpecificEntityEventSender class [7]
7.1.1 Structure
7.1.2 Participants
«Module» Entity.CommandService.RequestReply
Participant Responsibilities
EntityCommandService (PriceCommandService) • Declares methods for creating, updating, and deleting Entities
CreateEntityRequest (CreatePriceRequest) • Transfer Object that represents a request to create an Entity
CreateEntityReply (CreatePriceReply) • Transfer Object that represents a reply to an associated request to create an
Entity
11. Command-Query Separation Architecture Pattern
9 4/2/2020
UpdateEntityRequest (UpdatePriceRequest) • Transfer Object that represents a request to update an Entity
UpdateEntityReply (UpdatePriceReply) • Transfer Object that represents a reply to an associated request to update an
Entity
DeleteEntityRequest (DeletePriceRequest) • Transfer Object that represents a request to delete an Entity
DeleteEntityReply (DeletePriceReply) • Transfer Object that represents a reply to an associated request to delete an
Entity
«Module» Entity.CommandService.Event
Participant Responsibilities
EntityEventSender (PriceEventSender) • Declares methods for sending EntityEvent transfer objects.
«Module» Entity.Transfer.Shared
Participant Responsibilities
EntityData (PriceData) • Transfer Object that contains the transferrable data of an Entity
«Module» Entity.Transfer.Event
Participant Responsibilities
EntityEvent (PriceEvent) • Transfer Object that represents the types of events that can occur on an Entity
• Contains the EntityData that was the source of the event
«Module» Entity.CommandServer.Application
Participant Responsibilities
EntityCommandServiceImpl
(PriceCommandServiceImpl)
• Server-side implementation of the PriceCommandService interface
• Implements the method for creating, updating, and deleting Entities
• Emits EntityEvents that correspond to each operation (create, update, delete) on Entities
Mapper (PriceMapper) • Declares an interface for mapping Entities and related objects to and from their Transfer
Object equivalents
«Module» Entity.CommandServer.Domain
Participant Responsibilities
12. Command-Query Separation Architecture Pattern
10 4/2/2020
Entity (Price) • Represents a specific domain concept
• Has a unique identity
• Transitions through multiple states
EntityRepository (PriceRepository) • Declares methods for inserting, updating, deleting, and retrieving Entities to/from a
storage mechanism
EntityRepositoryImpl
(PriceRepositoryImpl)
• Provides a storage specific implementation of the EntityRepository interface
«Module» Entity.CommandServer.Platform
Participant Responsibilities
EntityCommandServiceEndpoint
(PriceCommandServiceEndpoint)
• Platform specific (e.g. HTTP/REST) endpoint for the service functionality declared in the
EntityCommandService interface
• Exposes EntityCommandService operations to clients
PlatformSpecificMapper
(ModelMapper)
• Platform specific implementation of the Mapper interface
PlatformSpecificEntityEventSender
(KafkaAvroPriceEventSender)
• Platform specific implementation of the EntityEventSender interface declared in the
Entity.CommandService.Event module
7.1.3 Collaborations
• EntityCommandServiceEndpoint receives platform specific requests, converts them to an application usable format
such as Create | Update | Delete EntityRequest, and forwards them to an implementation of the
EntityCommandService interface for processing
• EntityCommandServiceImpl processes an incoming Create|Update|Delete EntityRequest by
- inserting, updating, or deleting the Entity object specified in the request to/from the Entity.CommandDatastore
through the EntityRepository
- using a Mapper to map the Entity object to an EntityData transfer object
- creating an appropriate EntityEvent transfer event object with a mapped EntityData as the event source
- sending the EntityEvent to the Entity.Events topic using the EntityEventSender
7.2 Entity.UpdateHost Modules
The following table lists the components of the Entity.UpdateHost application and their constituent modules:
Component Module Responsibilities
Entity.CommandService Event • Represents the “Service” layer of the Update Host application
• Provides the EntityEventReceiver interface
• Provides the EntityEventListener interface
Entity.Transfer Shared • Provides the EntityData transfer object used for transferring the Entity’s data
13. Command-Query Separation Architecture Pattern
11 4/2/2020
Event • Provides the EntityEvent transfer object that represents entity events
Entity.QueryServer Application • Represents the “Application” layer of the Update Host application
• Provides the EntityEventListenerImpl class
• Provides the Mapper interface
Domain • Represents the “Domain” layer of the Update Host application
• Provides the EntityDataRepository interface
Platform • Represents the “Platform” layer of the Update Host application
• Provides the PlatformSpecificEntityDataRepositoryImpl class
• Provides the PlatformSpecificEntityEventReceiver class
7.2.1 Structure
14. Command-Query Separation Architecture Pattern
12 4/2/2020
7.2.2 Participants
«Module» Entity.CommandService.Event
Participant Responsibilities
EntityEventReceiver (PriceEventReceiver) • Declares methods for starting/stopping receiving EntityEvent transfer objects
• Declares methods for attaching/detaching an EntityEventListener to listen for
EntityEvents
EntityEventListener (PriceEventListener) • Declares an onEvent(EntityEvent) method to be called on receiving an incoming
EntityEvent
• Declares an onException(Exception) method to be called on encountering an
exception while receiving an incoming EntityEvent
«Module» Entity.Transfer.Shared
Participant Responsibilities
EntityData (PriceData) • Transfer Object that contains the transferrable data of an Entity
«Module» Entity.Transfer.Event
Participant Responsibilities
EntityEvent (PriceEvent) • Transfer Object that represents the types of events that can occur on an Entity
• Contains the EntityData that was the source of the event
«Module» Entity.QueryServer.Application
Participant Responsibilities
EntityEventListenerImpl
(PriceEventListenerImpl)
• Implements EntityEventListener interface
• Creates, updates, and deletes EntityData objects using EntityDataRepository
«Module» Entity. QueryServer.Domain
Participant Responsibilities
EntityDataRepository (PriceDataRepository) • Declares methods for inserting, updating, deleting, and retrieving EntityData
objects to/from a storage mechanism
«Module» Entity. QueryServer.Platform
15. Command-Query Separation Architecture Pattern
13 4/2/2020
Participant Responsibilities
PlatformSpecificEntityDataRepositoryImpl
(RedisPriceDataRepository)
• High speed storage specific implementation of the EntityDataRepository
interface
PlatformSpecificEntityEventReceiver
(KafkaAvroPriceEventReceiver)
• Platform specific implementation of the EntityEventReceiver interface declared
in the Entity.CommandService.Event module
7.2.3 Collaborations
TBD
7.3 Entity.QueryHost Modules
The following table lists the components of the Entity.QueryHost application and their constituent modules:
Component Module Responsibilities
Entity.QueryService RequestReply • Represents the “Service” layer of the Query Host application
• Provides the EntityQueryService interface
• Provides request/reply transfer objects [4] used in EntityQueryService
- GetEntityRequest/GetEntityReply
- GetEntitiesRequest/GetEntitiesReply
Entity.Transfer Shared • Provides the EntityData transfer object used for transferring the Entity’s data
Entity.QueryServer Application • Represents the “Application” layer of the Query Host application
• Provides the EntityQueryServiceImpl class
Domain • Represents the “Domain” layer of the Query Host application
• Provides the EntityDataRepository interface
Platform • Represents the “Platform” layer of the Query Host application
• Provides the PlatformSpecificEntityDataRepositoryImpl class
• Provides the EntityQueryServiceEndpoint class
16. Command-Query Separation Architecture Pattern
14 4/2/2020
7.3.1 Structure
7.3.2 Participants
«Module» Entity.QueryService.RequestReply
Participant Responsibilities
EntityQueryService (PriceQueryService) • Declares methods for retrieving EntityData
GetEntityRequest (GetPriceRequest) • Transfer Object that represents a request to retrieve a single EntityData
GetEntityReply (GetPriceReply) • Transfer Object that represents a reply to an associated request to retrieve a
single EntityData
GetEntitiesRequest (GetPricesRequest) • Transfer Object that represents a request to retrieve multiple EntityData
17. Command-Query Separation Architecture Pattern
15 4/2/2020
GetEntitiesReply (GetPricesReply) • Transfer Object that represents a reply to an associated request to retrieve
multiple EntityData
«Module» Entity.Transfer.Shared
Participant Responsibilities
EntityData (PriceData) • Transfer Object that contains the transferrable data of an Entity
«Module» Entity.QueryServer.Application
Participant Responsibilities
EntityQueryServiceImpl
(PriceQueryServiceImpl)
• Server-side implementation of the EntityQueryService interface
• Implements the methods for retrieving EntityData
«Module» Entity. QueryServer.Domain
Participant Responsibilities
EntityDataRepository (PriceDataRepository) • Declares methods for inserting, updating, deleting, and retrieving EntityData
objects to/from a storage mechanism
«Module» Entity. QueryServer.Platform
Participant Responsibilities
PlatformSpecificEntityDataRepositoryImpl
(RedisPriceDataRepository)
• High speed storage specific implementation of the EntityDataRepository
interface
EntityQueryServiceEndpoint
(PriceQueryServiceEndpoint)
• Platform specific (e.g. HTTP/REST) endpoint for the service functionality
declared in the EntityQueryService interface
• Exposes EntityQueryService operations to clients
7.3.3 Collaborations
TBD
8 Consequences
Benefits
• High Performance Queries –
18. Command-Query Separation Architecture Pattern
16 4/2/2020
• Robust Horizontal Scalability –
• Geographical Distribution –
Liabilities
• Inconsistency Window –
• Introduces Complexity –
Trade-offs
• Eventual Consistency vs High Performance Queries & Horizontal Scalability –
9 Implementation
The following implementation issues should be considered when implementing the Command-Query Separation pattern:
• Multi-Component Workspace Organization – The development workspace for a system following the Command-
Query Separation pattern will be composed of multiple applications and multiple components. A suggested way of
organizing this workspace is to group the code and resources for applications, components, and tests into separate
directories that clearly indicate the purpose of each element:
- Applications
The executable artifacts that host the system’s functionality
Applications are constructed by importing the required Components and wiring them together using a
dependency injection mechanism
A system has one or more applications
- Components
The composable artifacts that provide interfaces and implementations that make up the system’s microservices
and other mechanisms
A typical microservice is implemented with three Components:
▪ A service component that defines the contract of the microservice
▪ A server component that implements the business logic of the microservice
▪ A client component that provides proxies for consuming the microservice
- Tests
The executable artifacts that verify the operational semantics of the elements defined in the system’s
Components
Tests should be separate from Components
Tests can be mapped to Components in many ways (the example below follows a one-to-one mapping)
The directory structure of such a system would look like the following:
Applications/
Price.CommandHost/
19. Command-Query Separation Architecture Pattern
17 4/2/2020
Price.UpdateHost/
Price.QueryHost/
Components/
Price.CommandService/
Price.CommandClient/
Price.CommandServer/
Price.QueryService/
Price.QueryClient/
Price.QueryServer/
Price.Transfer/
Tests/
Price.CommandServiceTest/
Price.CommandClientTest/
Price.CommandServerTest/
• • •
This structure can be realized in many popular development environments. If you are implementing on a JVM platform you can
use an IDE such as IntelliJ IDEA. In IDEA, workspaces are called projects and elements such as applications, components, and
tests are called modules. IDEA enables the creation of multi-module projects that correspond to the above suggested directory
structure (see figure below).
20. Command-Query Separation Architecture Pattern
18 4/2/2020
If you are implementing on the .Net platform you can use the Visual Studio IDE. In Visual Studio, workspaces are called
solutions and applications, components, and tests are called projects. Visual Studio enables the creation of multi-project solutions
that correspond to the suggested directory structure (see figure below).
21. Command-Query Separation Architecture Pattern
19 4/2/2020
Dependency management and build tools such as Maven also can be configured to support multi-component workspaces. Maven
follows the same naming convention as IntelliJ IDEA (or rather IDEA follows Maven’s naming convention). Workspaces are
called projects and applications, components, and tests are called modules. Below is a multi-module Maven POM file that
supports our suggested directory structure:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>nrhl</groupId>
<artifactId>Cms.Price</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>Components/Cms.Price.Transfer</module>
<module>Components/Cms.Price.CommandService</module>
<module>Components/Cms.Price.CommandServer</module>
<module>Components/Cms.Price.CommandClient</module>
<module>Components/Cms.Price.QueryService</module>
<module>Components/Cms.Price.QueryServer</module>
<module>Components/Cms.Price.QueryClient</module>
<module>Tests/Cms.Price.TransferTest</module>
<module>Tests/Cms.Price.CommandServiceTest</module>
<module>Tests/Cms.Price.CommandServerTest</module>
<module>Tests/Cms.Price.CommandClientTest</module>
22. Command-Query Separation Architecture Pattern
20 4/2/2020
<module>Tests/Cms.Price.QueryServiceTest</module>
<module>Tests/Cms.Price.QueryServerTest</module>
<module>Tests/Cms.Price.QueryClientTest</module>
<module>Applications/Cms.Price.CommandHost</module>
<module>Applications/Cms.Price.UpdateHost</module>
<module>Applications/Cms.Price.QueryHost</module>
</modules>
</project>
Maven is often used inside of IntelliJ IDEA to configure dependency management and builds.
• Applications, Components, Tests, and Artifacts – Applications, components, and tests are logical elements that contain
source code and other resources. They are logical in the sense that they specify the parts of the system. During the
system build process, the source code and resources contained in the application, component, and test elements gets
compiled and packaged into artifacts. Artifacts are files that contain executable or interpretable code and are deployable
elements in the sense that they can be deployed to an execution environment and run in some fashion.
Application are executable artifacts in that they can be executed directly as in the case with .exe files or indirectly as in
the case *.jar files that contain a main entry point (a class with a static main method).
Components are composable artifacts in that they contain executable code, but they must be linked into an Application
to be executed. This is because components lack a main entry point to start application execution. On the .Net platform,
components are compiled and packaged into *.dll files while on the JVM platform components are compiled into *.jar
files.
Tests are essentially components but contain test code instead of application code.
• Modules and Dependency Injection – Each component in the system is further organized into one or more modules
that can be composed into larger networks of functionality. These modules are connected through interfaces to reduce
coupling. Each module exports a set of provided interfaces and imports a set of required interfaces. The benefit of the
modular architecture is that these components can be assembled into multiple configurations increasing software reuse
and because the components are highly decoupled it is much easier to test them independently [12].
Modules are also an important concept in dependency injection where they are a mechanism to define a set of related
dependency bindings. The dependency injection modules used by an application are loaded into the dependency
injection frameworks injector or container to wire together objects based on the bindings defined in the modules.
These two ideas—modules as logical groupings of functionality and modules as related dependency injection
bindings—align very well and is one of the main reasons that module-based dependency injection is a powerful tool for
organizing software. As mentioned above, logically, modules export a set of provided interfaces and import a set of
required interfaces. The following Java example illustrates how a module with provided and required interfaces are
specified using the Guice dependency injection framework [13].
The PriceApplicationModule is a class that extends the Guice AbstractModule base class. All Guice modules are
derived from AbstractModule and is the means by which the Guice Injector identifies modules.
public
class PriceApplicationModule
extends AbstractModule
{
@Override
protected void
configure()
{
bind(IPriceCommandService.class)
.to(PriceCommandService.class)
.in(getDefaultScope());
bind(ModelMapper.class)
23. Command-Query Separation Architecture Pattern
21 4/2/2020
.toProvider(
() ->
new ModelMapper()
.registerModule(new PriceMappingModule()))
.in(Scopes.SINGLETON);
. . .
}
}
PriceApplicationModule overrides the configure() method from AbstractModule to define the dependency injection
bindings of the module. In the above code example, the IPriceCommandService interface is being bound to the
PriceCommandService concrete class. IPriceCommandService is one of the provided interfaces of the module. The
required interfaces of the PriceApplicationModule are specified by the parameters of the PriceCommandService
constructor.
public
class PriceCommandService
extends AbstractService
implements IPriceCommandService, IPriceDomainEventObserver
{
private final IPriceRepository itsRepository;
private final IPriceEventSender itsSender;
private final ModelMapper itsMapper;
@Inject
public
PriceCommandService(
IUnitOfWorkProvider p,
IActionQueue q,
IPriceRepository r,
IPriceEventSender s,
ModelMapper m)
{
super(p,q);
itsRepository = r;
itsSender = s;
itsMapper = m;
}
. . .
}
The PriceCommandService constructor has the following parameters types:
- IUnitOfWorkProvider
- IActionQueue
- IPriceRepository
- IPriceEventSender
- ModelMapper
These types—excluding ModelMapper—are some of the required interfaces of the PriceApplicationModule and are the
provided interfaces of other modules that the PriceApplicationModule depends on. ModelMapper is not a required interface
because its binding is defined in PriceApplicationModule and is technically a provided interface that is injected into
PriceCommandService. The following diagram illustrates these dependencies.
24. Command-Query Separation Architecture Pattern
22 4/2/2020
PriceDomainModule and PricePlatformModule also extend AbstractModule and override configure() with
dependency injection bindings for their respective provided interfaces.
Modules are also a means of introducing a layered architecture to the system [6] [5]. The PriceApplicationModule,
PriceDomainModule, and PricePlatformModule correspond respectively to the Application, Domain, and Platform
layers of the Price.CommandHost application (see section 7 Module Level).
• High Performance Event Processing – One of the trade-offs of using the Command-Query Separation pattern is that
the solution is inherently eventually consistent so it can support high performance and horizontal scalability. Eventual
consistency means that there will be a window of time between the point that the Command Host commits a create,
update, or delete operation to the command datastore and the point that the Update Host receives the event associated
with that operation and updates the query datastore with the new state. We want to make that window as small as
possible to avoid inconsistency between the command state and the query state. A key element in reducing the
inconsistency window is to optimize the event processing between the command host and the update host.
There are multiple things that can be done to optimize event processing:
- Select a high performance messaging or streaming platform such as Apache Kafka [14]. Kafka is a distributed data
store optimized for ingesting and processing streaming data in real-time. The primary elements in Kafka are:
25. Command-Query Separation Architecture Pattern
23 4/2/2020
Topics and Partitions – A topic is a named stream to which records are published. Topics can have zero, one,
or many consumers that subscribe to the data written to it. Each topic stores its data in a partitioned log managed
by the Kafka cluster:
Each partition is an ordered, immutable sequence of records that is continually appended to—a structured
commit log. The records in the partitions are each assigned a sequential id number called the offset that uniquely
identifies each record within the partition. Partitions are the unit of concurrency in Kafka.
Producers – A producer publishes data to a topic by appending data records to the partitions contained in the
topic.
Consumers and Consumer Groups – A consumer subscribes to a topic in order to consume data that has been
published to the topic by producers. Each consumer is a member of a consumer group which is a mechanism
that enables the topic to deliver a published record to one and only one consumer in a specific consumer group.
If multiple consumer instances are part of the same consumer group, then records published on the topic will
effectively be load balanced over the consumer instances. If multiple consumer instances have different
consumer groups, then each record published on the topic will be broadcast to each of the consumer instances.
- Assign a consumer group for each region. Each geographical region will contain one or more consumer groups to
enable a broadcast of EntityEvents to Entity.UpdateHosts in all regions.
- Partition Kafka topics to maximize throughput. The Entity.Events topic should be created with at least the same
number of partitions as the maximum number of consumers instance in the consumers groups. Let N be number of
partitions for the Entity.Events topics and {C1, C2, …, Ci} be the set of consumers in each of the system’s consumer
groups, then
𝐍 ≥ 𝑚𝑎𝑥(𝐶1, 𝐶2, … , 𝐶𝑖)
- Ensure that entity events are delivered in order. When Kafka topics have more than one partition, producers must
determine which partition to write the event. Kafka provides a mechanism for controlling which partition is written
to by using a key during event publishing. The producer selects the partition by calculating the modulo of the key’s
hash code to the number of partitions in the topic:
𝑝𝑎𝑟𝑡𝑖𝑡𝑖𝑜𝑛𝐼𝑛𝑑𝑒𝑥 = 𝑘𝑒𝑦. 𝑔𝑒𝑡𝐻𝑎𝑠ℎ𝐶𝑜𝑑𝑒() 𝒎𝒐𝒅 𝑛𝑢𝑚𝑏𝑒𝑟𝑂𝑓𝑃𝑎𝑟𝑡𝑖𝑡𝑖𝑜𝑛𝑠
EntityEvent transfer objects contain an EntityData that is the source of the event:
If we use the EntityData’s entityId property as the key when publishing the EntityEvent to the topic, the producer
26. Command-Query Separation Architecture Pattern
24 4/2/2020
will write the events generated by a specific Entity to the same partition in the topic, thus ensuring that these events
will be delivered in order. The key can be set during ProducerRecord construction:
String topic = "Price.Events";
PriceEvent event = new PriceEvent().setSource(new PriceData(...));
String key = event.getSource().getPriceId();
ProducerRecord<String,PriceEvent> record = new ProducerRecord<>(topic,key,event);
- Use Apache Avro [15] to generate transfer objects and for serialization/deserialization. Kafka offers both string-
based and binary-based options for message serialization/deserialization. String-based serialization/deserialization
is convenient because it generally produces human readable data that makes it easier for engineers to read and
debug. But string-based serialization/deserialization is slower than binary-based mechanisms and the size of string-
based messages is much larger that the size of binary-based messages of the same data. Apache Avro is a binary-
based serialization/deserialization implementation that is well integrated with Kafka. In Avro, transfer objects are
defined using Avro Schema files. The following are the Avro Schema definitions of PriceEvent and related types:
{
"type" : "record",
"namespace" : "cms.price.transfer.event",
"name" : "PriceEvent",
"fields" : [
{"name" : "identifiers", "type": "strata.foundation.core.event.EventIdentifiersData"},
{"name" : "eventType", "type": "strata.foundation.core.event.StandardEventType"},
{"name" : "source", "type": "cms.price.transfer.shared.PriceData"},
{"name" : "exception", "type": ["null","strata.foundation.core.event.ExceptionData"], "default" : null}
]
}
{
"type" : "record",
"namespace" : "cms.price.transfer.shared",
"name" : "PriceData",
"fields" : [
{"name": "priceId", "type": ["null","long"], "default": null},
{"name": "productId", "type": ["null","long"], "default": null},
{"name": "currencyCode", "type": "string"},
{"name": "msrp", "type": "double"},
{"name": "regularPrice", "type": "double"},
{"name": "discounts", "type": {"type": "array", "items": "cms.price.transfer.shared.DiscountData"}}
]
}
{
"type" : "record",
"namespace" : "cms.price.transfer.shared",
"name" : "DiscountData",
"fields" : [
{"name": "discountName", "type": "string"},
{"name": "price", "type": "double"},
{"name": "percentage", "type": "double"}
]
}
These schema definitions are used to generate language-specific classes that are used by the application and clients
to produce and consume PriceEvents.
• Object to Object Mapping – Implementations following the Command-Query Separation pattern often use the Transfer
Object pattern [4] when building the request and reply objects that are inputs and outputs of the EntityCommandService
and the EntityQueryService interfaces. Transfer Objects in turn rely on the Mapper pattern [8] to map Transfer Objects
to and from Entities [4]. These mappers do not need to be built from scratch. There are several excellent object mapper
frameworks that do much of the work already. The Model Mapper framework [16] is available on the JVM platform
and the AutoMapper framework [17] is available on the .Net platform. Both frameworks use a convention-based
mapping strategy that automatically maps properties in source and destination object that have the same names and
types. For more complex mappings that are not supported by the default conventions, the frameworks provide fluent
interfaces for configuring the mappers as needed. The following is an example of configuring a Model Mapper mapping
27. Command-Query Separation Architecture Pattern
25 4/2/2020
module for the Price.CommandServer component in Java:
public
class PriceMappingModule
implements Module
{
@Override
public void
setupModule(ModelMapper mapper)
{
mapper
.createTypeMap(PriceData.class,IPrice.class)
.setProvider(request -> new StandardPrice())
.addMapping(PriceData::getPriceId,IPrice::setPrimaryId)
.addMappings(
mapping ->
mapping
.using(new DiscountDataListToDiscountMapConverter())
.map(PriceData::getDiscounts,IPrice::setDiscounts));
mapper
.createTypeMap(IPrice.class,PriceData.class)
.include(StandardPrice.class,PriceData.class)
.addMapping(IPrice::getPrimaryId,PriceData::setPriceId)
.addMappings(
mapping ->
mapping
.using(new DiscountMapToDiscountDataListConverter())
.map(IPrice::getDiscounts,PriceData::setDiscounts));
. . .
}
}
In the above code example, the PriceMappingModule is a class that defines all of the object-to-object mapping used
in the Price.CommandServer component. PriceMappingModule extends Model Mapper framework’s Module class
which is the base class of all mapping modules in Model Mapper. In the first mapping configuration, a mapping
relationship is created between the PriceData class (a transfer object) and the IPrice interface (an entity). Because
IPrice is an interface the configuration specifies the concreate StandardPrice class that implements IPrice with the
setProvider() method. Most of the properties in PriceData and IPrice have the same names and types so the
convention-based mappings are automatically used. The exception to this is the PriceId property in PriceData and the
PrimaryId property in IPrice. The PriceId property identifies the price object and is mapped to the PrimaryId property.
Another mismatch between the PriceData transfer object and the IPrice entity is that the Discounts property is
represented by different collection types. In PriceData, the Discounts property is a list of DiscountData objects. In
IPrice, the Discounts property is a map of Discount objects. The DiscountDataListToDiscountMapConverter class
is a converter that facilitates the conversion between the list and map types. In the second mapping configuration, the
opposite mapping relationship is created where IPrice is the source and PriceData is the destination.
• Repositories – The domain layers in both the Entity.CommandServer component and the Entity.QueryServer
components can be implemented using the Repository pattern [10] [11]. In the CommandServer component, the
EntityRepository is implemented with a relational database and an object-to-relational mapping framework such as
Hibernate. In the QueryServer component, the EntityDataRepository is implemented with a high speed key-value
store or a document store and some type of serialization/deserialization between the EntityData type and a string-based
format such as JSON.
• Key-Value Store vs Document Store – The following criteria is useful when deciding on what kind of high speed
datastore is needed for the query side of the system:
- If the domain of the service only requires simple key-based queries such as retrieving an object with a key or a set
of objects with multiple keys, then consider using a key-value datastore such as Redis.
28. Command-Query Separation Architecture Pattern
26 4/2/2020
- If the domain of the service requires more complex queries that select objects based on properties other than a
simple key, then consider using a document store such as Couchbase or Mongo.
• Service Endpoints – Services are exposed to clients through service endpoint classes. These classes are usually part of
a REST or Web Service framework and provide annotations (JVM platform) or attributes (.Net platform) that specify
the paths or routes, http request type (POST, PUT, GET, DELETE), and request and response content. The service
endpoint class can also use annotations from an api specification framework such as Open API [18] or Swagger [19]
to generate API documentation and executable API proxies for a variety of client platforms. The following is a code
example showing the PriceQueryServiceEndpoint class that uses the JAX-RS framework [20] to define the service
endpoint and Open API to generate API documentation and proxies:
@Path("price-query")
@Tag(
name = "PriceQueryService",
description =
"Service that provides create, update, delete, activate, " +
"and deactivate operations for prices.")
public
class PriceQueryServiceEndpoint
{
private IPriceQueryService itsImplementation;
@Inject
public void
setImplementation(IPriceQueryService impl) { itsImplementation = impl;}
@Path("get-price/{requestedPriceId}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Gets an existing price")
@ApiResponse(
responseCode = "200",
description = "successful reply",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = GetPriceReply.class)))
@ApiResponse(
responseCode = "500",
description = "exception",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = GetPriceReply.class)))
public CompletionStage<GetPriceReply>
getPrice(
@PathParam("requestedPriceId") Long requestedPriceId,
@QueryParam("correlationId") UUID correlationId)
{
return
itsImplementation.getPrice(
(GetPriceRequest)
new GetPriceRequest()
.setRequestedPriceId(requestedPriceId)
.setCorrelationId(correlationId)
.setTimestamp(Instant.now()));
}
. . .
}
PriceQueryServiceEndpoint also uses dependency injection to inject an instance of the IPriceQueryService
interface that provides the actual implementation of the service endpoint. This approach keeps the business logic
defined in the class implementing IPriceQueryService separate from the platform specific details of
PriceQueryServiceEndpoint. This separation of concerns enables multiple service endpoints if needed or an easy
path for moving to a different endpoint framework if the need arises.
29. Command-Query Separation Architecture Pattern
27 4/2/2020
• Scaling Horizontally with Containers, Clusters, and Container Orchestration – In order to scale horizontally, each of
the applications in the system—Entity.CommandHost, Entity.UpdateHost, and Entity.QueryHost—are built to be
executed in a container environment such as Docker and the instances of these containers are deployed to one or more
clusters such as Amazon EC2 or Google Cloud Platform. The number of container instances are automatically scaled
by the cluster’s container orchestration mechanism such as Kubernetes (Amazon EKS or Google GKE) or Amazon
ECS. The following is an example Dockerfile that defines the Docker image of Price.UpdateHost.
FROM openjdk:8-jre-alpine
ENV DEPLOY_ENV docker-development
EXPOSE 8097
COPY target/Cms.Price.UpdateHost-1.0-SNAPSHOT-jar-with-dependencies.jar /cms-price-updatehost.jar
CMD ["/usr/bin/java", "-jar", "-Xmx1024m", "/cms-price-updatehost.jar"]
The following diagram illustrates a common deployment pattern for systems following the Command-Query
Separation pattern:
31. Command-Query Separation Architecture Pattern
29 4/2/2020
10 Sample Code
Sample code for the Price Service used in the motivation section’s example can be found at:
https://hautelook.sharepoint.com/:u:/s/catalogmanagement/ESrzh_sjOn5Ns1RXuF9ZtrQBBFFwkUWWXcTzQzYy4QpEfQ?e=q53Ak1
11 Known Uses
TBD
12 Related Patterns
• Layers – The Layers Pattern [6] [5]
• Transfer Object – The Transfer Object Pattern [4]
• Mapper – The Mapper Pattern [8]
• Proxy – The Proxy Pattern [3]
• Messaging Gateway – The Messaging Gateway Pattern [7]
• Publish-Subscribe Channel – The Publish-Subscribe Channel Pattern [21]
• Repository – The Repository Pattern [10] [11]
• Unit-Of-Work – The Unit-Of-Work Pattern [22]
• Service Assembly – The Service Assembly Pattern [TBD]
13 References
[1] Peter Bailis and Ali Ghodsi, "Eventual Consistency Today: Limitations, Extensions, and Beyond," Queue, vol. 11,
no. 3, March 2013. [Online]. https://dl.acm.org/doi/pdf/10.1145/2460276.2462076
[2] Wikipedia Community. wikipedia.org. [Online]. https://en.wikipedia.org/wiki/ACID
[3] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, "Proxy," in Design Patterns: Elements of
Reusable Object-Oriented Software.: Addison-Wesley, 1995, pp. 207-212.
[4] Martin Fowler, "Data Transfer Object," in Patterns of Enterprise Application Architecture.: Addison-Wesley
Professional, 2003, pp. 401-413.
[5] Eric Evans, "Layers," in Domain-Driven Design: Tackling Complexity in the Heart of Software.: Addison-Wesley,
2004, pp. 71-72.
[6] Frank Buschman, Regine Meunier, Hans Rohnert, Peter Sommerlad, and Michael Stal, "Layers," in Pattern-
Oriented Software Architecture: A System of Patterns.: John Wiley & Sons, 1996, pp. 31-33.
[7] Gregor Hohpe and Bobby Woolfe, "Messaging Gateway," in Enterprise Integration Patterns: Designing, Building,
and Deploying Messaging Solutions.: Addison-Wesley, 2004.
32. Command-Query Separation Architecture Pattern
30 4/2/2020
[8] Martin Fowler, "Mapper," in Patterns of Enterprise Application Architecture.: Addison-Wesley Professional, 2003,
pp. 473-474.
[9] Eric Evans, "Entities," in Domain-Driven Design: Tackling Complexity in the Heart of Software.: Addison-Wesley,
2004, pp. 90-99.
[10] Martin Fowler, "Repository," in Patterns of Enterprise Application Architecture.: Addison-Wesley Professional,
2003, pp. 322-327.
[11] Eric Evans, "Repository," in Domain-Driven Design: Tackling Complexity in the Heart of Software.: Addison-
Wesley, 2004, pp. 158-159.
[12] Eric Evans, "Modules," in Domain-Driven Design: Tackling Complexity in the Heart of Software.: Addison-Wesley,
2004, p. 111.
[13] Google. (2015) github.com/google/guice. [Online]. https://github.com/google/guice/wiki/Motivation
[14] Apache Software Foundation. (2017) kafka.apache.org. [Online]. https://kafka.apache.org/intro
[15] Apache Software Foundation. (2012) avro.apache.org. [Online]. https://avro.apache.org/docs/current/
[16] Jonathan Halterman. (2019) modelmapper.org. [Online]. http://modelmapper.org/getting-started
[17] Jimmy Bogard. (2017) automapper.readthedocs.io. [Online]. https://automapper.readthedocs.io/en/latest/Getting-
started.html
[18] Open API Initiative. (2018) spec.openapis.org. [Online]. http://spec.openapis.org/oas/v3.0.2
[19] Smart Bear, Inc. (2019) swagger.io. [Online]. https://swagger.io/docs
[20] JAX-RS Specification Committee. (2017) github.com/jax-rs. [Online]. https://github.com/jax-
rs/spec/blob/master/spec.pdf
[21] Gregor Hohpe and Bobby Woolfe, "Publish-Subscribe Channel," in Enterprise Integration Patterns: Designing,
Building, and Deploying Messaging Solutions.: Addison-Wesley, 2004, pp. 105-106.
[22] Martin Fowler, "Unit of Work," in Patterns of Enterprise Application Architecture.: Addison-Wesley Professional,
2003, pp. 184-194.