CQRS Lite and Otherwise
george mauer - http://georgemauer.net
I’m George
I’m Director of Development at SURGE Consulting
I’m George
I’m Director of Development at SURGE Consulting
CQS
It’s a
Bertrand Russel thing so you know it’s
right
Separating Read and Write Objects is the Core Concept
CustomerService
void MakeCustomerPreferred(CustomerId)
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)
CustomerWriteService
void MakeCustomerPreferred(CustomerId)
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)
CustomerReadService
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/
CQRS Evolved From DDD
Udi Dahan
Greg Young
CQRS a la Greg Young
Guidance from Udi Dahan
So how does The Mob define it?
Task-based UI
Message Bus
Explicit Commands
Explicit Events
And an audit log
Event Sourcing
There is no database
Split Read/Write Data Models
Partitions & Multiple Processes
Eventual Consistency & SLAs
It’s Just Weird!
CQRS Lite
Which of these can we cut?
Which of these can we cut?
Wishful Thinking�Simplify�OK�OK�Later�Conceptually, Yes. As Needed
Introduce as needed�Much Later
Start with standard
n-tier MVC architecture
Write ViewModels are Commands
Write actions delegate to applicators and manage transactions
[HttpPost, Route("")]� public async Task<dynamic> Create(CreateTimeEntryCommand cmd) {� var id = await timeEntryModification.Apply(cmd);� await Db.SaveChangesAsync();� cmd.LogSuccess();� return Created($"api/time-entry/{id}", id);� }
public class TimeEntryModification :� IApplyCommand<DeleteTimeEntryCommand>,� IApplyCommand<EditTimeEntryCommand>,� IApplyCommand<AdminOverrideEditTimeEntryCommand>,� IApplyCommand<CreateTimeEntryCommand, Guid> {�� public async Task<Guid> Apply(CreateTimeEntryCommand cmd) {� // ..� return id;� }��}
BONUS IDEA: Applicators can access internal state
Read-only properties enforce the pattern
Applicators are internal to entities
Probably not necessary for the pattern
¯\_(ツ)_/¯
Applicators publish Events
which can be subscribed to
public async Task<Guid> Apply(LockPeriodCommand cmd) {� var tp = new TimePeriod(cmd.Start, cmd.End);� // yes, this is db-inefficient, but this is not a huge dataset and an infrequent operation� if ( (await db.LockedPeriods.ToListAsync()).Any(x => tp.OverlapsWith(x.TimePeriod())) )� throw new InvalidOperationException("Locked time periods cannot overlap");� var lp = new LockedPeriod(tp);� db.LockedPeriods.Add(lp);� await messages.Dispatch(new LockPeriodCommand.Occurred {
LockedPeriodId = lp.Id, TimePeriod = lp.TimePeriod() });� return lp.Id;� }
public class Adjustment :� IHandleEvent<LockPeriodCommand.Occurred> {�� public async Task Apply(LockPeriodCommand.Occurred ev) =>� await listItemsStore.Update(li => � li.IsUnlocked = false, await listItems.DataForAnyUser(queryModel(ev.TimePeriod, true))� );�}
Just Implement your own �Message Bus
Queries can be handled by a read service (can be a controller)
and are as simple as possible
and use custom stores as needed
To Summarize
Enable gradual transition
The goal:
Questions?
gmauer@gmail.com
@togakangaroo
http://georgemauer.net
Slides:
http://bit.ly/2rBaixy