Software design towards simplicity with value objects

11 / 04 / 2023

Read Time 20 minutes

Well written software design that is a joy to read and takes a complex problem and presenting it in a well thought out simple structure is rare. I want to draw your attention, not to the great design buzz words but to arguably the lowest part of your system. If you are working in a strongly typed language, how well are you using your type system? Domain models littered with primitives are a good indication that you are not taking full advantage of your programming language.

TL; DR: If you are using primitives in your domain models, you are not using your type system to its full potential. Replace the primitives with value objects, to create more robust software design.

What is a value object?

  • It is immutable.
  • If all its properties are equal, it is equal.
  • It validates itself.

To make this more practical, let us take an example.

The domain we are dealing with works with financial transactions in multiple countries. There are two entities, an account and its associated transactions. The account has information about our agreement with the client like the agreed-upon finance fee, and the transaction has information like the transaction amount.

What can we safely say about this class?

  • A valid account might have any combination of the following values
    • AccountFinanceFee
    • AccountTerm
    • AccountAdminFee
  • We could have an account Term of -5000.
  • What is that Term? Years, days or seconds? It is not apparent looking at just this code.
  • What is the fee, we know that if there is a fee, we can very precisely tell you how many, but
    it is unclear what it is? Perhaps it is cows.
  • We will not be able to use this class without a bunch of if statements.

There seem to be two types involved in this class, the one is the Term, and that is an indication of how long the account holder has to repay his debt. The current data type does not make sense. How would it be possible to have a negative amount? The other thing is that we could have an
account where the Term has not been set at all. That is even more strange than a negative number. Why would we go through all the effort to create the account and not have a repayment term? We need a better type for this one.

The admin fee and finance fee have the same problem as the Term, how can we have an account without those values been set. Although the values are very precise, they are still missing some very vital information. We are assuming that it is a money amount, and we do not know in what currency that amount is. It might be possible for a developer to mistakenly assign a dollar value to the Finance Fee and another developer thinking that value is in Euro.

If we create a money value type, would that make our code better? Let’s first test out our idea. A brand-new money type would contain a value for the amount and a value for that currency. Then we would be able to say I have 40 Euro. In my book, 40 Euro is 40 Euro. This is how such a money value object could look.

I am using the value object here based on the work of Vladimir Khorikov and Steven Roberts

The model:


In terms of the account Term, we have a data type in C# that will work better than the existing one, and it is a time span. Let us combine these with our money value type and come up with an account that is easier to reason about.

This is better. For starters, we have made sure this object makes sense in the domain. We cannot have an account with a NULL term. We also have been able to make sure that we cannot make accidental mistakes like setting the admin fee to pounds and the finance fee to Euro.

Now that we have done a simple example of the benefits, let’s look at a more involved example.

The next example is doing a simple calculation, using the  version of code without the value objects

Let’s first look at the formula

This is how it was implemented:

Some notes on this code

  • It is tricky to follow if the code is doing what the formula is saying it should do.
  • There is a lot of hardcoded values and extracting them to constants will not make this code much better.
  • There is a method that is being used for making sure that the formula can be calculated, this means that there are quite a few places to check if you can use a value.
  • There are nine places where there is potential for null reference exceptions; there is an attempt to mitigate these exceptions by checking if the calculation can be done with “IsTransactionFinanceFeeCalculatable”.
  • It will be hard to modify this code without reading that method to ensure that the value is not null.
  • The question is really “why are there 9 potential nulls?”, how is it possible to have so many nulls in so few lines.

Let us improve this by introducing several value types

  • Percent
  • Rate
  • Tax

There is a couple of things we still need to add to our types to get them where they can be super useful. We need to tell our value types how they can interact with each other. For example, in our formula, we have AdminFee x Tax. Do not do the calculation in the code, do the calculation in the
type, because that is how you get code re-use and the benefit of never ever having to think how money and percent interact in your system.

Adding this operator overload does that for you.

That then allows us to change our transaction to this, that makes it easy and straightforward to focus on what is essential.

Do I still need a developer to read the code and tell the domain expert what it is?

In Summary

Primitive types of software design have no place in our domain models, strings and integers are terrible to model the complex world of our domain languages. Do not allow external factors like the infrastructure libraries to dictate to you how your models should look. No amount of clean architecture will fix models that could just as well have been json files in your software design. For Assistance with your next software design project, get in touch with Haefele.

Source code: https://github.com/RihanMeij/ValueTypes

References

Opinions

Books

  • Domain-Driven Design: Tackling Complexity in the Heart of Software – Eric Evans
  • Applying Domain-Driven Design and Patterns – Jimmy Nilsson

Research

View
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies.
Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website.

These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.

Necessary

Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.

Non-Necessary

Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.