Very often within big organisations there is this convincing need to improve existing software, catch up with new technology, generally speaking, modernise existing IT infrastructure. Undoubtedly, it is a huge task and even the greatest effort to plan modernisation according to business served by the company. The worst-case scenario is a more complicated design and countless boundary dependencies.
This article explains the approach we took to design and implement Rewards and Benefits systems for the global retailer company. It covers Domain-Driven Design strategic techniques used to distill models together with adequate system boundaries. All of it resulted in a fully functional, understandable, and modernised system.
A reason behind understanding the domain
Almost all large organisations struggle with the increasing complexity of their domain. This is an inevitable side effect of the company growing, expanding its domain and evolving the way it operates. Also, from the technical perspective – adding new features, integrating more systems or changing existing ones leads to the creation of a more and more tangled web of dependencies where no single person can ever understand the software as a whole.
Complexity in software is the result of inherent domain complexity (essential) mixing with technical complexity (accidental). – S.Millet
As a result, adding new blocks to the organisation’s systems is increasingly more complicated, risky and expensive. Sometimes, it is better to pause for a moment to understand the environment around. Blind assumptions about how business works may lead to overcomplicated design to an already complex system and technology-driven design not necessary will fit the business needs.
As an example, we would like to show you the journey we took to understand and design ‘Employee Rewards and Benefits’ system for the global retailer company and explain why strategic Domain-Driven Design played a huge role in our project to deliver the service that not only ‘works’ but also fits perfectly within already existing surroundings.
Find your place
We all know that hierarchy is almost unavoidable while working in a big and multinational organisation. A well-known Conway’s Law states it clearly.
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure. – Melvin E. Conway
Our team was in a very similar situation. Fortunately, there are ways to work efficiently even in such an environment. Firstly, let’s think about what the main focus is and what requires the biggest effort.
Fig. 1: Too wide Retail domain
Obviously, for the retailer company, everything is related to retail. Nevertheless, “everything” sounds too large. When we took a closer look at the current site of internal APIs a little better representation was discovered.
Fig. 2: Organisation silos
Some sets of API are more customer-facing when others are more employee-facing. The Employee Experience might be a good candidate to put it in later. But, thinking about our area of interest which is Rewards and Benefits for employees it wasn’t so obvious. At the very first kick-off meeting, we heard about our first goals – Discount Cards and Savings. It didn’t take long to understand that Discount Cards not only exist for employees but also for customers. There is a relation that each employee can be also a customer and some customers might be also an employee.
Fig. 3: Overlapping Rewards and Benefits domain
It meant that our area of interest is somewhere between the employee and customer silos. We didn’t feel quite comfortable with such overlapping vision. At that moment we started digging deeper into existing structures, business units and tried to align it with strategic Domain-Driven Design patterns like domains. Good inspiration to visualise domains are DDD Domain Charts
Fig. 4: Domain chart
As presented in the chart [Fig. 4], we can distinguish between:
- Core domain – a part of the system which brings the highest value for the business
- Supporting domain – a necessary part which accompanies the Core domain
- Generic domain – a part which represents common concepts which can be implemented using SaaS or other out-of-shelf product
Fig. 5: Domains on the chart
The above chart is a result of multiple meetings with a relevant representative from the Employee Experience, Rewards and Benefits, HR, Payroll and Customer spaces. For us, it was very important to understand where our solution will sit, thus where our problem space is. The diagram depicts the importance of each domain and where it is located in terms of complexity.
Our space supports a core business. It needs to know about Customer Discount Cards and is driven by the Employee Work cycle. The generic Payroll and HR domains influence both Employee Workcycle and Rewards and Benefits.
Discover and learn
So far we have discovered where potentially sub-domain of Rewards and Benefits sits. Let’s elaborate on useful tips that help with discovery.
- Take a look at the current organisation structure – in our case, it was a notice for Employee and Customer facing solutions
- Try to find relevant experts in each area – domain knowledge so how business works is crucial for the discovery
- Business value should be different in each domain – Rewards and Benefits sub-domain serves a different purpose than i.e. Payroll
- Look for specific examples – avoid abstraction, even if employees can have Discount Card, such a card will have additional benefits
- Variability of business rules in the business process through domains – a process to issue Discount Card for an employee is different than for customer
Another game-changing approach to discover and learn is Event Storming, specifically Big Picture at this phase.
Fig. 6: Big Picture Event Storming for Rewards and Benefits domain
We as a team would like to understand what business says and business would like to communicate its ideas clearly. The Big Picture Event Storming is a perfect tool to start naming and grasping concepts from the ubiquitous language. Each domain event visible in the [Fig. 6], represented as an orange sticker, should reflect an important behaviour within our domain. It should follow the language spoken by key stakeholders. Taking a look at explored domain events, you might notice that visible concepts are more about entitlements. For us, this discovery was eye-opening because it brought a totally different perspective on what we are going to implement. Thus, it was very important to invite the Big Picture Event Storming session to the most relevant people from our project perspective. In our case, a mix of below individuals worked quite well.
- Solution architect – has a wide vision about flows between systems
- HR representative – an important persona from the perspective of the employee work cycle
- Benefits admins and stakeholders – they understand how particular benefits should work according to employee work cycle
- Engineers – our team, eager to understand dependencies and requirements
- Payroll representative – it turned out that he gave a good explanation on how Savings influences Payroll
Although Big Picture Event Storming is an exhaustive activity that consists of many steps, we learned a lot about flows, reasons and started speaking the same language. In this chaotic exploration, we discovered not only domain events and their sequence but also who and what external system can be involved in the process and how it changes over time. For example, the same physical employee in one place is called just “Employee” in another “Primary Card Holder”. Even if the result wasn’t perfectly layered, for us it was a good enough starting point to think about model, boundaries and dependencies.
The next steps were to enforce a timeline better and find processes boundaries.
Fig. 7: Big Picture Event Storming with bounded contexts
As you can see in the [Fig. 7], yellow straps divide the board into logically connected pieces. In the Domain-Driven Design terminology, such pieces are called Bounded Contexts. First of all, a Bounded Context is a linguistic boundary around the meaning of the domain model. Furthermore, it is a natural software boundary, so we’ve just entered the solution space.
To achieve it, we followed below heuristics:
- Find pivotal events – those are events that are marked with horizontal, yellow straps. They indicate that some new process starts in this place and often the same event is named differently in another bounded context.
- Put swimlanes – swimlanes are represented by vertical, yellow straps and show borders of specific contexts. Very often different bounded contexts from the same domain do not interact with each other like Savings and Discount Entitlements.
- Identify a core value – this a place, or a domain event which is the most important for the business. For instance, Primary Discount Card Issued as it ends some process.
- Look for autonomous decisions – a good bounded context should not ask another context for permission to proceed. All needed information should be already available inside it.
- Search for data used and changed together – in case if one change always triggers others, both should be placed in the same context i.e. Primary Discount Entitlement Associated and Secondary Discount Entitlement Associated.
- Follow the business process – count how many bounded contexts are involved through the business process – it is better if a single bounded context realizes a single domain.
- Define responsibilities – try to describe responsibilities of bounded context with a single sentence like “Savings bounded contexts is responsible for opting in and out to the savings scheme by an employee who can contribute a percentage value”.
- Justify integrations – it is worth asking if all integrations are necessary and understand a reason behind them
- Ensure a single source of truth – ask yourself if all business details about given feature are available inside the single bounded context
- Review conditional requirements – avoid repeating the same condition at the beginning of each process i.e. we could each time an employee joins or leaves checks if he/she is permanent or a contractor as the process differs on that information. Instead, model those flows separately
In our domain of Rewards and Benefits, we distinguish between below Bounded contexts.
Fig. 8: Big Picture Event Storming with bounded contexts boundaries
- Savings – green
- Permanent Employee Discount Entitlements – orange
- Contractor Discount Entitlements – purple
- Employee work cycle – blue
- Fraud investigation – grey
- Payroll – red
- Discount cards – represented as an external system
As we learned, there are some dependencies between them. Such dependencies could influence architecture, collaboration and business feature planning. In order to understand existing relations between we used a context map.
Fig. 9: Context map
We can distinguish between patterns such as:
- Shared Kernel [SK] – a model propagation where two bounded contexts share a common domain model
- Upstream-Downstream [U,D] – a team relation where actions of the upstream team will influence the downstream team while the opposite is not true
- Customer-Supplier – an upstream-downstream team relation where the downstream team has some influence on the upstream team
- Open-Host Service [OHS] – an API level integration pattern where a bounded context exposes several functionalities with a common model consumed by downstream bounded context
- Published Language [PL] – an API and model level integration where bounded contexts share a well-documented language to translate a model from and to it.
- Anticorruption Layer [ACL] – a model translation layer which adapts the incoming model to model from underlying bounded context
- Partnership – a team relation where two teams establish a process for a collaborative cooperation
As you can see on the map [Fig. 9], Employee Workcycle is an important bounded context but only acts as an upstream team relation. As a result, we decided to build an anticorruption layer inside each bounded context to ensure that we operate only on our own model.
In our organisation, most Open-Host Service patterns are implemented using RESTful architecture. Undoubtedly, it was a straightforward way to plan integration with Discount Cards and Fraud Investigation. However, the latter team works close to our domain and we would have liked to adjust our API to their needs.
The Payroll team is our partner in the delivery. In case of integration with Payroll, we established a well-structured file exchange as it was the only supported option.
When planning a work needed to implement particular features from the bounded context it is highly recommended that in a single bounded context works only one team. Our cross-functional team owns Savings, Permanent Employee Discount Entitlements and Contractor Discount Entitlements. Therefore, a Shared Kernel pattern for a common discount entitlement model sounded reasonable.
Plan and implement
Let’s iterate over already discovered artifacts:
- We found our place in the organisation and grasp an understanding of what our domain is.
- We discovered domain events, business processes and boundaries thanks to event storming.
- For each boundary, we established relations to integrate with external dependencies.
Now it is time to think about some software to be implemented.
Around discovered bounded contexts, we can start designing architecture, organise codebase, build integrations, populate metrics and finally implement the domain model.
In the era of microservices, a blind approach might be to build a separate microservice for each bounded context. For our purpose, we decided to at least have 2 – Savings and Discount Entitlements. They don’t interact with each other, have different models and reasons to exist. Nevertheless, they have one thing in common – integration with the Employee Work cycle. It was a good opportunity to build an anticorruption layer once as it serves the same purpose for Savings and Discount Entitlements.
Next, there is a shared kernel model propagation between Permanent Employee Discount Entitlements and Contractor Discount Entitlements. That is a perfect fit to a module shared between them and each of them can be implemented in a single codebase but in separate modules. Especially, that language used in both of them is the same, just different use-case and set of rules.
Looking at the bounded context complexity in terms of domain events sequence and flows, it easy to notice that Savings context looks much simpler than Discount Entitlements. It is a valuable indicator for simpler patterns even like CRUD compared to Discount Entitlements where maybe tactical Domain-Driven Design building blocks suit better. We encourage you to proceed with the below steps to even better understand requirements:
- Try Process and Design Level Event storming
- Grasp non-functional requirements
- Discuss a business and technical metrics
- Establish SLA and SLO
- Understand release and deployment policies
- Collaborate with upstream teams to plan work accordingly to timelines
All covered patterns and above additional list of steps will help you to create a tailored-made software reflecting real business needs.