The Value of Value Objects
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
- Tackle the large problem in small iterative approach
- Generate the right conversations
- Get to Software Correctness
What’s the Problem?
What do we see here?
- Everything is Public
- Everything is a Primitive Type
- The names are acting as description
- The comments are acting as further description
- 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:
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:
- Domain-Driven Design: Tackling Complexity in the Heart of Software: Amazon.co.uk: Evans, Eric: 8601300201665: Books
- Implementing Domain-Driven Design: Amazon.co.uk: Vernon, Vaughn: 8601404568893: Books
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
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:
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?
- What are the rules for this? Can you convert MPH to Celsius? (Everything discussed about age)
- Can this value ever be changed? Quite often values are just there for the ride
- When can it be changed?
- 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:
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:
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
I ran a simple 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
.
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
- Don’t Throw Exceptions Don’t throw exceptions in C#. Do this instead — YouTube
- Treating Primitive Obsession Treating Primitive Obsession with ValueObjects | DDD in .NET — YouTube
Vaughn Vernon
- Implementing Domain Driven Design Implementing Domain-Driven Design: Amazon.co.uk: Vernon, Vaughn: 8601404568893: Books
Scott Wlaschin
- Value Objects Designing with types: Constrained strings | F# for fun and profit (fsharpforfunandprofit.com) Low overhead type definitions | F# for fun and profit (fsharpforfunandprofit.com)
Mcintyre321
- Value Of mcintyre321/ValueOf: Deal with Primitive Obsession — define ValueObjects in a single line (of C#). (github.com)
- DUs in C# mcintyre321/OneOf: Easy to use F#-like ~discriminated~ unions for C# with exhaustive compile time matching (github.com)
Modular Monolith DDD
LanguageExts