Applying DDD+CQRS+ES for mail delivery in an occasionally connected mobile environment.
Have you ever imagined that you could try to implement event sourcing on a mobile device running on Windows Mobile 6 with .NET Compact framework 3.5? Well, me neither until I started this project in which a mail and parcels delivery company asked me to completely re-design the software that their employees use to scan and track the mail and parcels they deliver to customers across the country.
In this talk, I will explain both the business problem that we were trying to solve and the technical issues linked to the fact that our software had to run on an industrial mobile device with very specific hardware and software, had to be fast and reactive so the users where not slowed down in their daily work when in front of a customer, and had to occasionally send its data back to a central server when the device found connectivity so that other depending systems could be updated.
I will show how events really fitted this particular business problem and how designing a system based on events solved many technical issues while enabling simplicity in its implementation.
- Mail/parcel delivery business
- Customer prefers to stay anonymous
Manager/User : Outdated
Operator : Customize reference data
Developer : Maintenance
Operator : Data upload during tour (GPRS ?)
Manager/User : GUI homogeneity
Developer : Modularity / separation
- NEventStore : A persistence agnostic Event Store for .NET. It is a persistence library used to abstract different storage implementations when using event sourcing as storage mechanism
- SQLite : small footprint, efficient
- OpenNETCF : low level CF stuff (connectivity, OS, etc.), frameworks
- Resco : MobileForms Toolkit for grids, tabs, toolbars, etc.
- SOTI MobiControl : Enterprise Mobility Management (EMM) and Bring Your Own Device (BYOD) Management solution ï deployment packages, retrieve files/logs
- Logon is done on premises, at the mail distribution warehouse
- Tour is prepared be each delivery agent by scanning all the products he has to distribute
- The agent goes out and starts his tour
- He has to distribute all the mail he has and collect some mail boxes on the way
A Tour has Products of different subtypes
Registered
Prime
Parcel
etc.
A Tour has as status that specifies if it is
Started
suspended
completed
A Product has a state that defines whether it has been
delivered,
notified (client is not there),
returned to sender,
etc.
Seems reasonable ?
- Designing domain with events requires that you think a little bit differently
- Login command with the tour code is sent to the tour
- The tour raises an event that it has been created
- Or that it has been restored if it had already been started before
A tour exists for a given tour code, at a given date
- Enlist command with product code is sent to the tour
- The tour checks if the product code is already enlisted
- Otherwise it raises a product enlisted event containing the product code and product type
- The product type is deducted from the product code
- Deliver product command containing the product code, customer name and signature when needed is sent to the tour
- Tour checks that all required information has been provided according to the product type retrieved from the product code
- Tour raises an event that product has been delivered to the given customer
- Nofity command containing the product code is sent to the tour
- The tour retrieves in which office the product will be notified according to the tour code
- The tour raises an event that the prodcut will be notified at the given office
- Return to sender command containing the product code and the reason of the return is sent to the tour
- Reasons : incorrect address, no mail box, customer refused, customer deceased, customer gone, unknown by agent
- The tour checks that this type of product can be returned to sender and that a reason has been provided
- The rour raises an event that the product has been returnred to sender for the given reason
- Logout command is sent to the tour
- The tour checks whether all products have been processed
- If itâs the case, tour raises an event that it is completed
- Otherwise, the tour raises an event that it has been suspended
- Combination of product types and delivery options by category is quite large
- In some cases, extra information is required, like signature, customer name or notification office
Product definition :
registered mail
Prime
small or cumbersome parcel
Amazon parcel
bundle of registered mail
- Delivered : product has been given to the customer
- Notified : customer was not there and we notified the he can come to a given office to get his mail
- Returned to sender : the mail will not be delivered at all
- Sent back to distribution : the mail has to wait to be delivered (hollidays, retention orders) or address needs to be checked
- Packup : Routed to a packup station ï considered as delivered to customer
- Several options per delivery category
- Reasons why the mail is returned to sender
- Reasons why the mail is sent back in distrubtion
- Combinations of product types and delivery options
- Delivery requirement specifies required data and drives UI behavior
- We still need 5 events to maintain the tour status
- Sync framework to synchronize reference data
- No need of event sourcing here because it is mainly CRUD
- ES would have been an overkill
TIME ï 12:15
- TimerDispatchScheduler : could not port AsyncDispatchScheduler because it is based on concurrent queues that does not exist in CF
- IDispatchCommits : needed a bool to check if events were dispatched properly, did not want to use exceptions for that
- System.CF : Had to reimplement some parts of the .NET Fx
- Encryption : legal obligation
- Pipeline hook : denormalize in memory, cannot have 2 denormalizers and needed the denormalizer to sync events on the server
- Implement generic interface
- Get read model
- Get necessary data
- Use event data to update read model
Implement IPipelineHook
Actions on events as denormalizers
PostCommit ï denormalize on the fly
Select ï when rehydrating
List of guid to keep track of denormalized commits (idempotence)
- Only used to retrieve aggregate id from tour code and date