slidenumbers: true build-lists: true
- Command Query Responsibility Segregation
- A pattern for separating read and write operations
- Key motivation is separation of concerns
- Query (read) returns data and does not alter the state of the object
- Command (write) changes the state of an object but does not return any data
// LocationService - typical CRUD
void MarkLocationPreferred(LocationId)
Location GetLocation(LocationId)
// apply CQRS
// LocationWriteService
void MarkLocationPreferred(LocationId)
// LocationReadService
Location GetLocation(LocationId)
^ Put like this it isn't terribly interesting, though it allows some powerful options which we'll talk about
- Greg Young says ...
- CQRS is not a top-level architecture
- Top-level will look more like SOA and EDA [service-oriented or event-driven (messaging) architecture]
- Different than CRUD
- Domain Driven Design requires tasks
- CQRS is not required
^ * Enables task centered UI actions
- submit application, reserve seats, join quest
- Tasks are more user centered
- CRUD tends to shape the solution vs. focus on business need
- Captures domain knowledge from the domain experts
- Enables the team to determine scope and verify the consistency of that knowledge
- Expressed in code by the developers
- Defined when talking with domain experts
- Identify nouns / verbs when talking about the business process
- "Quest" is a noun
- "start" is a verb
- start, join, leave - these are your tasks
- Entity - defined by an identity which is constant
- Value objects - defined by their values
- Services - an example would be a 3rd party service, which is typically stateless
- A cluster of related entities and value objects that form a consistency boundary within the system
- That consistency boundary is usually based on transactional consistency
- Gatekeeper object for the aggregate
- All access to the objects within the aggregate must occur through the aggregate root
- External entities are only permitted to hold a reference to the aggregate root
// working with an aggregate
membersJoined(cmd) {
// load aggregate
let agg = documents.fetchEntity(cmd.entityId)
// call method on aggregate
agg.membersJoined(cmd.day, cmd.location, cmd.members)
// persist aggregate
documents.save(agg)
}
- A grouping of domain models, ubiquitous language, entities, etc. within the overall system
- Larger systems may have multiple bounded contexts
^ Conference management application - Conference Reservations, Program Management (speakers, sessions), Badge Printing
- Documentation that describes the relationships between bounded contexts
- Notifications of something that has already happened
- Immutable one-way messages
- Published by a single source, may have multiple subscribers
documents.appendEvents(id, bookSeat({name: 'Quinn', seat: 'E23'}))
- Typically include additional information about the event
- Should describe business intent
Seat E23 was booked by Quinn
- vs. -
In the bookings table the row with key E23 had the
name field updated with the value Quinn
^ Can possibly use separate data stores for read/write, each store optimized for its use case.
- Aggregates define consistency boundaries
- Use an event raised by an aggregate to notify interested parties that a transaction has taken place
- Use the ID of an aggregate to determine the source of the event
- Persisting your application's state by storing the history that determines the current state of your application
- Replay the event stream to get the current state
^ Example: a bank balance
{
entityId: '345',
day: 1,
location: 'Hobbiton',
members: [ 'Frodo', 'Sam' ]
}
// working with aggregate
membersJoined(cmd) {
let agg = documents.fetchEntity(cmd.entityId)
agg.membersJoined(cmd.day, cmd.location, cmd.members)
documents.save(agg)
}
class Documents {
save(aggregate) {
documents.appendEvents(
aggregate.id,
aggregate.uncommitedChanges() // list of events
)
}
}
membersJoined(cmd) {
documents.appendEvents(
cmd.entityId,
membersJoinedEvent(cmd.day, cmd.location, cmd.members)
)
}
class Quest extends AggregateRoot {
constructor(id) {
super()
this.id = id
this.name = null
super.handle('event:quest:started', this.applyQuestStarted)
}
applyQuestStarted(event) {
this.name = event.data.name
}
}
handle(name, handler) {
this.handlers.push({
name: name,
handler: handler
})
}
loadFromHistory(events) {
each(events, event => this.applyChange(event))
}
applyChange(event) {
this.version = event.version
const handle = find(
this.handlers,
{ 'name': event.data.eventName }
)
handle.handler(event)
}
- Create a "snapshot" of an aggregate at a current state
- Load the snapshot and any future events vs. the whole event stream
- Coordinate and route messages between bounded contexts and aggregates
- Manage a long-lived business transaction or process
- An alternative to using a distributed transaction - avoid locks
- Duration doesn't have to be days/weeks, can be seconds
- Manage process, not business logic
- State machines
- Scalability
- Reduced complexity
- Flexability
- Focus on the business
- Facilitates task-based UIs
^ Scalability - the number of reads typically exceed number of writes Reduced complexity - read logic is typically much simpler and can be decoupled, separation of concerns, multiple users, performance, transactions, consistency Flexability - Add more read views (reports) without dealing with mutation logic Focus on business - CRUD tends to shape the solution, CQRS helps you focus on tasks Tasked based UI - tasks tend to focus on the domain operations and the ubiquitous language CAP Theorem - Consistency, Availability, Partition Tolerance - choose two
- can choose different decisions on the write vs. read
- Audit trail
- Performance - events are immutable
- Simplification - simple objects that describe what happened, not a complex object
- Additional business value from event history
- Testing
- Learning curve is steep
- Stale data - "eventually consistent"
- Simple domains
- Non-collaborative environments
- Exploring CQRS and Event Sourcing - free book by Microsoft Patterns & Practices
- https://msdn.microsoft.com/en-us/library/jj554200.aspx
- Most images are taken from this book
- Greg Young
- Jonathan Oliver - Sagas
- Marten - http://jasperfx.github.io/marten