DEV Community

Aman Agrawal
Aman Agrawal

Posted on • Originally published at amanagrawal.blog on

An Exercise in Domain Modeling Guided By Strategic Domain Driven Design – Part 1

I have written about my pet project- my personal expense tracking application – before. This application can trace its lineage to VB 6.0 and MS Access database so that should tell you how long I have been working on this on the side. Spoiler: around 18 years!

When I first started on this project back in 2004-2005, I took a very database centric view of the design, starting with the entities I needed, mapping them to database tables in an entity relationship diagram, thinking about database interaction first etc. This is just how most people, including myself, built software back in the dark days (some people still build like that today, change is hard for some I guess) 🤷‍♂️.

Nonetheless, it is a functioning application that me and my wife use on a regular basis, and it also serves as a test/exercise bench for me to try out various ideas, practices and patterns. In around 2016-17, I redesigned the application using the DDD tactical patterns, you can read about that journey here. Because it had been 5-6 years since I created the current domain model, it was time to review and improve it based on my growth in learning about DDD in the intervening years. Its a good way to test if you have learned anything over time or just clocking more years.

⚠️ DISCLAIMER: the application is not particularly complex, the domain logic is not particularly complex. There are plenty of free and paid tools that do a fantastic job of tracking expenses and offer advanced capabilities, so I am not competing with them here. The point of this post is to showcase one of the ways DDD patterns can be applied: strategic and tactical ones, and how domain modeling can be approached, not how to build personal finance software. I personally and professionally find DDD very useful at various levels, in not only building knowledge of the domain but also reflecting that knowledge in code using the language of the domain.

Revisiting the Problem and Solution Space

In order to evolve the model, I needed to revisit the problem space, and this is where the application of strategic DDD comes in. Simply put the broader problem context can be defined as wanting to maintain good personal financial health and making sure I am able to meet expenses that are critical and minimise waste.

My broader solution context is keeping a track of expenses over time:


High level problem and solution spaces

In strategic DDD parlance this would be akin to understanding the business model and vision but something like a business model canvas was an overkill for my operation, so a broad understanding of the problem and solution contexts was sufficient to get started.

Big Picture Event Storming

Given this problem and the broader solution context, next step was to break down the domain of personal financial health maintenance into sub-domains i.e. areas of discrete responsibilities and capabilities that I can more easily reason about and design software around (or not). For this I used the big picture event storming technique:


Big picture event storm for the whole domain

In this exercise I mapped out the whole flow from start to finish using all the domain events and the roles that are involved. Of course in this 2 person operation of mine, there really are only 2 roles: Expense Administrator (me) and Expense Manager (my wife and sometimes me). There are no additional requesters because this is not a general purpose system, but I still wanted to visualise that part for completeness sake.

The BPES primarily shows only domain events across the entire business, but I also find it helpful to:

  • Highlight the various user roles that are responsible for generating these events (small yellow stickies). Typically, if the same user role is responsible for triggering a group of related events in close proximity, it might generally indicate that those events should be within the same sub-domain
  • Identify pivotal events (ones with green vertical bar in the above storm) i.e. the events that trigger other processes downstream potentially owned and operated by different user roles may be in different sub-domains or bounded contexts. These are identifiable by a change in language used (i.e. “expense manager login credentials sent” –> “budget opened” –> “budget summarised”). And,
  • Indicate passage of time where the events in a sequence are not strictly required to happen right away but can have a large time gap between them (think: workflows), because that can help identify aggregate boundaries that will be crucial from a data consistency pov. Things that don’t have to happen together don’t have to be immediately consistent, those can be prime candidates to be broken up further.

Taking into account the above indicators, I created the following groupings of event stickies, each representing a potential sub-domain:


Big-picture event storm with first draft of sub-domains identified

  • User administration sub-domain is responsible for registering users into and deactivating them from, the system. It also sends the newly registered users their login credentials. Without this crucial first step, users cannot use the system. People operating in this sub-domain are called Expense Administrators
  • Budgeting and Expense Tracking sub-domain is responsible for opening new budgets, allocating pots and tracking expenses against them. Eventually the budget will be closed when the time is right. People operating in this sub-domain are called Expense Managers.
  • Budget Summarisation sub-domain is responsible for generating summarisation reports from active and closed budgets. This serves as a useful trend tracker for spend vs saving per budget. These summaries are generated by the system responding to various events from the Budgeting and Expense Tracking sub-domain.

Looking at this grouping of sub-domains and the processes they are responsible for, each sub-domain mapped pretty much one to one with a bounded context i.e. an area of the sub-domain with consistent language and meaning of terms, that communicates with other bounded contexts using messages (synchronous or asynchronous), without revealing its internal complexity and details (hence the “encapsulated” word).

Note: in other more complex environments, this may not always be true i.e. a single sub-domain might comprise of multiple bounded contexts depending on the complexity of the business processes and value streams. I am writing another article on a real life example of such a situation, so watch this space!

There is no single best or correct split for bounded contexts and/or sub-domains, there might be multiple models that might work at different levels. Key thing to bear in mind is that models do not reflect reality 100%, they are only an approximation of reality and as such make some simplifying assumptions and put some constraints to help us design a working software system.

This doesn’t mean we play fast and loose with models, it means it might not make ROI sense to model reality 100% in a software system otherwise complexity would be too great, may be 90-95% cases covered can be a good model and the remainder get handled offline where feasible. The “correctness” or the suitability of a model, is directly proportional to its ability to solve the business problem at hand and the problems that are likely to arise in the future. The first couple of cuts are likely to be wrong or sub-optimal, so starting somewhere and iterating over time is a good idea.

Process Level Event Storming

Since we are talking about designing software, the big picture event storm is a good starting point to map the overall complexity of all key flows and identify groups of capabilities but it doesn’t give me the details necessary to converge onto a potential software design.

Therefore I need to dig deeper and discover the internal details of each of these bounded contexts, what actions are taken, with what information, using what systems, what are the reactions to various events etc. For this I used the Process Modelling variant of event storming which introduces more coloured stickies (blue for command, green for read model or data, pink for external systems, lilac for business policy, yellow for aggregates). Putting those puzzle pieces in, I got this:


Process modelling event storm (don’t try and read the text, I will explain separately)

I will narrate the story line quickly for each bounded context:

User Administration

A requester that wants access to the system, asks the expense administrator (EA) for access. The EA uses the requester’s first name, last name and their choice of user name, and a secure password to register the requester as an expense manager via the user administration system. When the expense manager is registered successfully, the login credentials are given to the new expense manager. The expense manager is now ready to use the budgeting and expense tracking system.

If the requester wants to stop using the system, they ask to be deactivated and the EA deactivates them via the user administration system. This is the terminal state for this expense manager.

In both use cases, an action failure triggers a retry until the operation succeeds.

Budgeting and Expense Tracking

The expense manager logs into the budgeting system and takes the following actions:

  • The system is empty or system has some past budgets but no active budget. In this case, the expense manager first opens a new budget with certain start and end date. This triggers the budget opened event. They might then decide to allocate expense pots right away or decide to do it later. Allocating a pot triggers a pot allocated event.
    • If they make a mistake whilst allocating a pot and the pot has no expenses , they can deallocate the pot and reallocate the correct one. Deallocating a pot triggers a pot deallocated event
    • If they make a mistake whilst allocating a pot and the pot has expenses , the system will not allow deallocating that pot because then its not clear what should happen with the expenses. Expenses are immutable, they happened in real life so we can’t just remove them.
  • The system already has an active budget and allocated pots. In this case, they might decide to add expenses using information like date of expense, amount spent, description and pot. For each expense added, an _ expense added _ event is triggered
    • If they make a mistake whilst adding an expense (e.g. added to the wrong pot or with wrong details), they can reverse that expense and re-add it correctly. Once again, expenses are to be treated as immutable because we should have a full log of expense activity for reconciliation or diagnoses later, so editing an expense or simply removing an expense is not allowed. Reversing an expense triggers an expense reversed event
  • The system has an active budget that has reached its end date, at this point the expense manager can close the budget which triggers the budget closed event

I cheated here a little bit, for this part of the event storm I used the Design Level Event Storming by bringing in aggregates, instead of using generic systems, just because its the bounded context I am focussing on for remodeling. In the new revisions of this technique, they have replaced the word “aggregate” with “business constraint” but for my case I will stick with the former.

Budget Summarisation

In order to keep track of spend vs saving trends over time, this bounded context listens to some key events and creates a projection for these data points:

  • Pot allocated/deallocated (to keep a track of total allocation per budget)
  • Expense added/reversed (to keep a track of total spend per budget)
  • Budget closed (to trigger the calculation of total savings per budget)


Events can be used to calculate aggregated values

These calculations can be done in the following ways:

  • Continuously as a budget undergoes relevant changes (asynchronously)
  • At regular intervals by summarisation context pulling information from the budgeting context (batch), or
  • Just in time when an expense manager asks for it.

It depends on how computationally expensive these calculations will be and whether or not the expense manager can deal with eventual consistency i.e. this summary might be updated a little bit after the budget undergoes a relevant change. It also depends on how many other consumers are there for this information. The more computational expensive these calculations are or the more consumers of this information there are, the more sense it might make to build this in an event driven way. Otherwise, the simplicity of synchronous on-demand summary generation might suffice. This sub-domain is not the focus for the re-modeling exercise for now, so I won’t spend any more words on it.

Just because you do an event storming session and discover all the domain events, doesn’t always mean that all events need to be implemented in code and asynchrony introduced where it doesn’t bring specific value. Event driven mindset is more important here so we know that if the situation changes, we can switch to an event driven model as well. I find it valuable to not constraint the system design too much to a specific approach because that takes optionality away which reduces engineering agility. A good design decision is also reversible or is able to be implemented without significant rework, if I am not sure an event is valuable to be implemented in code but I feel it should be implemented because some day we will need it, I generally won’t add it. I would instead make sure that adding that event later is not going to be a gargantuan effort. Push comes to shove, the most I’d do is create the events in my domain model code but only record them in-memory and not publish it outside of my bounded context, until a use case becomes clearer. For this exercise, I opted for this approach but its possible that in a work setting this approach might be seen as overkill.

In the next post, I will zoom in on the core budgeting and expense tracking bounded context and talk about the current domain model implementation, the other alternatives I considered as a part of the review exercise, the final design improvements decisions I made and wrap up with some takeaways.

Stay tuned…

Resources

Top comments (0)