an intro to cqrs
TRANSCRIPT
DDD South West 2An Introduction to CQRS approaches to system architecture
Aims
An overview of CQRS UI Command Handling View Handling
How to write an Event Sourced DDD system The command handling bit
But first some code
To 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 Presentation Presentation { get; set; } public string Location { get; set; } public DateTime Time { get; set; } }
But what about Queries?
They’re just queries They support the UI which should
provide a Decision Support System
Intentful UIShopping
Cart Service
Buying Choices Service
Offers Service
Bought Together Service
Ratings Service
Product Images Service
Command
Command
Command
Command
Command
CommandCommand
Intentful UI
Captures Intent Aligns with commands
Uses the query infrastructure For display For decision support▪ Commands should succeed
Will contain logic, is typically rich
Some Slides
Big picture & background stuff!
Commands
Queries
Faça
de\C
lient
Handler
Bus
EventuallyConsistent
Publisher
Subscribers
Subscriber
Subscriber
Subscriber
ID : 123Name : The Art of WarAuthor: Sun TzuISBN: 1234ABCD5678
Event: BookIsbnChangedNewValue: 4321DCBA8765
…there are times when we don't just want to see where we are,we also want to know how we got there
http://martinfowler.com/eaaDev/EventSourcing.html
Time to See Some Code
You didn’t think I’d make you watch me type did you?
Coding for EventSourcing
namespace ProgNetDemo{ public class CatalogItem { }}
A Command Method
namespace ProgNetDemo{ public class CatalogItem { public void Retire() { } }}
Some State To Change
namespace ProgNetDemo{ public class CatalogItem {
private bool _retired;
public void Retire() { } }}
Guard the Mutation
namespace ProgNetDemo{ public class CatalogItem {
private bool _retired;
public void Retire() {
if (!_retired)throw new InvalidOperationException();
} }}
Make the State Mutation A Domain Event
namespace ProgNetDemo{ public class CatalogItem {
private bool _retired;
public void Retire() {
if (!_retired)throw new InvalidOperationException();
ApplyEvent(new RetiredEvent(_id)); } }}
Need an Identifier for the Aggregate
public class CatalogItem{
private bool _retired; private Guid _id;
public void Retire(){
if (!_retired)throw new InvalidOperationException();
ApplyEvent(new RetiredEvent(_id)); }}
Create the Event
public class RetiredEvent{
private readonly Guid _id;
public RetiredEvent(Guid id) {
_id = id;}
}
Need to be Able to Apply the Event
public class CatalogItem{
private bool _retired; private Guid _id;
public void Retire(){
if (!_retired)throw new InvalidOperationException();
ApplyEvent(new RetiredEvent(_id)); }}
Create a Base Class
public class AggregateRoot { protected void ApplyEvent(Event
@event){}
}
We’ll need to handle more than
just the one type of event!
Create the Event Type
public class Event { }
Have our RetiredEvent Inherit From the Base Type
public class RetiredEvent : Event{
private readonly Guid _id;
public RetiredEvent(Guid id) {
_id = id;}
}
Still Need to Mutate the Statepublic class CatalogItem : AggregateRoot{
private bool _retired; private Guid _id;
public void Retire(){ if (!_retired)throw new InvalidOperationException();ApplyEvent(new RetiredEvent(_id));
}}
Create a Method to Handle the State Change From the Event
public class CatalogItem : AggregateRoot{
private bool _retired; private Guid _id;
public void Retire(){
if (!_retired)throw new InvalidOperationException();
ApplyEvent(new RetiredEvent(_id)); }
private void ApplyRetiredEvent(RetiredEvent @event){
_retired = true;}
}
Done!?
public class CatalogItem : AggregateRoot{
private bool _retired; private Guid _id;
public void Retire(){
if (!_retired)throw new InvalidOperationException();
ApplyEvent(new RetiredEvent(_id)); }
private void ApplyRetiredEvent(RetiredEvent @event){
_retired = true;}
}
Need to Connect the Event with the Handler
public class CatalogItem : AggregateRoot{
private bool _retired; private Guid _id;
public CatalogItem(){
RegisterHandler<RetiredEvent>(ApplyRetiredEvent);}
public void Retire(){
if (!_retired)throw new InvalidOperationException();
ApplyEvent(new RetiredEvent(_id)); }
private void ApplyRetiredEvent(RetiredEvent @event){
_retired = true;}
}
Allow Handlers to be Registered public class AggregateRoot { protected void RegisterHandler<TEvent>(AppliesEvent<TEvent>
handler)where TEvent : Event
{}
protected void ApplyEvent(Event @event){}
}
Create the Delegate Signaturepublic delegate void AppliesEvent<TEvent>(TEvent @event)
where TEvent : Event; public class AggregateRoot { protected void
RegisterHandler<TEvent>(AppliesEvent<TEvent> handler)where TEvent : Event
{}
protected void ApplyEvent(Event @event){}
}
Need to Maintain the Registrypublic delegate void AppliesEvent<TEvent>(TEvent @event)
where TEvent : Event; public class AggregateRoot { private readonly IDictionary<Type, Action<Event>> _handlerRegistry;
protected void RegisterHandler<TEvent>(AppliesEvent<TEvent> handler)where TEvent : Event{var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e));}
protected void ApplyEvent(Event @event){}
}
Delegate Adjuster:From Greg’s Blog
public class DelegateAdjuster{ public static Action<TBase> CastArgument<TBase,
TDerived>(Expression<Action<TDerived>> source) where TDerived : TBase { if (typeof(TDerived) == typeof(TBase)) { return (Action<TBase>)((Delegate)source.Compile());
} ParameterExpression sourceParameter = Expression.Parameter(typeof(TBase),
"source"); var result = Expression.Lambda<Action<TBase>>( Expression.Invoke( source, Expression.Convert(sourceParameter, typeof(TDerived))), sourceParameter); return result.Compile(); }}
Need to Maintain the Registrypublic delegate void AppliesEvent<TEvent>(TEvent @event)
where TEvent : Event; public class AggregateRoot { private readonly IDictionary<Type, Action<Event>> _handlerRegistry;
protected void RegisterHandler<TEvent>(AppliesEvent<TEvent> handler)where TEvent : Event
{var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e)); _handlerRegistry.Add(typeof(TEvent), castHandler);
}
protected void ApplyEvent(Event @event){}
}
Need to Apply the Event Stillpublic delegate void AppliesEvent<TEvent>(TEvent @event)
where TEvent : Event; public class AggregateRoot { private readonly IDictionary<Type, Action<Event>> _handlerRegistry;
protected void RegisterHandler<TEvent>(AppliesEvent<TEvent> handler)where TEvent : Event
{var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e)); _handlerRegistry.Add(typeof(TEvent), castHandler);
}
protected void ApplyEvent(Event @event){
Action<Event> handler;if (!_handlerRegistry.TryGetValue(@event.GetType(), out handler)){
throw new InvalidOperationException();}handler(@event);
}}
Need to Track Applied Eventspublic delegate void AppliesEvent<TEvent>(TEvent @event)
where TEvent : Event; public class AggregateRoot { private readonly IDictionary<Type, Action<Event>> _handlerRegistry;
private readonly ICollection<Event> _events;
protected void RegisterHandler<TEvent>(AppliesEvent<TEvent> handler)where TEvent : Event
{var castHandler = DelegateAdjuster.CastArgument<Event, TEvent>(e => handler(e)); _handlerRegistry.Add(typeof(TEvent), castHandler);
}
protected void ApplyEvent(Event @event){
Action<Event> handler;if (!_handlerRegistry.TryGetValue(@event.GetType(), out handler)){
throw new InvalidOperationException();}handler(@event);_events.Add(@event);
}}
All done now???
public class CatalogItem : AggregateRoot{
private bool _retired; private Guid _id;
public CatalogItem(){
RegisterHandler<RetiredEvent>(ApplyRetiredEvent);}
public void Retire(){
if (!_retired)throw new InvalidOperationException();
ApplyEvent(new RetiredEvent(_id)); }
private void ApplyRetiredEvent(RetiredEvent @event){
_retired = true;}
}
Could use a convention & just
make the AggregateRoot
declare which events it produces
Not Quite
Need to Expose the events for persistence & publishing Put a GetChanges() method on the AggregateRoot base
class Need to be able to rebuild the Aggregate from
history Put a LoadFromHistory(IEnumerable<Event> events)
method on the AggregateRoot base class▪ Reapply the events▪ Don’t track them as changes – overload apply event▪ protected void ApplyEvent(Event @event, bool trackAsChange)
if (add) _events.Add(@event);
Possibly memento pattern for snapshoting See Mark Nijhof’s blog & sample code on GitHub
Back to the Whiteboard!
Some more on Query Stores
Some Other Benefits of Event Sourcing
Already We can build new stores for new services, we have
the full set of available information ready We can rebuild stores for existing services We use the same mechanism for our Query Stores
as we use for any other service in the Enterprise More Granular Service Boundaries More Explicit Boundaries We aren’t just restricted to storing the Events▪ Can send Emails based on them▪ Can perform Complex Event Processing▪ … The worlds your Oyster, you have the RAW MATERIAL
Some Other Benefits of Event Sourcing
Also A Very Provable Audit Log Very Simple Horizontal Scaling More Granular Service Boundaries More Explicit Boundaries Can Replay Events in Debugging
Scenarios Suits Behaviour Based Testing &
Outside-In Development
NO SILVER BULLETS
The coding is the EASY BIT Don’t need a grand framework
The thinking & conversations is the HARD BIT
Referenced Material/Links
Greg Youngs: Course – http://bit.ly/gregscourse Blog – http://bit.ly/gregyoungsblog
Udi Dahans: Course – http://bit.ly/udiscourse Blog – http://bit.ly/udisblog
Mark Nijhof’s sample code http://github.com/MarkNijhof/Fohjin