💡 UPDATE 2024: This post described the performance hit on using a now-obsolete package named 'StringlyTyped.
ValueObjects'. There is a newer project that does not have the same performance impact, named Vogen.
But be aware that there are value object libraries still out there that do have the issues described in this post.
In my previous post, I covered what PrimitiveObsession is and how ValueObjects help. I linked to a NuGet package that provides a simple implementation of ValueObject in .NET.
In this short post I'm going to touch more on where to use ValueObjects and where not to to use ValueObjects.
TL;DR: don't swap all your primitives everywhere for ValueObjects because it's slower and uses more memory. If you do this, you'll end up with a new 'code smell', which I have just named ValueObjectObsession!
ValueObjects solve a particular problem but are not a silver bullet to be used everywhere.
Just to recap on the terms
- ValueObjects are one way of curing PrimitiveObsession
- PrimitiveObsession (emphasis mine): "Primitive Obsession is using primitive data types to represent domain ideas"
Probably the most important phrase here is domain ideas. In our pretend Billing domain, we have the following method (written by our young naive selves before we realised and addressed our PrimitiveObsession):
void SendInvoice(int customerId, int orderId)
Because it's obsessed with primitives, we have to dedicate half of the method to validating the input parameters. Here's the same using ValueObjects:
void SendInvoice(CustomerId customerId, OrderId supplierId)
We don't need to check the validity of what we're given, because we're guaranteed that in our Billing domain, that it's impossible to create an invalid CustomerId
or OrderId
.
So all is good in the domain and our ValueObjects are used to represent our domain ideas. ValueObjects are a good fit here because the domain is where our domain logic and models reside. Domain models, where ValueObjects live, are generally stable - that is, it's not a place where millions of objects are created and destroyed in quick succession.
That 'and destroyed' bit above is important: the biggest difference between primitives and ValueObjects are that the primitives could've be on the stack, but ValueObjects (at least in the implementation linked to above) are objects that live on heap.
In contrast to the relatively stable area of the domain, is infrastructure. This usually contains adapters for various technologies and can be a lot more turbulent in terms of object creation and destruction. So, it probably wouldn't be a good place to use ValueObjects instead of primitives, as you'd be creating a lot of extra work for the Garbage Collector (in .NET) which both increases memory usage and slows performance.
I did a very rough 'simulation' for this with Benchmark.net. The simulation compared primitives with ValueObjects in two scenarios:
- 'domain' - uses lots of relatively long-lived objects
- 'infrastructure' - creates and destroys lots of short-lives objects
Here's the results:
It shows:
- there's little difference between primitives and ValueObjects in the domain (long-lived objects)
- speed is comparable - ValueObjects seem slightly quicker but this should be disregarded as this is not a realistic 'domain' in terms of performance; it focuses on overhead of 'use' rather than speed of logic.
- memory is comparable - ValueObjects use slightly more memory which is expected
- there's significant difference between primitives and ValuesObjects in infrastructure code (short-lived) objects
- speed is significantly slower using ValueObjects, likely due to increased Garbage Collector pressure
- memory is significantly higher using ValueObjects; each allocation is on the heap as opposed to small, short-lived allocations on the stack
🔮 future thinking: In the future, perhaps
record structs
# in C# 10 will give us the best of both worlds; clear representation of domain ideas in something that is as cheap to use and destroy as a primitive (and it also might have operators!)
What have we seen? #
- ValueObjects solve a problem. They solve the problem of fully representing domain ideas. Those domain ideas belong in the domain 'layer' of your application (the area where the real 'businessey' stuff takes place and business terms are used).
- Using ValueObjects elsewhere can cause performance problems, particular in areas of code where there are lots of short-lived object being created a destroyed.
- That Microsoft need to pull their finger out and give us
record structs
as soon as possible!
🙏🙏🙏
Since you've made it this far, sharing this article on your favorite social media network would be highly appreciated 💖! For feedback, please 🦋 ping me on Bluesky! 🦋
Leave a comment
Comments are moderated, so there may be a short delays before you see it.
Published