An intro to cqrs
-
Upload
neil-robbins -
Category
Business
-
view
1.825 -
download
6
Transcript of An intro to cqrs
![Page 2: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/2.jpg)
Aims
An overview of CQRS UI Command Handling View Handling
How to write an Event Sourced DDD system The command handling bit
![Page 3: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/3.jpg)
But first some code
To Visual Studio!
![Page 4: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/4.jpg)
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; } }
![Page 5: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/5.jpg)
But what about Queries?
They’re just queries They support the UI which should
provide a Decision Support System
![Page 6: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/6.jpg)
Intentful UIShopping
Cart Service
Buying Choices Service
Offers Service
Bought Together Service
Ratings Service
Product Images Service
Command
Command
Command
Command
Command
CommandCommand
![Page 7: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/7.jpg)
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
![Page 8: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/8.jpg)
Some Slides
Big picture & background stuff!
![Page 9: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/9.jpg)
Commands
Queries
Faça
de\C
lient
Handler
Bus
EventuallyConsistent
![Page 10: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/10.jpg)
Publisher
Subscribers
Subscriber
Subscriber
Subscriber
![Page 11: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/11.jpg)
ID : 123Name : The Art of WarAuthor: Sun TzuISBN: 1234ABCD5678
Event: BookIsbnChangedNewValue: 4321DCBA8765
![Page 12: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/12.jpg)
…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
![Page 13: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/13.jpg)
Time to See Some Code
You didn’t think I’d make you watch me type did you?
![Page 14: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/14.jpg)
Coding for EventSourcing
namespace ProgNetDemo{ public class CatalogItem { }}
![Page 15: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/15.jpg)
A Command Method
namespace ProgNetDemo{ public class CatalogItem { public void Retire() { } }}
![Page 16: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/16.jpg)
Some State To Change
namespace ProgNetDemo{ public class CatalogItem {
private bool _retired;
public void Retire() { } }}
![Page 17: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/17.jpg)
Guard the Mutation
namespace ProgNetDemo{ public class CatalogItem {
private bool _retired;
public void Retire() {
if (!_retired)throw new InvalidOperationException();
} }}
![Page 18: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/18.jpg)
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)); } }}
![Page 19: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/19.jpg)
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)); }}
![Page 20: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/20.jpg)
Create the Event
public class RetiredEvent{
private readonly Guid _id;
public RetiredEvent(Guid id) {
_id = id;}
}
![Page 21: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/21.jpg)
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)); }}
![Page 22: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/22.jpg)
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!
![Page 23: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/23.jpg)
Create the Event Type
public class Event { }
![Page 24: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/24.jpg)
Have our RetiredEvent Inherit From the Base Type
public class RetiredEvent : Event{
private readonly Guid _id;
public RetiredEvent(Guid id) {
_id = id;}
}
![Page 25: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/25.jpg)
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));
}}
![Page 26: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/26.jpg)
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;}
}
![Page 27: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/27.jpg)
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;}
}
![Page 28: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/28.jpg)
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;}
}
![Page 29: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/29.jpg)
Allow Handlers to be Registered public class AggregateRoot { protected void RegisterHandler<TEvent>(AppliesEvent<TEvent>
handler)where TEvent : Event
{}
protected void ApplyEvent(Event @event){}
}
![Page 30: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/30.jpg)
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){}
}
![Page 31: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/31.jpg)
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){}
}
![Page 32: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/32.jpg)
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(); }}
![Page 33: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/33.jpg)
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){}
}
![Page 34: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/34.jpg)
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);
}}
![Page 35: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/35.jpg)
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);
}}
![Page 36: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/36.jpg)
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
![Page 37: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/37.jpg)
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
![Page 38: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/38.jpg)
Back to the Whiteboard!
Some more on Query Stores
![Page 39: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/39.jpg)
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
![Page 40: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/40.jpg)
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
![Page 41: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/41.jpg)
NO SILVER BULLETS
The coding is the EASY BIT Don’t need a grand framework
The thinking & conversations is the HARD BIT
![Page 42: An intro to cqrs](https://reader035.fdocuments.us/reader035/viewer/2022062419/5590bd471a28abbf308b47e0/html5/thumbnails/42.jpg)
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