agile, architecture, ddd and cqrs
DESCRIPTION
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.TRANSCRIPT
Agile & DDD & CQRS
Jeppe Cramon – Partner TigerTeam ApSAANUG konference – September 2013
We’re doing a cool new project…
So our first focus is technology
We’re building based on…
• Entity Framework• SQL server• RavenDB• Ninject• ASP MVC• AngularJS• ServiceStack
So are you building a technology solution?
What’s the architecture?
Data Access
Logic
UI
Show me something with business meaning please
Shipping
BillingOrder
Fullfillment
Sales ManagementReporting
So what are these boxes?
• Multiple Applications• Application• Component• Module• Class• Function
> 100.000 lines of code?
< 100 lines of code
SIZE MATTERS!
But how do we know which it is?
What has agile learned us?
Start small, quick feedback, do just enough, yagni
Ah, so no analysis or thinking?
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)
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• ..
Beware of Y
AGNI
too ear
ly
But what about TDD?
The last D in TDD is for development NOT design
Example: Let’s just start with a simple model
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
As time goes by…
And it gets more and more complicated
And finally we drown
ONE MODEL TO RULE THEM ALL
ONE MODEL TO FIND THEM
ONE MODEL TO BRING THEM ALL
AND IN THE DARKNESS BIND THEM
Beaware of your Architectural constraints
What are your non-functional (or cross functional)
requirements?
One big model = ONE DATABASE
=
The database determines our scalability
And databases often scale poorly
To solve the performance problems for a few, you have to upgrade it all
Do not design for locality and try to distribute
Design for distribution, take advantage of locality
Alternatively we can distribute anduse Read replicas
Master Slave SlaveSlaveSlave
Read
Update
You’re now eventually consistent
Or introduce caching…
Read
Update
Now you’re both eventual consistent and lack a synchronization mechanism?
?
Why spend a lot of money on designing an ACID compliant system
after which you insert a cache to make it eventual consistent?
SO WHY IS upfront Macro architecture important?
Because – REFACTORING at the macro level is MUCH more EXPENSIVE than at the micro level
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
How can DDD help?
Ubiquitous Language &
Bounded Contexts
Many perspectives on data
Customer
Retail System
Pricing
Inventory
Sales
Product
Unit PricePromotional PricePromotion End Date
Stock Keeping Unit (SKU)Quantity On Hand (QOH)Location Code
PriceQuantity OrderedName
The lifecycle for Data is VERY important!
Forecasting?
Smaller models & clear data ownership
SalesProduct
ProductIDNameDescriptionQuantity Ordered…
Sales
InventoryProduct
ProductIDSKUQOHLocation Code… Inventory
PricingProduct
ProductIDUnit PricePromotional Price…
Pricing
DDD:Bounded Context
SOA:Service
Retail System
Shared Entity identity
Context map
Shipping
BillingOrder
Fullfillment
Sales
Management
Reporting
Macro vs micro
Shipping
BillingOrder
Fullfillment
Sales
Management
Reporting
Domain Architecture
MacroCommunication
Architecture
MicroArchitecture
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
Differ
ent l
ifecy
cles
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
A bounded-context contains all parts related to it
DB Schema
Domain Services
Application Services
DomainModel
Aggregates, Entities,
Value Objects, Events
Integration Endpoints
(REST, SOAP,
Pub/Sub)
User Interface
Silo
Improving our internal architecture by sound design principles
CRUD
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
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
CQS from a code perspectivepublic 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: https://gist.github.com/1964094
SO WHAT IS CQRS?
CQRS is simply the creation of two objects where there was previously only one
CQRSIn 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: https://gist.github.com/1964094
CommandsA 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
Terminology: AggregatesWhat:• 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
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
CQRS requires a New UI paradigmeTask-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
New UI paradigm
Going from CRUD to Task based UI
CRUD style
Going from CRUD to Task based UI
Task based style
Going from CRUD to Task based UI
Task based style dialog interaction when pressing the Assign Todo button – capturing the users intent
This means we need to look at what architecture structure that fit this approach
Thin Layers of complexity
UI Data
Pro: Easy and simple
Con: Scales poorly for complex domains
Language of the
database
Language of a Layered application
UIApplicatio
nDomain Data
Pro: Handles complex domains better by Separating usecase and domain logic
Con: Increasing complexity. Risk of AnemicDomain model combined with transaction-script.
Time
Tran
sacti
on sc
ript
Real domain model
Com
plex
ity
Language of DTO’s
Language of getters
and setters
Language of the DB
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
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
We need to department from traditional layered architecture
UI
Business Logic
Data Infr
ast
ruct
ure
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.
Alternative visualization of a Layered architecture
Infrastructure
Data Access Logic
Business Logic
User Interface
Tests
DB File
WebService
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
Clean Architecture / Onion Architecture
Domain Model
(Entities, Aggregate
s)
Use Cases
Controllers
Dev
ices
External Interfaces
DB
FileWebService
Pre
sente
rs
Gateways
Web
DB UI
Enterprise Business RulesApplication Business RulesInterface Adapters
Frameworks & Drivers
Hexagonal Architecture (Ports & Adapters)
ADomain
Model
ApplicationTr
igge
r da
taAdm
inis
trat
i
onN
otification
s
Database
Test
GUI
http
app-to-
app
Test
Wire feed
http feed
Mock
Telephon
e
Answerin
g
Machine
Mock Database
Database
Incoming ports
Outgoing ports
AdaptersAdapters
Use Case boundary
Message transformation occurs in the
Adapter
Hexagonal Architecture example
Customer MasterDomain &
Business Logic
<<WebService>>PrivateCustomerServ
ice
<<WebService>>SearchPrivateCusto
merService
<<WebService>>AddressService
<<WebService>>PrivateCustomerEventRetrievalService
<<OSB:WebService>>
PrivateCustomerEventReceiverServi
ce
Customer Support UI
EventPublish
er
JPABasedEventStore
HornetQ
CustomerViewCustomerVi
ewCustomerView Oracle
Oracle
Mail/SMS
sender
Customer Master Subdomain/context
Each Service/Business-Domain is its own Hexagons/Onions
Product Catalog
Inventory
Shipping
Pricing
Sales
LET’S HAVE A LOOK AT A DIFFERENT WAY OF BUILDING A SOLUTION THAT SUPPORTS THE
ONION ARCHITECUTURE
Circular architecture
Separation of Concerns
Task basedUI
Command
Domain ModelRead Model
Query
1. RealizationCommands and Queries
support very different usecases
A single model cannot be appropriate for reporting, searching and transactional behavior
2. realizationQuery 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
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?
So how is this relevant in the real world?
Let’s look at a real-life scenario
Collaborative domains
1. Read 2. Read
4. Update3. Coffee time
5. Stale data
We’re working with stale data
Optimist
ic Lo
cking Exc
eption
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 least120 ms stale
+Thinking time
+ thinking time (1000-1500 ms)
LET’S USE STALE DATA TO OUR ADVANTAGE BY OFFLOADING THE DATABASE BY USING OUR READ
MODELS
UIApplicatio
nDomain Write
model
Commands – Change data
UIApplicatio
nRead
models
Queries – Ask for data (no side effects)
ALL WE NEED IS A GOOD WAY TO SYNCHRONIZE OUR WRITE AND READ MODELS
UIApplicatio
nDomain Write
model
Commands – Change data
UIApplicatio
nRead
models
Queries – Ask for data (no side effects)
Let’s make the implicit explicit!
Business EventsWhich:• 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
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
With Domain Events we now have a mechanism to support our denormalized View/Query models
Read modelRead model
Commands, Events and Query Models
Commands
Events
Queries
UI
Domain modelQuery model
”AcceptOrder”
command
”OrderAccepted”event
”Find all Accepted Orders”Query
Commands are Imperative: DoStuffEvents are Past tense: StuffDone
Messaging ArchitecturesMost CQRS implementations see Commands and
Events as (asynchronous) Messagespublic 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
CQRS Building blocks
Client
CommandsCommand
Bus
Sends
Command Handlers
Modify
Repositories
Read Write
DatastoreEvent
Bus
Publish Events
EventBus
Command Services
Event HandlersEvents
Read storeQuery Facade
Query HandlersQuery Results
Queries
Query Services
Events
Domain
LET’S TAKE IT A STEP FURTHER
Event Sourcing
Entities/Aggregates track their own Domain Events and derive state from them
OrderCreated ProductAdded
ProductAdded
ProductRemoved
ProductAdded
OrderAccepted
Time
07:39
Time
07:40
Time
07:41
Time
07:45
Time
07:46
Time
07:50
With Event Sourcing we have solved auditing and bi-temporal history
Full CQRSWith EventSourcing
UI Domain
EventStore
Commands – Change data
Commands Events
SQL DB Document DB Graph DB
UI Data
Queries – Ask for data
EventsQuery Build
Our single source of truth
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; }}
Testing CQRS BDD style@Testpublic 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) );}
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
SOA / Integration
Commands, Queries and Events form natural integration interfaces
So integration is baked in from the beginning!
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!
CAP theorem
Source: http://bit.ly/QUurnY
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)
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 http://www.allthingsdistributed.com/2008/12/eventually_consistent.html for more
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 scaleour solution - instead of trying
to buy a bigger database server.
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
CQRS in .NET with no Frameworks
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);}
CommandHandlerpublic 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); }}
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)); }}
Aggregatepublic 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; }}
Project Aggregate Statepublic 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; } }
}
EventStore
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); }}
Projections/EventHandlerspublic 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; }}
Classical CQRS challenge
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
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() {@Overridepublic void onRollback(Throwable failureCause) {
uniquePersonKeysRepository.cancelReservationOfUniqueKeys(userName, emailAddress,
socialSecurityNumber, mobilePhoneNumber, actorId);}
});}
}
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.)
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)
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
Questions?@jeppec on [email protected]
@TigerTeamDK on Twitterhttp://tigerteam.dk