Agile, Architecture, DDD and CQRS


Published on

Each new project starts by choosing technology and framework first. After this they might start looking at what kind of a problem to solve. Unless we are in familiar territory, that is in my opinion quite dangerous.

After the selection the technology we try, in the best agile spirit to break problems down into small manageable sizes, for example through TDD. Most often this is done at the expense of our architecture, which ends up being messy and inflexible.

TDD works at the micro scale, where as our architecture works on a macro scale. It is significantly more expensive to refactor an architecture than refactor a "unit".

In this talk I will show how I see Agile and Domain Driven Design (DDD) work hand in hand to give us the simplest possible solution, by focusing on the domain, our non/cross functional constraints and how this affects our architecture. Often this will involve the identification of several loosely coupled domain-areas that each can be solved with different a architecture.

During the talk, I will cover the basic principles of DDD such as Bounded Contexts, Aggregates and various architecture partial solution principles Layered Architecture, Hexagonal (Ports & Adapters) / Onion Architecture, CQRS.

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • When the various logical models (subdomain models) need to grow to facilitate new features, each conflicting concern will impede the progress of each other.The bigger the change, e.g. adding a new logical model (such as forecasting), the worse the effect will become.
  • Scale up vertically
  • E.g. In an Inventory system a Product (often referred to as an Item) can have manydifferentnamesdepending on where in the lifecycletheyare:An Ordered Item not yetavailable for Sale is called a Back-Ordered ItemAn Item beingReceived is called a Goods ReceivedAn Item in stock is called a Stock ItemAn Item beingconsumed is called a Item Leaving InventoryAn Item beingspoiled or broken is called a Wasted Inventory Item
  • Autonomy and organizational unity around the subdomain. Independence on SLA
  • Aggregates reference each other by Id only in a scalable systemAn Aggregate Root is more likely to match a Use Case than any model structure
  • See for more
  • See formore
  • See formore
  • Spaghetti
  • Lasagna
  • [OrderService|registerOrder(...);closeOrder(...);updateAvailablility(...);addToOrder(...)]->[Order|OrderDate;Amount;Customer|get/setDate();get/setDate();get/setCustomer();get/setOrderLines()], [Order]++-*>[OrderLine|ProductId;UnitPrice;NoOfUnits|get/setProductId();get/setUnitPrice();get/setNoOfUnits()]
  • [OrderService|registerOrder(...);closeOrder(...);updateAvailablility(...);addToOrder(...)]->[Order|OrderDate;Amount;Customer|update();close();totalAmount()], [Order]++-*>[OrderLine|ProductId;UnitPrice;NoOfUnits|calculateAmount()]
  • From
  • Ourread model is structurallyoriented (the usersperspective on the data in the given context)The write model is behaviouraloriented (transaction and consistencyboundaryoriented)
  • Why is this the bestwecan offer – Whyshould theuserbeinterrupted by a technicalconstraint?
  • Commands and Events areimmutable
  • [Order|OrderDate;Amount;Customer|addProduct();removeProduct();ship();...|onProductAdded(..);onProductRemoved(..);onOrderClosed(..);onOrderShipped(..)],[Order]++-*>[OrderLine|ProductId;UnitPrice;NoOfUnits|calculateAmount()]
  • EventStore: Why store only the currentstate of the system.Matureindustries store transactions and calculate the currentstate
  • Wecanassertthatbothwhatweexpectedhappened and nothingelsehappened (the unexpectedbecomesexplicit)
  • This report is generatedbased on the previous test code
  • (but youcanchosewhichare most important to you in the different cases)
  • ACID:Atomic: The transaction is indivisible - either all the statements in the transactionareapplied to the database, or none are.Consistent: The database remains in a consistentstatebefore and aftertransactionexecution.Isolated: While multiple transactionscanbeexecuted by one or more userssimultaneously, onetransactionshould not see the effects of otherconcurrenttransactions.Durable: Once a transaction is saved to the database (an action referred to in database programmingcircles as a commit), itschangesareexpected to persist.
  • Agile, Architecture, DDD and CQRS

    1. 1. Agile & DDD & CQRS Jeppe Cramon – Partner TigerTeam ApS AANUG konference – September 2013
    2. 2. We’re doing a cool new project… So our first focus is technology
    3. 3. We’re building based on… • Entity Framework • SQL server • RavenDB • Ninject • ASP MVC • AngularJS • ServiceStack
    4. 4. So are you building a technology solution? What’s the architecture? Data Access Logic UI
    5. 5. Show me something with business meaning please Shipping Billing Order Fullfillment Sales Management Reporting
    6. 6. So what are these boxes? • Multiple Applications • Application • Component • Module • Class • Function > 100.000 lines of code? < 100 lines of code SIZE MATTERS!
    7. 7. But how do we know which it is?
    8. 8. What has agile learned us?
    9. 9. Start small, quick feedback, do just enough, yagni
    10. 10. Ah, so no analysis or thinking?
    11. 11. Recommendation: set yourself up for success • Create a cross functional team with Architect, Developers, Business Analyst, … • Spend from a few days to a few weeks, depending on project scope/size, and focus on – Carving out requirements – Write stories – Do small spikes – Estimate • Also known as: – All hands on deck (Coplien) – Inception phase (Unified Process)
    12. 12. Competing forces at different stages of development To begin with • Simplicity and homogenousity • Ease of development • As much as possible close together • Simple prototype cases … as time progresses … • More complex scenarios • Multiple teams • Modularization • Decoupling • Autonomy • ..
    13. 13. But what about TDD? The last D in TDD is for development NOT design
    14. 14. Example: Let’s just start with a simple model
    15. 15. One Domain Model to Rule them All? Combining subdomains into one big domain model increases complexity as everything tends to be connected to and depend on everything else
    16. 16. As time goes by…
    17. 17. And it gets more and more complicated
    18. 18. And finally we drown
    20. 20. Beaware of your Architectural constraints What are your non-functional (or cross functional) requirements?
    21. 21. One big model = ONE DATABASE =
    22. 22. The database determines our scalability
    23. 23. And databases often scale poorly To solve the performance problems for a few, you have to upgrade it all
    24. 24. Do not design for locality and try to distribute Design for distribution, take advantage of locality
    25. 25. Alternatively we can distribute and use Read replicas Master Slave SlaveSlaveSlave You’re now eventually consistent
    26. 26. Or introduce caching… Now you’re both eventual consistent and lack a synchronization mechanism? ?
    27. 27. Why spend a lot of money on designing an ACID compliant system after which you insert a cache to make it eventual consistent?
    28. 28. SO WHY IS upfront Macro architecture important? Because – REFACTORING at the macro level is MUCH more EXPENSIVE than at the micro level
    29. 29. Recommendation: Divide and concour • Start from the outside and model business capabilities • Identify Business Domains • Design your Macro Architecture around this • Determine communication and interaction principles • Ensure common monitoring and logging facilities
    30. 30. How can DDD help?
    31. 31. Ubiquitous Language & Bounded Contexts
    32. 32. Many perspectives on data Customer Retail System Pricing Inventory Sales Product Unit Price Promotional Price Promotion End Date Stock Keeping Unit (SKU) Quantity On Hand (QOH) Location Code Price Quantity Ordered Name The lifecycle for Data is VERY important! Forecasting?
    33. 33. Smaller models & clear data ownership Sales Product ProductID Name Description Quantity Ordered … Sales Inventory Product ProductID SKU QOH Location Code … Inventory Pricing Product ProductID Unit Price Promotional Price … Pricing DDD: Bounded Context SOA: Service Retail System Shared Entity identity
    34. 34. Context map Shipping Billing Order Fullfillment Sales Management Reporting
    35. 35. Macro vs micro Shipping Billing Order Fullfillment Sales Management Reporting Domain Architecture Macro Communication Architecture Micro Architecture
    36. 36. Macro vs micro architectures • Macro focuses on – Domain responsibilitiy and capability • Separating unrelated or limited related related business concerns • Focus on ensuring a stable and consistent domain models • ”Build for replacement” instead of ”Build for reuse” – Integration Protocols – UI Integration – Data formats – Logging and Monitoring • Micro focuses on – Programming languages – Frameworks – Tools – Database – Patterns (e.g. Layers) – DRY – YAGNI
    37. 37. Be aware of Conways law Organizational structure and Architecture follow each other. Think about that when building teams. Center them around business capabilities/bounded contexts
    38. 38. A bounded-context contains all parts related to it DB Schema Domain Services Application Services Domain Model Aggregates , Entities, Value Objects, Events Integration Endpoints (REST, SOAP, Pub/Sub) User Interface Silo
    39. 39. Improving our internal architecture by sound design principles
    40. 40. CRUD
    41. 41. IT exists to support the business by support various usecases Usecases can be categorized as either • ”User” intent to manipulate information • ”User” intent to find and read information We already support this in OO at a Property level: • Setter (or mutator) methods Manipulate data • Getter (or accessor) methods Read data Let’s raise it to a higher Level
    42. 42. CQS Separation of functions that write & functions that read Functions that write are called Command methods and must not return a value Functions that read are called Query methods and must have no side effects
    43. 43. CQS from a code perspective public class CustomerService { // Commands void MakeCustomerPreferred (CustomerId) void ChangeCustomerLocale(CustomerId, NewLocale) void CreateCustomer(Customer) void EditCustomerDetails(CustomerDetails) // Queries Customer GetCustomer(CustomerId) CustomerSet GetCustomersWithName(Name) CustomerSet GetPreferredCustomers() } From:
    44. 44. SO WHAT IS CQRS? CQRS is simply the creation of two objects where there was previously only one
    45. 45. CQRS In its simplest form public class CustomerWriteService { // Commands void MakeCustomerPreferred (CustomerId) void ChangeCustomerLocale(CustomerId, NewLocale) void CreateCustomer(CreateCustomer) void EditCustomerDetails(CustomerDetails) } public class CustomerReadService { // Queries Customer GetCustomer(CustomerId) CustomerSet GetCustomersWithName(Name) CustomerSet GetPreferredCustomers() } From:
    46. 46. Commands A Command’s primary goal is to capture USER INTENT A Command supports a single usecase and targets a single Aggregate Commands are stated in the imperative: – DoStuff – CreateCustomer – ShipOrder – AddComment – DeleteComment
    47. 47. Terminology: Aggregates Invoice InvoiceLine * Account * What: • Cluster coherent Entities and Value Objects, with complex associations into Aggregates with well defined boundaries. • Choose one entity to be root and control access to objects inside the boundary through the root. • External objects hold references to the root • Aggregates only refer to other aggregates by identity (their id) Motivation: Control invariants and consistency through the aggregate root. Enables: Loading schemes, coarse grained locking, consistency/transactional boundaries for Distributed DDD Root
    48. 48. Aggregate rules and invariants • A single transaction only changes one Aggregate • Aggregate to Aggregate references are by Id only • An Aggregate must ensure Consistency using at least Optimistic Concurrency, so we can spot overlapping updates • Using Event Sourcing for your Aggregate can allow even more fine grained Concurrency Control by merging overlapping changes that are not in conflict • At the cost of extreme scalability it will be preferable if an Aggregate can track and guarantee complete ordering of all changes to it self (e.g. provide a sequential Event sequence as a simple counter) • Business Transactions involving more than 1 aggregate must be implemented as a Process (Choreographed or Orchestrated). Otherwise we limit our scalability tremendously
    49. 49. CQRS requires a New UI paradigme Task-based/Inductive UI • A depart from the traditional WHAT UI (CRUD) • Task based UI’s focuses on HOW the user wants to use the application • Guides users through the work process • The UI becomes an intrinsic part of the design • The UI design directly affects our commands and thereby our transactional boundaries • Should be preferably use asynchronous Commands * See New UI Paradigm slide deck for more
    50. 50. New UI paradigm
    51. 51. Going from CRUD to Task based UI CRUD style
    52. 52. Going from CRUD to Task based UI Task based style
    53. 53. Going from CRUD to Task based UI Task based style dialog interaction when pressing the Assign Todo button – capturing the users intent
    54. 54. This means we need to look at what architecture structure that fit this approach
    55. 55. Thin Layers of complexity UI Data Pro: Easy and simple Con: Scales poorly for complex domains Language of the database
    56. 56. Language of a Layered application UI Application Domain Data Pro: Handles complex domains better by Separating usecase and domain logic Con: Increasing complexity. Risk of Anemic Domain model combined with transaction-script. Time Complexity Language of DTO’s Language of getters and setters Language of the DB
    57. 57. Aneamic Domain Model The Good parts • All Order logic is called in ONE class • Lower entry level for junior developers The bad parts • Hard to test • Hard to maintain • Code dupliction • An exposed domain model is like walking around without clothes – everyone can access your private parts and violate your invariants
    58. 58. We need a rich domain model Aggregate with Order as Aggregate Root • Order forms our Unit of consistency, which controls our invariants • Order and OrderLines are created, updated and deleted together
    59. 59. We need to department from traditional layered architecture UI Business Logic Data Infrastructure
    60. 60. Problems with the classical layered architecture • Each subsequent layers depends on the layers beneath it • Typically each layer also depends on some kind of infrastructure layer • This creates a very tights coupling of which the biggest is that – UI and Business Logic depends on data access logic (transitive) – UI can’t function if Business Logic isn’t there – Business Logic can’t function if Data access logic isn’t there – This makes the data access logic our sour point – Because Business Logic and the Domain model directly depend on the Data access logic + infrastructure you can be sure to have Data access logic creeping in everywhere – This makes changing the data access logic very hard and expensive – If coupling prevents easily changing parts of the system, then the normal choice is to let the system fall behind and this is how legacy systems become stale, and eventually they are rewritten.
    61. 61. Alternative visualization of a Layered architecture Infrastructur e Data Access Logic Business Logic User Interface Tests DB File WebServic e The application is built around data access and other infrastructure. Because of this coupling, when data access, web services, etc. change, the business logic layer will also have to change
    62. 62. Clean Architecture / Onion Architecture Domain Model (Entities, Aggregates) Use Cases Controllers External Interfaces DB FileWebServic e Enterprise Business Rule Application Business Rules Interface Adapters Frameworks & Drivers
    63. 63. Hexagonal Architecture (Ports & Adapters) A Domain Model Application Incoming ports Outgoing ports Adapters Adapters Use Case boundary Message transformation occurs in the Adapter
    64. 64. Hexagonal Architecture example Customer Master Domain & Business Logic <<WebService>> PrivateCustomerServic e <<WebService>> SearchPrivateCustomer Service <<WebService>> AddressService <<WebService>> PrivateCustomerEventR etrievalService <<OSB:WebService >> PrivateCustomerEve ntReceiverService Customer Support UI Event Publishe r JPABased EventStore HornetQ CustomerVie wCustomerVie wCustomerVie w Oracle Oracle Mail/SMS sender Customer Master Subdomain/context
    65. 65. Each Service/Business-Domain is its own Hexagons/Onions Product Catalog Inventory Shipping Pricing Sales
    67. 67. Circular architecture Task based UI Domain ModelRead Model
    68. 68. 1. Realization Commands and Queries support very different usecases
    69. 69. A single model cannot be appropriate for reporting, searching and transactional behavior
    70. 70. 2. realization Query results contain only data – no logic • Give me the customer with his last 10 orders • Give me the customer with total sum of orders for the last year • Give me the complete order • Give me the shipping status for the order • Give me the customers last review So why go through the domain layer? UI Application Data
    71. 71. Things to think about in relation to Queries • Why take the pain of performing JOINS and/or UNIONS of our nicely normalized Relational Model? • Why not having a completely denormalized data model (or View) to support our Query? • When we completely denormalize our data, do we really need a relational database or could we use a Key/Value- or Document Store? • Do we really need our Read Data to be to the microsecond consistent with our Write data?
    72. 72. So how is this relevant in the real world? Let’s look at a real-life scenario
    73. 73. Collaborative domains 1. Read 2. Read 4. Update 3. Coffee time 5. Stale data
    74. 74. We’re working with stale data
    75. 75. We’re always working with stale data! 20 ms 1 ms 100 ms 100 ms100 ms 1 ms 20 ms At this point our data is at least 120 ms stale + Thinking time + thinking time (1000-1500 ms)
    76. 76. LET’S USE STALE DATA TO OUR ADVANTAGE BY OFFLOADING THE DATABASE BY USING OUR READ MODELS UI Application Domain Write model Commands – Change data UI Application Read models Queries – Ask for data (no side effects)
    77. 77. ALL WE NEED IS A GOOD WAY TO SYNCHRONIZE OUR WRITE AND READ MODELS UI Application Domain Write model Commands – Change data UI Application Read models Queries – Ask for data (no side effects)
    78. 78. Let’s make the implicit explicit! Business Events Which: • Signal that something has happened • Closely aligned to the Domain Model • Are handled by a messaging system • They are in the past tense: – CustomerBilled – ParcelShipped – CustomerCreated – ReviewCreated – CommentAdded – CommentDeleted
    79. 79. Commands & Events Commands mutate Aggregate state which results in one or more Events being issued/published. Command Event(s) AcceptOrder OrderAccepted ShipOrder OrderShipped AddComment CommentAdded QuarantineReview ReviewQuarantined UnquarantineReview ReviewUnquarantined
    80. 80. With Domain Events we now have a mechanism to support our denormalized View/Query models
    81. 81. Read model Read model Commands, Events and Query Models Events UI Domain modelQuery model ”AcceptOrder” command ”OrderAccepted ” event ”Find all Accepted Orders” Query Commands are Imperative: DoStuff Events are Past tense: StuffDone
    82. 82. Messaging Architectures Most CQRS implementations see Commands and Events as (asynchronous) Messages public class CreateCustomer { public final Guid CustomerId; public final Name CustomerName; … } public class CustomerCreated { public final Guid CustomerId; public final DateTime CreationDateTime; public final Name CustomerName; } Command Event Difference: INTENT
    83. 83. CQRS Building blocks Client Commands Command Bus Sends Command Handlers Modify Repositories Read Write Data store Event Bus Command Services Event Handlers Events Read store Query HandlersQuery Results Queries Query Services Events Domain
    85. 85. Event Sourcing Entities/Aggregates track their own Domain Events and derive state from them OrderCreated ProductAdded ProductAdded ProductRemoved ProductAdded OrderAccepte d Time 07:39 Time 07:40 Time 07:41 Time 07:45 Time 07:46 Time 07:50
    86. 86. With Event Sourcing we have solved auditing and bi-temporal history
    87. 87. Full CQRS With EventSourcing UI Domain Event Store Commands – Change data Commands Events SQL DB Document DB Graph DB UI Data Queries – Ask for data Events Query Build Our single source of truth
    88. 88. public class CustomerCommandHandler { private Repository<Customer> customerRepository; public CustomerCommandHandler(Repository<Customer> customerRepository) { this.customerRepository = customerRepository; } @CommandHandler public void handle(UnsignCustomer cmd) { Customer customer = repository.load(cmd.getCustomerId()); customer.unsign(); } } public class Customer { private boolean signedUp; public void unsign() { if (signedUp) { apply(new CustomerUnsignedEvent()); } } @EventHandler private void handle(CustomerUnsignedEvent event) { signedUp = false; } }
    89. 89. Testing CQRS BDD style @Test public void a_signedup_customer_can_unsign() { UUID customerId = UUID.randomUuid().toString(); FixtureConfiguration fixture = Fixtures.newGivenWhenThenFixture(); fixture.registerAnnotatedCommandHandler( new CustomerCommandHandler(fixture.createGenericRepository(Customer.class)) ); fixture.setAggregateIdentifier(customerId); fixture .given( TestFactory.customerCreatedEvent(customerId), TestFactory.customerSignedUpEvent(customerId) ) .when( TestFactory.unsignCustomerCommand(customerId) ) .expectEvents( TestFactory.customerUnsignedEvent(customerId) ); }
    90. 90. CQRS / BDD Report generated based on the previous test Scenario: A signed-up customer can unsign Given a Customer with id ”abc1234” that has been created and a customer with id ”abc1234” that is signed-up When a request for Customer with id ”abcd1234” to be unsigned is received Then the Customer with id ”abcd1234” is unsigned
    91. 91. SOA / Integration Commands, Queries and Events form natural integration interfaces So integration is baked in from the beginning!
    92. 92. What about Scalability? CAP Theorem • Consistency: All nodes in the cluster see exactly the same data at any point in time • Availability: Failure of a node does not render the entire system inoperative • Partition tolerance: Nodes can still function when communication with other groups of nodes is lost You can’t have all three!
    93. 93. CAP theorem Source: Strong  Weak  Eventual Consistency ACID systems are hard and expensive to scale Latency concerns Unless you use Pessimistic Locking – all data is stale (and eventual consistent when delivered to the user)
    94. 94. BASE Basically Available Soft-state Eventually consistent Eventually Consistency levels: • Causal consistency: This involves a signal being sent from between application session indicating that a change has occurred. From that point on the receiving session will always see the updated value. • Read your own writes: In this mode of consistency, a session that performs a change to the database will immediately see that change, even if other sessions experience a delay. • Monotonic consistency: In this mode, A session will never see data revert to an earlier point in time. Once we read a value, we will never see an earlier value. See for more
    95. 95. CQRS can give us BASE at the architectural level through asynchronous Commands and Events This gives the business and IT control over where to spend money to scale our solution - instead of trying to buy a bigger database server.
    96. 96. What about Validation • Happens in the UI before Commands are sent • Typically validated using Read models • Helps to ensure that commands don’t often fail • Validates simple things (values ranges, unique keys/values) • Doesn’t enforce business logic rules
    97. 97. CQRS in .NET with no Frameworks
    98. 98. IBus public interface IBus : ICommandSender, IEventPublisher { void RegisterHandler<T>(Action<T> handler, DispatchType dispatchType) where T : IMessage; } public interface ICommandSender { void Send<T>(T command) where T : Command; } public interface IEventPublisher { void Publish(IEnumerable<Event> events); }
    99. 99. CommandHandler public class ProjectCommandHandlers { private readonly EventStore _eventStore; public ProjectCommandHandlers(EventStore eventStore, IBus bus) { _eventStore = eventStore; bus.RegisterHandler<CreateNewProject>(Handle, DispatchType.ThreadPooled); bus.RegisterHandler<ChangeProjectAnnotations>(Handle, DispatchType.ThreadPooled); } public void Handle(CreateNewProject cmd) { if (_eventStore.HasAggregateWithId(cmd.AggregateId)) throw new ProjectAlreadyCreatedException(cmd.AggregateId); var project = new Project(cmd.AggregateId, cmd.Name, cmd.Description); _eventStore.AppendEventsToStream(cmd.AggregateId, project.EventsOccured); } public void Handle(ChangeProjectAnnotations cmd) { var project = new Project(_eventStore.LoadEventStream(cmd.AggregateId).Events); project.ChangeProjectAnnotations(cmd.Annotations); _eventStore.AppendEventsToStream(cmd.AggregateId, project.EventsOccured); } }
    100. 100. Project Aggregate public class Project: Aggregate<ProjectState> { private readonly ProjectState _state; public Project(IEnumerable<DomainEvent> events) { _state = new ProjectState(events); } public Project(Guid aggregateId, string name, string description) { _state = new ProjectState(); ApplyEvent(new NewProjectCreated(aggregateId, name, description)); } public void ChangeProjectAnnotations(Annotations annotations) { ApplyEvent(new ProjectAnnotationsChanged(AggregateId, annotations)); } }
    101. 101. Aggregate public abstract class Aggregate<T> : IAggregate where T : IAggregateState { private readonly IList<DomainEvent> _eventsOccurred = new List<DomainEvent>(); public abstract Guid AggregateId { get; } public IList<DomainEvent> EventsOccured { get { return _eventsOccurred; } } protected void ApplyEvent(DomainEvent @event) { @event.EventSequenceNumber = CreateNextEventSequenceNumber(); @event.UserId = UserContext.Instance.LoggedInUserId; _eventsOccurred.Add(@event); State.ApplyEvent(@event); } /// <summary> /// Creates a new EventSequenceNumber (has sideeffects) /// </summary> /// <returns>the new sequence number</returns> private long CreateNextEventSequenceNumber() { return State.LastEventSequenceNumber + 1; } protected abstract T State { get; } }
    102. 102. Project Aggregate State public class ProjectState : AggregateState { private Guid _aggregateId; public string ProjectName { get; private set; } public string Description { get; private set; } public Annotations Annotations { get; private set; } public ProjectState() {} public ProjectState(IEnumerable<DomainEvent> events) { foreach (var @event in events) { ApplyEvent(@event); } } public void Apply(NewProjectCreated pce) { _aggregateId = pce.AggregateId; ProjectName = pce.Name; Description = pce.Description; } public void Apply(ProjectAnnotationsChanged pac) { Annotations = pac.Annotations; } public override Guid AggregateId { get { return _aggregateId; } } }
    103. 103. EventStore
    104. 104. EventStore public abstract class EventStore : IEnumerable<IEvent> { public void AppendEventsToStream(Guid id, ICollection<DomainEvent> events) { InternalAppendEventsToStream(id, IGNORE_VERSION_AND_APPEND, events); _eventPublisher.Publish(events); } }
    105. 105. Projections/EventHandlers public class ProjectsList : CachedAggregatedView<Guid, ProjectListItem>, IProjectsList { public ProjectsList(IBus bus, ILocalRepository repository) : base(repository) { bus.RegisterHandler<NewProjectCreated>(Handle, DispatchType.Direct); bus.RegisterHandler<ProjectAnnotationsChanged>(Handle, DispatchType.Direct); } protected void Handle(NewProjectCreated e) { var projectListItem = new ProjectListItem(e.AggregateId, e.Name, e.Description); projectListItem.LastEventSequenceNumber = e.EventSequenceNumber; projectListItem.IsAvailableLocally = true; projectListItem.LastSavedByUserId = e.UserId; projectListItem.OwnerOrganizationId = e.OwnerOrganizationId; this[e.AggregateId] = projectListItem; } protected void Handle(ProjectAnnotationsChanged e) { ProjectListItem item = this[e.AggregateId]; item.LastEventSequenceNumber = e.EventSequenceNumber; item.Annotations = e.Annotations; item.IsAvailableLocally = true; item.LastSavedByUserId = e.UserId; this[e.AggregateId] = item; } }
    106. 106. Classical CQRS challenge
    107. 107. Set based operations • How to ensure that no two persons have same – Username – Email address – Mobile phone number – Social security number • Tough choice between Consistency/Eventual consistency – performance and how much you trust your clients
    108. 108. Axon has decent support for this through the UnitOfWork - continued public class PersonCommandHandler { private void reserveUniqueKeys(String userNameToReserve, String emailAddressToReserve, String socialSecurityNumberToReserver, String mobilePhoneNumberToReserver, String actorId, final UnitOfWork unitOfWork) { ReservationControl reservationControl = uniquePersonKeysRepository.reservereUniqueKeys( userNameToReserve, emailAddressToReserve, socialSecurityNumberToReserver, mobilePhoneNumberToReserver, aktoerId); if (reservationControl.isReservationOk()) { duringErrorCancelReservationsOfUniqueKeys( userNameToReserve, emailAddressToReserve, socialSecurityNumberToReserver, mobilePhoneNumberToReserver, aktoerId, unitOfWork); } else { throw new UniquePersonKeysAreAlreadyTakenException( userNameToReserve, emailAddressToReserve, socialSecurityNumberToReserver, mobilePhoneNumberToReserver, reservationControl); } } private void duringErrorCancelReservationsOfUniqueKeys(final String userName, final String emailAddress, final String socialSecurityNumber, final String mobilePhoneNumber, final String actorId, final UnitOfWork unitOfWork) { unitOfWork.registerListener(new UnitOfWorkListenerAdapter() { @Override public void onRollback(Throwable failureCause) { uniquePersonKeysRepository.cancelReservationOfUniqueKeys(userName, emailAddress, socialSecurityNumber, mobilePhoneNumber, actorId); } }); } }
    109. 109. This is a 99,99% solution • Reservation and Cancellation of UniqueKeys occurs in a separate transaction (Requires_new) – We could potentially used Nested Transactions using JDBC SavePoints but not all database supports this • Therefore, if the creation process fails for some reason we need to cancel our unique key reservations • This can fail for the same reason that the creation process failed, in which case our reservation persist • So there’s no way around handling clean up periodically (either based on timers / triggers / events / etc.)
    110. 110. Experiences using CQRS • Some what overkill for this solution if there wasn’t a requirement for local caches in the source systems • Forced us to focus on the domain and follow proper modeling practices – Commands are fantastic for capturing user intent • Clean code and architecture by focusing on a single aspect at a time – Formal separation of “bounded contexts” – Driven by consistency boundaries – Loose coupling through events – Model per view/usecase • Refactoring and learning friendly • Very easy to test • Task based UI makes for a very understandable UI compared to CRUD (but also takes more time to develop)
    111. 111. Experiences using CQRS - continued • Learning curve was harder for some developers – Breaking old bad habits – Handling business logic in Aggregate event handlers is NO NO • Eventual consistency is tricky – don’t go overboard
    112. 112. Questions? @jeppec on @TigerTeamDK on Twitter