DDD South West 2An Introduction to CQRS approaches to system architecture@NeilRobbinsneil@computer.org
AimsAn overview of CQRSUICommand HandlingView HandlingHow to write an Event Sourced DDD systemThe command handling bit
But first some codeTo Visual Studio!
What’s wrong with this? public class Event    {        public string Name { get; set; }        public int Venue { get; set; }        public DateTime Date { get; set; }        public IEnumerable<Session> Sessions { get; set; }    }    public class Session    {        public PresentationPresentation { get; set; }        public string Location { get; set; }        public DateTime Time { get; set; }    }
But what about Queries?They’re just queriesThey support the UI which should provide a Decision Support System
Intentful UIShopping Cart ServiceCommandCommandRatings ServiceCommandCommandProduct Images ServiceCommandCommandCommandBuying Choices ServiceOffers ServiceBought Together Service
Intentful UICaptures IntentAligns with commandsUses the query infrastructureFor displayFor decision supportCommands should succeedWill contain logic, is typically rich
Some SlidesBig picture & background stuff!
Façade\ClientBusCommandsEventuallyConsistentQueriesHandler
SubscriberSubscriberPublisherSubscribersSubscriber
ID : 123Name : The Art of WarAuthor: Sun TzuISBN: 1234ABCD5678Event: BookIsbnChangedNewValue: 4321DCBA8765
…there are times when we don't just want to see where we are,we also want to know how we got therehttp://martinfowler.com/eaaDev/EventSourcing.html
Time to See Some CodeYou didn’t think I’d make you watch me type did you?
Coding for EventSourcingnamespaceProgNetDemo{public classCatalogItem    {    }}
A Command MethodnamespaceProgNetDemo{public classCatalogItem    { public void Retire()      	{        	}    }}
Some State To ChangenamespaceProgNetDemo{public classCatalogItem    {privatebool _retired; public void Retire()      	{        	}    }}
Guard the MutationnamespaceProgNetDemo{public classCatalogItem    {privatebool _retired; public void Retire()      	{if (!_retired)thrownewInvalidOperationException();        	}    }}
Make the State Mutation A Domain EventnamespaceProgNetDemo{public classCatalogItem    {privatebool _retired; public void Retire()      	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));        	}    }}
Need an Identifier for the Aggregatepublic classCatalogItem{privatebool _retired;privateGuid_id; public void Retire()	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));    	}}
Create the EventpublicclassRetiredEvent{privatereadonlyGuid _id;publicRetiredEvent(Guid id)    {		_id = id;	}}
Need to be Able to Apply the Eventpublic classCatalogItem{privatebool _retired;privateGuid_id; public void Retire()	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));    	}}
Create a Base Classpublic classAggregateRoot {protected voidApplyEvent(Event @event)	{	}}We’ll need to handle more than just the one type of event!
Create the Event Typepublic classEvent { }
Have our RetiredEvent Inherit From the Base TypepublicclassRetiredEvent : Event{privatereadonlyGuid _id;publicRetiredEvent(Guid id)    {		_id = id;	}}
Still Need to Mutate the Statepublic classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id; public void Retire()	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));    	}}
Create a Method to Handle the State Change From the Eventpublic classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id; public void Retire()	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));    	}private voidApplyRetiredEvent(RetiredEvent @event)	{		_retired = true;	}}
Done!?public classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id; public void Retire()	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));    	}private voidApplyRetiredEvent(RetiredEvent @event)	{		_retired = true;	}}
Need to Connect the Event with the Handlerpublic classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id;publicCatalogItem()	{RegisterHandler<RetiredEvent>(ApplyRetiredEvent);	} public void Retire()	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));    	}private voidApplyRetiredEvent(RetiredEvent @event)	{		_retired = true;	}}
Allow Handlers to be Registeredpublic classAggregateRoot {protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event	{	}protected voidApplyEvent(Event @event)	{	}}
Create the Delegate Signaturepublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event	{	}protected voidApplyEvent(Event @event)	{	}}
Need to Maintain the Registrypublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event	{var castHandler = 		        DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e));	}protected voidApplyEvent(Event @event)	{	}}
Delegate Adjuster:From Greg’s Blogpublic class DelegateAdjuster{    public static Action<TBase> CastArgument<TBase, TDerived>(Expression<Action<TDerived>> source) whereTDerived : TBase{        if (typeof(TDerived) ==typeof(TBase)){            return (Action<TBase>)((Delegate)source.Compile());}ParameterExpressionsourceParameter = Expression.Parameter(typeof(TBase),"source");varresult = Expression.Lambda<Action<TBase>>(Expression.Invoke(                source,Expression.Convert(sourceParameter, typeof(TDerived))),sourceParameter);        return result.Compile();}}
Need to Maintain the Registrypublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event	{var castHandler = 		        DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e));		 _handlerRegistry.Add(typeof(TEvent), castHandler);	}protected voidApplyEvent(Event @event)	{	}}
Need to Apply the Event Stillpublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event	{var castHandler = 		        DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e));		 _handlerRegistry.Add(typeof(TEvent), castHandler);	}protected voidApplyEvent(Event @event)	{Action<Event> handler;if (!_handlerRegistry.TryGetValue(@event.GetType(), out handler))		{throw newInvalidOperationException();		}		handler(@event);	}}
Need to Track Applied Eventspublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;private readonlyICollection<Event> _events;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event	{var castHandler = 		        DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e));		 _handlerRegistry.Add(typeof(TEvent), castHandler);	}protected voidApplyEvent(Event @event)	{Action<Event> handler;if (!_handlerRegistry.TryGetValue(@event.GetType(), out handler))		{throw newInvalidOperationException();		}		handler(@event);		_events.Add(@event);	}}
All done now???public classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id;publicCatalogItem()	{RegisterHandler<RetiredEvent>(ApplyRetiredEvent);	} public void Retire()	{if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id));    	}private voidApplyRetiredEvent(RetiredEvent @event)	{		_retired = true;	}}Could use a convention & just make the AggregateRoot declare which events it produces
Not QuiteNeed to Expose the events for persistence & publishingPut a GetChanges() method on the AggregateRoot base classNeed to be able to rebuild the Aggregate from historyPut a LoadFromHistory(IEnumerable<Event> events) method on the AggregateRoot base classReapply the eventsDon’t track them as changes – overload apply eventprotected voidApplyEvent(Event @event, booltrackAsChange)if (add) _events.Add(@event);Possibly memento pattern for snapshotingSee Mark Nijhof’s blog & sample code on GitHub
Back to the Whiteboard!Some more on Query Stores
Some Other Benefits of Event SourcingAlreadyWe can build new stores for new services, we have the full set of available information readyWe can rebuild stores for existing servicesWe use the same mechanism for our Query Stores as we use for any other service in the EnterpriseMore Granular Service BoundariesMore Explicit BoundariesWe aren’t just restricted to storing the EventsCan send Emails based on themCan perform Complex Event Processing… The worlds your Oyster, you have the RAW MATERIAL
Some Other Benefits of Event SourcingAlsoA Very Provable Audit LogVery Simple Horizontal ScalingMore Granular Service BoundariesMore Explicit BoundariesCan Replay Events in Debugging ScenariosSuits Behaviour Based Testing & Outside-In Development
NO SILVER BULLETSThe coding is the EASY BITDon’t need a grand frameworkThe thinking & conversations is the HARD BIT
Referenced Material/LinksGreg Youngs:Course – http://bit.ly/gregscourseBlog – http://bit.ly/gregyoungsblogUdiDahans:Course – http://bit.ly/udiscourseBlog – http://bit.ly/udisblogMark Nijhof’s sample codehttp://github.com/MarkNijhof/Fohjin

An intro to cqrs

  • 1.
    DDD South West2An Introduction to CQRS approaches to system architecture@NeilRobbinsneil@computer.org
  • 2.
    AimsAn overview ofCQRSUICommand HandlingView HandlingHow to write an Event Sourced DDD systemThe command handling bit
  • 3.
    But first somecodeTo Visual Studio!
  • 4.
    What’s wrong withthis? public class Event { public string Name { get; set; } public int Venue { get; set; } public DateTime Date { get; set; } public IEnumerable<Session> Sessions { get; set; } } public class Session { public PresentationPresentation { get; set; } public string Location { get; set; } public DateTime Time { get; set; } }
  • 5.
    But what aboutQueries?They’re just queriesThey support the UI which should provide a Decision Support System
  • 6.
    Intentful UIShopping CartServiceCommandCommandRatings ServiceCommandCommandProduct Images ServiceCommandCommandCommandBuying Choices ServiceOffers ServiceBought Together Service
  • 7.
    Intentful UICaptures IntentAlignswith commandsUses the query infrastructureFor displayFor decision supportCommands should succeedWill contain logic, is typically rich
  • 8.
    Some SlidesBig picture& background stuff!
  • 9.
  • 10.
  • 11.
    ID : 123Name: The Art of WarAuthor: Sun TzuISBN: 1234ABCD5678Event: BookIsbnChangedNewValue: 4321DCBA8765
  • 12.
    …there are timeswhen we don't just want to see where we are,we also want to know how we got therehttp://martinfowler.com/eaaDev/EventSourcing.html
  • 13.
    Time to SeeSome CodeYou didn’t think I’d make you watch me type did you?
  • 14.
  • 15.
    A Command MethodnamespaceProgNetDemo{publicclassCatalogItem { public void Retire() { } }}
  • 16.
    Some State ToChangenamespaceProgNetDemo{public classCatalogItem {privatebool _retired; public void Retire() { } }}
  • 17.
    Guard the MutationnamespaceProgNetDemo{publicclassCatalogItem {privatebool _retired; public void Retire() {if (!_retired)thrownewInvalidOperationException(); } }}
  • 18.
    Make the StateMutation A Domain EventnamespaceProgNetDemo{public classCatalogItem {privatebool _retired; public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); } }}
  • 19.
    Need an Identifierfor the Aggregatepublic classCatalogItem{privatebool _retired;privateGuid_id; public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); }}
  • 20.
    Create the EventpublicclassRetiredEvent{privatereadonlyGuid_id;publicRetiredEvent(Guid id) { _id = id; }}
  • 21.
    Need to beAble to Apply the Eventpublic classCatalogItem{privatebool _retired;privateGuid_id; public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); }}
  • 22.
    Create a BaseClasspublic classAggregateRoot {protected voidApplyEvent(Event @event) { }}We’ll need to handle more than just the one type of event!
  • 23.
    Create the EventTypepublic classEvent { }
  • 24.
    Have our RetiredEventInherit From the Base TypepublicclassRetiredEvent : Event{privatereadonlyGuid _id;publicRetiredEvent(Guid id) { _id = id; }}
  • 25.
    Still Need toMutate the Statepublic classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id; public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); }}
  • 26.
    Create a Methodto Handle the State Change From the Eventpublic classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id; public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); }private voidApplyRetiredEvent(RetiredEvent @event) { _retired = true; }}
  • 27.
    Done!?public classCatalogItem :AggregateRoot{privatebool _retired;privateGuid_id; public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); }private voidApplyRetiredEvent(RetiredEvent @event) { _retired = true; }}
  • 28.
    Need to Connectthe Event with the Handlerpublic classCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id;publicCatalogItem() {RegisterHandler<RetiredEvent>(ApplyRetiredEvent); } public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); }private voidApplyRetiredEvent(RetiredEvent @event) { _retired = true; }}
  • 29.
    Allow Handlers tobe Registeredpublic classAggregateRoot {protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event { }protected voidApplyEvent(Event @event) { }}
  • 30.
    Create the DelegateSignaturepublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event { }protected voidApplyEvent(Event @event) { }}
  • 31.
    Need to Maintainthe Registrypublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event {var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e)); }protected voidApplyEvent(Event @event) { }}
  • 32.
    Delegate Adjuster:From Greg’sBlogpublic class DelegateAdjuster{ public static Action<TBase> CastArgument<TBase, TDerived>(Expression<Action<TDerived>> source) whereTDerived : TBase{ if (typeof(TDerived) ==typeof(TBase)){ return (Action<TBase>)((Delegate)source.Compile());}ParameterExpressionsourceParameter = Expression.Parameter(typeof(TBase),"source");varresult = Expression.Lambda<Action<TBase>>(Expression.Invoke( source,Expression.Convert(sourceParameter, typeof(TDerived))),sourceParameter); return result.Compile();}}
  • 33.
    Need to Maintainthe Registrypublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event {var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e)); _handlerRegistry.Add(typeof(TEvent), castHandler); }protected voidApplyEvent(Event @event) { }}
  • 34.
    Need to Applythe Event Stillpublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event {var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e)); _handlerRegistry.Add(typeof(TEvent), castHandler); }protected voidApplyEvent(Event @event) {Action<Event> handler;if (!_handlerRegistry.TryGetValue(@event.GetType(), out handler)) {throw newInvalidOperationException(); } handler(@event); }}
  • 35.
    Need to TrackApplied Eventspublic delegate voidAppliesEvent<TEvent>(TEvent @event)where TEvent : Event;public classAggregateRoot {private readonlyIDictionary<Type, Action<Event>> _handlerRegistry;private readonlyICollection<Event> _events;protected voidRegisterHandler<TEvent>(AppliesEvent<TEvent> handler)whereTEvent : Event {var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e)); _handlerRegistry.Add(typeof(TEvent), castHandler); }protected voidApplyEvent(Event @event) {Action<Event> handler;if (!_handlerRegistry.TryGetValue(@event.GetType(), out handler)) {throw newInvalidOperationException(); } handler(@event); _events.Add(@event); }}
  • 36.
    All done now???publicclassCatalogItem : AggregateRoot{privatebool _retired;privateGuid_id;publicCatalogItem() {RegisterHandler<RetiredEvent>(ApplyRetiredEvent); } public void Retire() {if (!_retired)thrownewInvalidOperationException();ApplyEvent(new RetiredEvent(_id)); }private voidApplyRetiredEvent(RetiredEvent @event) { _retired = true; }}Could use a convention & just make the AggregateRoot declare which events it produces
  • 37.
    Not QuiteNeed toExpose the events for persistence & publishingPut a GetChanges() method on the AggregateRoot base classNeed to be able to rebuild the Aggregate from historyPut a LoadFromHistory(IEnumerable<Event> events) method on the AggregateRoot base classReapply the eventsDon’t track them as changes – overload apply eventprotected voidApplyEvent(Event @event, booltrackAsChange)if (add) _events.Add(@event);Possibly memento pattern for snapshotingSee Mark Nijhof’s blog & sample code on GitHub
  • 38.
    Back to theWhiteboard!Some more on Query Stores
  • 39.
    Some Other Benefitsof Event SourcingAlreadyWe can build new stores for new services, we have the full set of available information readyWe can rebuild stores for existing servicesWe use the same mechanism for our Query Stores as we use for any other service in the EnterpriseMore Granular Service BoundariesMore Explicit BoundariesWe aren’t just restricted to storing the EventsCan send Emails based on themCan perform Complex Event Processing… The worlds your Oyster, you have the RAW MATERIAL
  • 40.
    Some Other Benefitsof Event SourcingAlsoA Very Provable Audit LogVery Simple Horizontal ScalingMore Granular Service BoundariesMore Explicit BoundariesCan Replay Events in Debugging ScenariosSuits Behaviour Based Testing & Outside-In Development
  • 41.
    NO SILVER BULLETSThecoding is the EASY BITDon’t need a grand frameworkThe thinking & conversations is the HARD BIT
  • 42.
    Referenced Material/LinksGreg Youngs:Course– http://bit.ly/gregscourseBlog – http://bit.ly/gregyoungsblogUdiDahans:Course – http://bit.ly/udiscourseBlog – http://bit.ly/udisblogMark Nijhof’s sample codehttp://github.com/MarkNijhof/Fohjin

Editor's Notes

  • #2 I’m afraid my job title has the word architect in it – so we’re off to an ivory tower for this bit 
  • #3 Boundaries are Explicit Acknowledge potential cost (perf &amp; dev) of going between services: geographical cost trust boundaries execution environments
  • #7 Which might seem a strange place to begin a talk on CQRS!Every button, link the lot – is a command of interest – though not all may be handled in the same way.
  • #8 Which might seem a strange place to begin a talk on CQRS!Every button, link the lot – is a command of interest – though not all may be handled in the same way.
  • #9 Which might seem a strange place to begin a talk on CQRS!Every button, link the lot – is a command of interest – though not all may be handled in the same way.
  • #11 Maintaining the ViewState
  • #13 Capturing all state changes made to an object as Event ObjectsAdvantages:Rebuild state from eventsTemporal Queries – rebuild state to a point in timeDebugging – replay events that led to a problemSnapshotsDisadvantagesCan look a bit magical