Publishing events to notify about changes in a domain keeps different domains decoupled from each other, but if there really is a logical flow of events it becomes implicit and hard to follow. A better solution is to use a Process Manager to keep track of the overall process, Bernd Rücker stated in his presentation about long running processes and Domain-Driven Design (DDD) at this year’s DDD eXchange conference.
Rücker, with more than 10 years’ experience working with long running processes and co-founder of Camunda, notes that from a customer perspective, the process of buying something from an online store is quite simple; place order, pay, receive shipment. Implementing this using DDD perspective could result in four business capabilities and corresponding bounded contexts; Shop, Payment, Inventory and Shipping. These contexts can then submit domain events matching the buying process; order placed, payment received, goods picked and goods shipped.
To fulfil the ordering process the contexts need to collaborate and respond to the events submitted. When an order placed event is submitted, the payment context, subscribing to these events, will then collect money for the order before it submits an event that the payment has been done. One problem with this however, is that the payment context needs to be aware of the order placed event, and with all other events that should trigger a payment. In practice, this means that whenever a new client requires payment, the payment context has to be touched.
Rücker notes that a problem with event flows is that, although it induces a low level of coupling, there is no notion of a process. This makes it harder to see the overall logical flow, and he refers to Martin Fowler. Another disadvantage is that a new business requirement which causes a change in the event flow, for instance that VIP customers can order with invoice, may require a change in several contexts.
A better solution for Rücker is to keep track of the overall process in one place and use a Process Manager, with the main responsibilities being:
- Do event-to-command transformation. The order placed event is something that has happened in the past, and it implies something we really want to happen, a command, which in this example could be a retrieve payment command.
- Implement the flow as a first-class citizen of domain logic. The overall flow as well as the logic in each individual step are part of the domain logic in the contexts involved.
- Handle state for long running flows.
From an implementation view the most challenging for Rücker is state handling. Ways of implementing this includes:
- Actor model, using for instance Akka where an actor can have local state. One actor implements the process manager and is responsible for the overall flow. This is also described by Vaughn Vernon in his book Reactive Messaging Patterns with the Actor Model.
- Keeping state of the process flow in an entity. This is the most common implementation in Rücker’s experience.
- Routing slip where all required steps is sent together with the message. This avoids the need for a central storage of the process state.
- A state machine in which each step is defined. Rücker notes that one advantage you get using a state machine is the visibility of the state of a process.
In his experience working with customers implementing long running processes, Rücker has seen some common mistakes which include using no workflow engine or a homegrown one, and using zero-code suites. Instead, he recommends using an open source lightweight framework library, where Camunda is one of several, that can be embedded in your code. He also recommends implementing only the common cases, excluding the most exceptional cases. Taking care of maybe 99% of all cases and leaving the rest for human intervention is a recommendation also made by Greg Young.
Rücker has published an example on GitHub that implements a simple order system using the ideas from his presentation.
Next year’s DDD eXchange Conference in London is scheduled for April 26-27, 2018.