This blog describes the journey our Production Engineering team went through to bring Domain Driven Design concepts into their project to help solve some of their problems.
Who We Are
The Production Engineering team are responsible for picking up customer orders created through the web or app interfaces and automating the steps required for them to get to the factories for printing and despatch. This includes preparing the artwork for cards and gifts to be printed with customers’ customisations as well as integrating with third-parties that fulfill non-customisable gifts.
What Were We Doing
When I first started working in the Production team, before we knew what Domain Driven Design was we had a mandate to drag our legacy systems from a collection of windows services and thick client-server winforms apps, kicking and screaming, onto a scalable, cloud based platform.
Problems We Faced
We initially took the approach of analysing the old system and doing a lift and shift of code into services, however this proved to be problematic.
Autogenerated Stovepipe: We faced the problems with a lot of legacy code that was not suitable for a distributed system and a lot of dependencies made it difficult to isolate.
Architecture by implication: We assumed we understood requirements from the legacy solution and so didn’t need user stories, just technical tasks. We made many assumptions but then found ourselves having to spend days trying to unravel legacy code, eventually realising it was impossible to estimate. After a while we just said that anything involving a re-write of legacy code could not be estimated.
Also by using the legacy system as a template we were assuming that it was doing the job correctly. However, in supporting the systems we knew that sometimes the workflows was defined by how the tool worked. Effectively the software was directing the job as opposed to supporting it.
This also led to a bottom up approach, resulting in highly complex logic trying to solve many problems with a single solution.
Analysis Paralysis and Design by Committee: We often had differing opinions on expected behaviour and responsibility, which led to long discussions about small details or deferring decisions because we couldn’t agree or didn’t understand the context.
Symptom Solving Not Problem Solving: We localised analysis to only those parts of the code that we thought was the most inefficient, never really looking at the bigger picture. Often we were just looking at symptoms not really understanding the problems and never really convinced we were delivering a complete solution, just trying to get something out the door to try and keep momentum.
Adopting a Domain Driven Design Approach
We decided to take a more radical approach and agreed to revisit the problems of the past and attempt to start from step one, identifying the real problem behind the original solution and working with accurate core use cases.
We saw some benefits from adopting a Domain Driven Design approach, mostly based around the actual modeling and attempting to bring more context into the problems we were addressing.
We were enticed by the use of a ubiquitous language not only when communicating requirements but also in our code and throughout our solution.
The Product Owner was encouraged by the prospect of writing more user stories and making story review sessions more efficient and effective, resulting in better visibility and more accurate requirements.
A focus on understanding the domain would also give developers a way to improve the quality of feedback loops, encouraging a rich dialog with users, and allowing us to ask the right questions to unearth deeper problems and guide design decisions. We also hoped this would help avoid analysis paralysis and reduce the time from story to implementation.
Modelling with Domain Driven Design
We decided the modelling phase would bring the most value, even if we didn’t go down the Domain Driven Design route completely, and so went about modelling the problem and business domains.
We focused on a single core use case, with a simple problem: “We need to produce and ship a personalised card”.
We started by mapping the process of an order in and out of the fulfilment workflow. We followed an order through our factory, talking with users and managers and researching industry standards.
We were able to use event storming techniques to identify language, important events and actors so we could begin modeling the domain boundaries and identifying where work was being automated, performed through legacy tools or manually performed.
To model the problem domain we mapped out core domains, breaking them down to sub-domains and defining the way in which domains collaborate.
We were then able to map the problem domain against the existing business domain, identifying where contexts overlap and identifying the way collaboration occurs currently.
This helped us identify complexity and hypothesise on ways to simplify it.
We identified aggregates and their responsibilities and identified from the language, a rudimentary domain model for each of our sub domains.
To get a more holistic view of the problem we mapped out external domains that collaborated with fulfilment and discovered a particular aspect of the business model that looked unnecessarily complex. We were able to identify that a change in this complexity could simplify not only our model but also that of several other domains and improve the quality of the product, giving not only operational value but also value through customer satisfaction.
The mapped domains also help identify impact, so if we wanted to change one domain we could better see what would be impacted by it. We also identified where collaboration with the legacy data model was necessary, thus identifying the need for adaptors/anti-corruption layers.
We were keen to use context maps to identify our domains and collaboration, though these were to be kept very simple and not overloaded with information. This led us to using multiple context maps to describe how specific data flowed through the domains of our business.
We eventually reached a point where any further design offered little value and so stopped and discussed the best starting point.
We had a requirement that we had been working on which involved improving the efficiency of producing print files.
We decided this would be an ideal opportunity to look at the problem with a holistic approach and identify impact within the domain.
We identified a subset of sub domains, one of these would be the domain which acts as our entry point into the flow, two of which would be new domains specific to the work we were doing, another was a domain that consisted of some supporting services that we had already migrated successfully to the cloud and a 3rd party tool that was represented by its own domain.
We also identified the Sales domain as being the domain from which we receive orders, and a couple of Customer domains that we needed to collaborate with.
The bigger picture model still remains as a complete view of the bigger problem of fulfilling an order, however some new sub-domains that had been identified were left out, so as not to add unnecessary work.
This pretty much marked the end of our initial investigation and design phase.
At this point, with a keen understanding of what we were trying to achieve, we began implementing bounded contexts for the new sub-domains in the model.
This is where my story ends and the next story begins.