tl;dr
In this post I continue my progress towards an DDD version of my favorite pet project. In this iteration I add a way to check the budget against the real expenses. The code is on GitHub and the picture contains the new model.
Continuing Domain-Driven Design
In my previous blog post I started to apply Domain-Driven Design to my favorite learning project. The quick summary of that post is that I started to create a Ubiquitous language and designed two aggregates.
Checking the budget
The main goal for the application is to check my expenditures against a budget for a certain category. To quote (a part of) the ubiquitous language:
To achieve financial insight the application needs to import and categorize financial transactions. I want to set budgets for a year and compare them to the actual amounts.
In order to check if my expenditures are still on budget I like to execute a method on Budget like budget.remaining(). To easiest way to calclulate the budget.remaining() is to substract the amount used for a budget from the amount planned.
To implement this method I need information that is stored in two aggregates. The actual expenditures are stored in the Category aggregate and the budget amount in Budget. I found several ways to implement the method:
- Add the category repository to the budget aggregate.
- Rethink the aggregate boundaries.
- Make an application service to coordinate.
- Make a domain service that calls the repository from the budget.
- Using eventual consistency in combination with domain events.
Add the category repository to the budget aggregate
I can add the category repository to Budget, however this seems mixing responsibilities. Of course it will work. If I look at the internet this seems bad practice.
Rethink the aggregate boundaries
It is also possible to rethink my original design of aggregates. I could make budget the aggregate root that includes category and thus the transactions. This would make it easy to calculate the budget remaining. However Budget and Category have a different life cycle. A Budget is bound to a specific year while a Category typically will live for many years. Therefore this option is not the right one.
Application service to coordinate everything and do the calculation
I can create an application service that will coordinate the change and let it update both the category and the budget. In my opinion that sounds too much like procedural coding and is not a viable option.
Make a domain service that calls the repository from the budget
Another option is using a domain service that indirectly calls the repository. Linking the domain service in the entity is allowed. Although this seems actually not really different from including the repository, just another indirection. Therefore not an option for me.
Using eventual consistency in combination with domain events
The final option is raising an event every time a transaction is added. A separate event listener can then update the budget. This way I realize separation of concerns. The downside: some “infrastructure” is needed before it actually works. This is for me the cleanest option and thus the way forward.
However in this option there are some extra things to think about. A category spans multiple years and a budget is specific for a single year. So the question is which budget needs updating. There are several options for solving this.
- Loop through all yearly budgets for a specific category and update all budgets based on the new amount.
- Make the event specific for a year.
For now I have chosen for the option to loop through budgets for a specific category. Mainly because this is easiest to implement. Otherwise I need to compose a complex event that is targeted to update budgets including at least the transaction year or worse the whole transaction. That feels like mixing the aggregates a bit. Next to that by making the event more generic it is easier to reuse later if that is needed. By choosing this approach the consequence is that performance will take a hit.
Work to be done
Currently only the field amount is stored in budget. So now I need to change amount into amountPlanned and introduce amountUsed. Furthermore I need to create an AmountUsedUpdater that acts as an event listener to update the amountUsed. I didn’t use amountSpent because I have also some budgets for my income.
Conclusion
In this blog post I show the way I implement the comparison between the budget and the actual expenditures. The code is on GitHub. The picture below shows the domain model.