The Value of Value Objects

Callum Linington
7 min readMar 8, 2023

--

I’ve given a few talks on “The Value of Value Objects” where I outline the importance of Software Correctness and how we can achieve that with our C# code.

The idea is around, how can we start doing DDD effectively in the middle of a project that isn’t doing DDD. I feel like this is a more common scenario then “Lets start off this project with DDD”.

There are a few tactical patterns that are a part of DDD, and I picked up on Value Objects probably being one of the best places to start. It allows us to

  1. Tackle the large problem in small iterative approach
  2. Generate the right conversations
  3. Get to Software Correctness

What’s the Problem?

Basic Record

What do we see here?

  1. Everything is Public
  2. Everything is a Primitive Type
  3. The names are acting as description
  4. The comments are acting as further description
  5. Similar Concepts are broken down to smallest component parts

Why are these all problems? We’re breaking Encapsulation, a.k.a. does the developers of the system really need access to all that data? Primitive types don’t allow the compiler to help you, consider this:

Strings aren’t a match

Since when is a name an email — well the compiler doesn’t care. This is why the previous record example was annotated with comments and descriptive names — “to help prevent you from messing up”. But when you hit ctrl + shift + b and get no errors… well, nothing is wrong, right? We could just rely on tests, no? Writing a false positive or a false negative test is quite easy, and can be done accidentally, so we can’t always rely on testing. In this scenario, we acutally really don’t have to — and in fact we can make our testing so much easier.

What’s a Value Object?

If you haven’t read:

I highly suggest setting some time aside to go through them. However, this isn’t necessary here, I will just paraphrase from Implementing DDD (IDDD).

Value Objects may posses just one, or a number of individual attribute

Value Object
The attributes of a Value Object

Now this uses the awesome library ValueOf to create a Value Object. Here you can quickly see that we have a Celsius object (now a compiler recognised type) which is backed by decimal .

As IDDD states — Value Objects are a vital building block of DDD. We’re allow the developers to express the Domain Language into the code, this way both Product and Development speak the same language through all the stacks.

Value Objects should be immutable. So, given a Celsius value object and you want to give it addition behaviour, the public Celsius Add(Celsius valueToAdd) would be the appropriate signature.

IDDD also states — A value object is a concept that measures, quantifies or otherwise describes a thing in the Domain. So, Age isn’t really a thing but measures or quantifies the number of years the person (thing) has lived.

Value Objects should also employ value equality, such that if the values of each attribute on both value objects that are being compared are equal, then we can say that both value objects are equal.

Illustrating the Point

In C# the String class can hold about 2GB of data, or about 1 billion characters.

It is used to represent:

  • Salutations, Names, Address Lines, Postcodes
  • URLs, File Paths
  • Hashes, Tokens
  • Usernames, Passwords
  • A Word, a Paragraph or a Book

The Int struct can represent whole numbers between -2,147,438,648 to +2,147,438,647.

It is used to represent:

  • Age, Weight, Height
  • Price, Quantity, Count
  • Length, Speed, Temperature, Percentages

But to be clear:

This doesn’t hold true

Also consider this, if used for Age , can someone be 2,147,438,648 years old? Well, it depends? Context is very important, are we storing the age of rocks collected from the universe? Potentially, the lower bound is fine. Are we storing the age of dead icon people throughout time? Possibly, the lower bound could be too much. Are we storing the age of people who currently have an active bank account? Then I guess there maybe a restriction when it comes to age and the both bounds will just not work. Can something be a negative age? What does that represent?

The Discussion

One of the most fundamental parts of software craftmanship is having the right conversations. There are many practices we can use to start generating the right discussions, Architectural Decision Records are definitely one of them, and right up there with it is Value Objects.

So what questions could be asked when thinking about Value Objects?

  1. What are the rules for this? Can you convert MPH to Celsius? (Everything discussed about age)
  2. Can this value ever be changed? Quite often values are just there for the ride
  3. When can it be changed?
  4. How should it be represented?

The major breakthrough with this is who are you going to ask to get this information? Now we’re generating conversations with Product and the Business.

Architecture

The next question, once you’re bought into the idea of Value Objects, is what does the overall picture look like?

Consider Hexagonal, or Onion, or Ports and Adapters architecture:

Hexagonal Architecture

The point of this is that right at the heart of your software is the Domain. This is where we should have all the Value Objects and no primitives (database Ids included…).

Usually the data flow paths look something like this:

Path of Data

Once they’re in the Domain, either the Aggregate or the Domain Service can do what it needs as per your business rules. Then this path gets reversed…

For the real world implementation this leaves you with two potential paths

JSON => DTO => Value Objects => Domain Model

JSON => Value Objects => Domain Model

The decision is either, have a Data Transfer Object which houses the primitives to make JSON deserialisation easier. Or, suck it up and create some converters or a converter factory that handles value objects and go straight from JSON to Value Objects.

I prefer the latter, the former is just as much work, also leaves you wide open for primitive obsession.

The latter is actually fairly easy in .NET.

Implementation

Simple Name Converter
Minimal API with Customer Converter

I ran a simple echo test

Postman Echo Test

Now, you could go via the JsonConverterFactory route, because what you’ll notice is a lot of the time you just use From .

Generic Json Converter
Simple test for the Converter
Green all the way

Conclusion

It’s very important to have the right conversations and draw out the right information on how something is to be modelled. It’s equally important to make sure those concepts are accurately represented, name and all (business rules, etc.). We can’t always start at the beginning, we can’t always start over again, but what we can do is create a roadmap to take us from one place to another — sometimes you can’t see where that next place is, but there’s always a next step, take the next step.

For me, Value Objects are the next step towards: getting to Software Correctness, getting to Domain Driven Design, getting to Ubiquitous Language, getting to common understanding.

I’m interested in understanding what you may think is a better step, or another step, please leave your comments below!

Extra Links

Nick Chapsas

Vaughn Vernon

Scott Wlaschin

Mcintyre321

Modular Monolith DDD

LanguageExts

--

--