Using Value Objects to represent technical concerns
I was asked a question recently which I find quite interesting. It is actually a part of a code review request (which I’ll cover next week) but I decided to elevate it to its own blog post.
Using Value Objects to represent technical concerns
The question is this:
What do you think about Value Objects like Optional70LimitedString?
From the name of this Value Object, I assume that it represents any nullable string that should not exceed the 70 characters limit.
I answered a similar question in the article entitled When to create a Value Object. In it, I stated that the answer would depend on the specifics of one’s project and that the programmer should take into account the following variables when making such a decision:
-
The number and complexity of invariants.
-
The number of primitives the Value Object embodies.
-
Overall project complexity.
-
The number of duplications it will allow you to get rid of.
We could dissect the Value Object in question and say that there is only one invariant in that Value Object (the string should not be longer than 70 characters) and a single primitive underneath it (the string itself). So, judging by the first two parameters, you would probably pass on introducing it unless the project’s complexity or importance is really high or there just a lot of strings in the domain model that should not be longer than 70 characters. In all other cases, introducing such a Value Object wouldn’t justify the addition of a new class.
We could do all that but the answer would actually be irrelevant. When initially describing those variables, I made an implicit but very important assumption. I assumed that the Value Object represents a concept from the problem domain.
But does Optional70LimitedString
really represent some domain concept?
It doesn’t. Being of 70 characters max is a technical concern that has nothing to do with the domain. It could be one of the invariants of a domain concept but it shouldn’t be treated as such in and of itself.
The problem with this type becomes obvious when you ask the following question: is this Value Object part of the ubiquitous language? In order for a class to be a genuine domain concept, the domain experts should use it when communicating between themselves and with programmers. And it’s not like a stakeholder would tell you:
Hey, we should probably check the optional 70 limited strings of each person that register with us, to make sure this is not a duplicate entry. Each customer must have a unique optional 70 limited string.
This sounds ridiculous. Value Objects are one of the most fundamental building blocks of your domain model, and so they should always tell a story about the problem you are solving. There’s no room for technical concerns here. The core parts of your domain model must speak the ubiquitous language.
So, to answer the original question of what I think about such a Value Object, I’d say this: I don’t like it. Optional70LimitedString
doesn’t convey any domain knowledge; you cannot possibly know what this string is ought to represent.
And it’s not that different from a regular String
if you think about it. You could use it to represent, say, a customer name, and you could also use it to represent a customer email. But does it really help you differentiate the two?
It doesn’t. Just as String
allows you to do something like this:
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
}
Customer customer = GetCustomer();
customer.Name = customer.Email; // Compiles perfectly
Optional70LimitedString
would allow you to do the same:
public class Customer
{
public Optional70LimitedString Name { get; set; }
public Optional70LimitedString Email { get; set; }
}
Customer customer = GetCustomer();
customer.Name = customer.Email; // Compiles perfectly
This assignment is meaningless, though, just as it would be meaningless to assign a Warehouse object to a Customer variable. Introducing a Value Object should help you reveal such issues.
Compare the code above to the following:
public class Customer
{
public CustomerName Name { get; set; }
public Email Email { get; set; }
}
Customer customer = GetCustomer();
customer.Name = customer.Email; // Doesn't compile
Now, it is not only obvious what those properties are about, it’s also impossible to mistake one for the other. The compiler wouldn’t allow you to do that.
What about the DRY principle?
So, don’t allow technical concerns to infiltrate the core parts of your domain model. If you want to introduce a Value Object, make sure you use the ubiquitous language of your domain.
Even if two domain concepts have similar invariants, don’t reuse the same Value Object. Either have separate Value Objects or stick to primitive types if those concepts are too simple to be worthy of distinct types. A Value Object like Optional70LimitedString
is something that resides right in the middle between these two approaches. And it has the drawbacks of both without any of their benefits.
At this point, you might ask: but what about the DRY principle? What if we have two domain notions that, while being different from the domain’s point of view, still share the same invariants? Wouldn’t having two separate Value Objects entail code duplication?
It would. But it still wouldn’t contradict the DRY principle. Remember, DRY is not about code, it’s about domain knowledge. If two Value Objects happen to share the same invariants, it’s just a coincidence that has nothing to do with domain knowledge duplication.
Summary
-
When introducing a Value Object, make sure it represents a domain notion.
-
Don’t allow technical concerns to infiltrate the core parts of your domain model.
-
The maximum length of 70 characters is a technical concern and has nothing to do with the problem domain.
Related articles
Subscribe
Comments
comments powered by Disqus