DEV Community

Zohar Pfeffer
Zohar Pfeffer

Posted on

Why you desperately need Value Objects in your life

This article is targeted toward developers who want to level up their programming skills. I’ll introduce you to Value Objects, an advanced concept that will help:

  • Making sure your system is always in a valid state
  • Better organize your code and keep it DRYer
  • Increase testability, readability, and maintainability
  • Make your software more robust, fail-proof, and less buggy

The Value in Value Objects

Let me walk you through an example to demonstrate the problem we’re trying to solve. Let’s assume that we need to write a function that sends an invitation to a birthday party. The crudest implementation might look something like this:

initial function

I’m using Typescript throughout the examples, but the principles I’ll discuss here are relevant in pretty much any language and can even be implemented to some extent in non-typed languages. The examples are simplistic and I’m not using the latest syntax to keep things easy to understand.

Notice that the function has no input validation, nothing prevents passing to it arguments that will either break it (like an invalid recipient email) or will simply produce a weird and unwanted output (like “d4$k” as the name or -6.293 as the age).

Typescript does a nice job enforcing some business rules, it ensures that “senderName” is a string, and that “age” is a number, but that’s not nearly enough! If we look, for example, at the first argument (”senderName”) and think about what makes up a valid name, we can come up with the following specification:

  • A string
  • Consists only of alphabetical characters and spaces (no digits or symbols)
  • Between 2-100 characters long

There’s a lot more we can add to that list, like rules around whitespace, capitalization, etc. But these 3 rules will do for the sake of our example. The point to take here is that real life entities can rarely be modeled to a simple primitive data type in our code (string / number / boolean etc.). They usually have a set of business rules that determine what a valid value looks like, even seemingly simple models such as a name.

Introducing Validation

So because we want to avoid processing invalid input, we introduce validations into our function:

function containing validation

What do you think about the way we handle validation here? Notice anything wrong? I mean it’s fine, it’s doing its job. But writing code that works is the easy part and is not enough, writing readable, maintainable, testable, and extensible code is much harder. It’s what separates great programmers from bad ones.

What’s wrong then?

The last change might look harmless, but I argue that it introduced several problems into our codebase.

First of, does the name validation actually belong here? What if we’re handling names in other places in the app, do we duplicate the validation? That wouldn’t be DRY… And we are likely to encounter more and more use cases that require a valid name as our application grows.

So maybe we can extract the validation to a common service? That’s not a bad idea, but it will be very hard to keep track on whether a certain value has been validated already or not. I’ll probably want to validate the values before passing them as arguments to functions, at the same time I must validate the input on the beginning of every function I write, because how can I be 100% certain that the input was validated before being passed to the function? You can see how difficult to manage this gets. I guarantee that if you go with that solution you’re going to find yourself unnecessarily validating the same value multiple times, and the name validation code is going to be scattered all over the codebase resulting in clutter and taking the focus away from the important business logic.

Besides, the function in the example is about sending birthday invitations. Ideally, that’s the only thing it should do (single responsibility principle). It shouldn’t be concerned with the validation logic of each parameter.

How can we remedy this?

Here’s a wild idea - wouldn’t it be great if we could know for sure that the values we pass around are always valid, so we can avoid frantically validating them all around our functions and services? Wouldn’t it be nice if we make sure that our value cannot be modified into an invalid value?

Let’s recap our needs:

  • We need one sensible place that will encapsulate all the business rules concerning the validity of a given value.
  • We need to disallow modifications to that value that will put it in an invalid state.
  • We need to enforce that our solution is used 100% of the time.

Enter Value Objects

Value Objects act as a container that holds a value and all the business rules to validate it. They run all the validations on creation, and since they are immutable (cannot be changed) they don’t allow the value to ever go invalid. If you want to modify a Value Object, instead of changing the existing one you just create a new one in its place. The idea is to pass around the Value Object rather than the primitive value, then we can always be confident that it’s valid.

So let’s get our hands dirty and create a Value Object for the name data model, we will implement the value object principles one by one.

Principle no. 1 - Validates on Creation:

initial value object

instatiation examples

Now, whenever we create a Name we can be certain that it’s valid, but we have no assurance that it won’t be modified into an invalid value somewhere along the way. Like so:

modification allowed

Principle no. 2 - Value Objects Are Immutable

So the next step would be to make the value immutable, in Typescript we can do that by using the “readonly” keyword at the property declaration:

adding readonly

This prevents the value from being changed after instantiation:

immutability example

Principle no. 3 - Enforce The Use of Value Objects

Now that we’ve created this awesome Value Object, we need to make sure that it is always used when calling the “sendBirthdayInvitation” function. So we declare it as the parameter type, like so:

change name parameter

Unfortunately this isn’t enough. Even after setting “Name” as the parameter type, the following arguments are completely acceptable by TypeScript:

structural typing problems

What the hell is this? Why would TypeScript allow a literal object and a class other than the “Name” class to be passed as an argument? This has to do with the fact that Typescript employs a structural type system. Without going into too much detail, this means that Typescript compares types by their interface (structure), so two types of the same interface are interchangeable as far as TypeScript concerns. Our Name class interface is just one property called “value” of type string, so any object with a “value“ property of type string will be accepted by TypeScript.

This completely circumvents our efforts to enforce using the “Name” Value Object whenever calling “sendBirthdayInvitation”. But worry not, I’ve found an easy way to workaround it - all we need to do is to add a private property to the class:

adding private property

I gave the private property the weird name of “_X_” just to make sure it won’t clash with property names I would want to use in the future, but you can really name it anything you like.

Let’s see how TypeScript reacts if we try to pass anything but a “Name” instance now:

literal object rejection

So no one will ever be able to pass a literal object, because literal objects cannot have private properties. What about classes?

other class rejection

Notice that TypeScript complains even if I add a private “_X_” property to this class too. This is perfect for us because it means that we can use this private property in all of our Value Objects.

Congratulations

Our first Value Object is now complete, here it is with all it’s glory:

complete value object

Take a moment to appreciate how easy it is to unit test it. How readable and maintainable it is. We defined our domain model so well that it is now virtually impossible to put it in an illegal state at some point in the future.

The approach I used to implement the Value Object utilizes classes, which is a pretty Object-Oriented way to do that. If you’re into Functional Programming know that this can be done in a functional way as well. You’re likely to find better implementations of Value Objects online, the one I use here is simplified to make it more understandable.

Putting Value Objects in Use

If we go back to our original “sendBirthdayInvitation” function, once I define Name as the parameter type, I can rest assured that the argument passed is a Name instance which means it had undergone all the required validations. If we do the same with the other parameters, we can have the function purely focused on its responsibility and clean of any input validation:

value objects as parameters

Make all of your values into Value Object and you can be confident that your business rules will always be respected. Your functions will be much cleaner and as a result, easier to change or refactor.

Many software architecture patterns advocate organizing your application into several layers, what we did here is essentially created a separate layer for our data models. That way other layers can remain uncontaminated with our models business rules, which usually manifest themselves as convoluted input validations, if statements, and case clauses.

One more added benefit

I sure hope that at this point you are already convinced that using Value Objects can tremendously improve the quality of your code. Still, I’d like to mention one last perk in using them - it acts as the perfect place to document the data model it represents:

documentation comment

documentation tooltip

Conclusion

That’s It! If you made it this far give yourself a pat on the back.

I Hope you enjoyed my work and that I managed to convey the immense benefit I see in Value Objects. It was the tip of the iceberg, there’s so much that can be improved in this Value Object implementation, and there’s so much more to Value Objects than what I showcased here, but I tried to keep this article bite sized. I hope it ignited your curiosity and I encourage you to explore this subject further.

Please feel free to provide feedback, and let me know if you want to see more content on this subject. There’s so much we didn’t touch on like better error handling, creating a Value Object super class, how Value Objects play out in the Domain Driven Design framework and so on. For any questions/comments you can contact me on linkedin.

This article was heavily influenced by Domain Driven Design, Secure by Design, Clean architecture, and Khalil Stemmler’s blog.

Top comments (9)

Collapse
 
jcubic profile image
Jakub T. Jankiewicz • Edited

Interesting article. You can simplify the Value Object by using:

class Name {
   private _name: string;
   get name() {
     return this._name;
   }
   constructor(name: string) {
   }
}
Enter fullscreen mode Exit fullscreen mode

and no need for odd private values and readonly can be also at runtime. I would probably also add valueOf() or toString() method tought. I don't think that your example with value object inside template literal will work you will get [Object object] instead of a name.

Collapse
 
zoharp profile image
Zohar Pfeffer • Edited

Thanks for these great suggestions @jcubic!
Making the value private is a nice idea but it doesn't prevent modifying the value from within the class, a combination of private and readonly sounds like the best way to go.

   private readonly _value: string
Enter fullscreen mode Exit fullscreen mode

A toString method is very likely to come in handy.

Collapse
 
jcubic profile image
Jakub T. Jankiewicz • Edited

@zoharp Why you need readonly if use only have getter and constructor. Are you really worried that you or someone will modify the private variable in Value Object like this?

Thread Thread
 
zoharp profile image
Zohar Pfeffer

@jcubic this is just sticking with the principles of Value Objects.
It is possible that someone will try to modify the value from within the class some day, if that happens, at least he will have a stop sign telling him that he shouldn't do that.

Collapse
 
peerreynders profile image
peerreynders • Edited
  • Please use code blocks instead of inaccessible images—the font is too small.

  • Your regular expression '^[a-zA-Z ]$' only passes only on single character names. '^[a-zA-Z ]{2,100}$' takes care of everything.

But worry not, I’ve found an easy way to workaround it - all we need to do is to add a private property to the class.

Until your favourite duct tape programmer comes along.

class InvalidName extends Name {
  constructor(value: string) {
    super('Valid Name');
    this.value = value;
  }
}

sendBirthdayInvitation(
  new InvalidName('Inv@lid n@m3'),
  'someone@gmail.com',
  30
);
Enter fullscreen mode Exit fullscreen mode

😁

In TypeScript this is usually referred to as a type brand (nominal typing) and there is a way to be more sneaky about it:

// inside the `Name.ts` module
declare const validName: unique symbol;

// export type Name = …
type Name = string & {
  [validName]: true;
};

// export
function assertValidName(value: string): asserts value is Name {
  if (!/^[a-zA-Z ]*$/.test(value))
    throw new Error(
      `Invalid name ${value}, Should consist of Alphabetical characters and spaces only`
    );
  if (value.length < 2 || 100 < value.length)
    throw new Error(`Invalid name S{value}: Should be 2-100 characters long`);
}

/*
  Assertion Functions:
  https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions
*/

// In another module
const tryName = 'valid Name';
assertValidName(tryName);
sendBirthdayInvitation(tryName, 'someone@gmail.com', 30);
//
// works; `tryName` is now typed as
// const tryName: "valid Name" & {
//   [validName]: true;
// } 

const invalidName = 'Inv@lid n@m3';
assertValidName(invalidName); // Error: invalid name Inv@lid n@m3, Should consist of Alphabetical characters and spaces only
sendBirthdayInvitation(invalidName, 'someone@gmail.com', 30);

const anotherTry = 'Inv@lid n@m3';
sendBirthdayInvitation(anotherTry, 'someone@gmail.com', 30);
// Transpilation error:
// Argument of type 'string' is not assignable to parameter of type 'Name'.
// Type 'string' is not assignable to type '{ [validName]: true; }'.(2345)

function sendBirthdayInvitation(
  senderName: Name,
  recipient: string,
  age: number
) {
  console.log(senderName);
}
Enter fullscreen mode Exit fullscreen mode

playground
credit

The validName property only exists at compile time so there is no runtime overhead.

It is well worth looking beyond class/interface into type, intersection types, and discriminated unions.


James Coplien
"How many of you do object-oriented programming? What language? Java is the only language in which you cannot do object-oriented programming. Huh? Other languages. Smalltalk - no. C# - no. Python - no. No. Any JavaScript programmers here? Here are my object-oriented programmers - they're writing objects - the rest of you are writing classes".


The fundamental idea behind a value object is that its identity is established by the contained values rather than the reference of the actual object.

"With Value Objects, we establish identity through the structural equality of two instances."
Value Objects - DDD

Given that new Name('John Doe') !== new Name('John Doe') there really needs to be a comparison method (equals()) on the class that checks for structural equality or in the case of "objects as records" a comparison function.

JavaScript Tuples and Records (Stage 2, December 2021) may make that easier in the future.

Collapse
 
zoharp profile image
Zohar Pfeffer

Wow! it was worth putting the effort to write this article only to get this amazing feedback from you! Thank you for all this information.

Since this is an introductry article with the main goal of explaining the core principles of Value Objects, I prioritized understandability above all in my example implementation. So there are a few flaws in my implementation at the price of illustrating the concepts of Value Objects more clearly and keeping it readable for folks using other languages than TypeScript.

Have you ever written a project following Domain Driven Design with TypeScript? If so, please share with us how did you implement its principles? Did you use a dedicated library?

Sorry it was hard to read the code, I went with screenshots rather than code blocks because I wanted to show how the Value Object utilizes the IDE to make our lives much easier. Also, I thought it would be helpful if I highlight the last changes in each code snippet. Maybe I'll use a combination of code blocks and screenshots next time.

Yep, I got that Regex wrong 😅, I intentionally checked the length on a separate line for readability though.

An equals() method is definitely a must here. I did not want to go into equality comparison and how it diffrentiate between Value Objects and Entities since it's an introductory article.

Thank you for explaining type branding so clearly, I did come across the concept on my research but decided not to use it for the example in the article. If you use it you cannot use a base Value Object class though, how would you deal with that? Composition maybe?

Collapse
 
peerreynders profile image
peerreynders

illustrating the concepts of Value Objects more clearly and keeping it readable for folks using other languages than TypeScript.

It comes across as if you actually mean "class-based object oriented" languages rather than languages in general.

Domain-Driven Design: Tackling Complexity in the Heart of Software was published in 2003 and was a product of its time. Java was dogmatically object-oriented but "values" where needed, so Eric Evans conceived the "Value Object". However now the broader spectrum of languages aren't constrained to objects. And neither is DDD constrained to OO.

While classes are types, not all types are classes. Many languages support tuples and records in such a way that they can serve as "Value Objects" even though they're not objects.

"TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the programmers at Microsoft could bring traditional object-oriented programs to the web. As it has developed, TypeScript’s type system has evolved to model code written by native JavaScripters. The resulting system is powerful, interesting and messy."
ref

So even TypeScript had to adapt. In JavaScript arrays often serve as tuples and plain objects as records or dictionaries; they are treated as values even though they have the runtime semantics of objects (not necessarily created by a class).

Have you ever written a project following Domain Driven Design with TypeScript?

No, likely because most TypeScript based projects still embrace the Smart UI philosophy as it fits the team's capabilities better.

Did you use a dedicated library?

DDD is an approach, a way of thinking and working, not a software tool. And in my experience frameworks often tend to make it harder to implement DDD because they prioritize the needs of the framework. I would even go as far as saying that tooling is largely responsible for the dominance of the Smart UI approach.

Now perhaps there may be domain specific libraries out there but that means

  • that someone already has done the hard work for you
  • you are locked into that library's opinion and may only discover much later that it isn't a good fit or prevents you from evolving into the direction that you need to go.

Bring Clarity To Your Monolith with Bounded Contexts (Rails)

Maybe I'll use a combination of code blocks and screenshots next time.

I'd favor code blocks and use images only when absolutely necessary (perhaps augmented with links to something like codesandbox) Typically just code blocks will do because with images you should be supplying the alt text anyway.

I did not want to go into equality comparison and how it differentiate between Value Objects and Entities since it's an introductory article.

Structural equality is a key characteristic of values and value objects; so an introduction without that leaves the audience largely in the dark of the value that value objects bring.

If you use it you cannot use a base Value Object class though, how would you deal with that? Composition maybe?

I'm not sure I understand the question. I would need some convincing that using inheritance with value objects is even a good idea. And the technique you applied falls within the practice "type branding".

Collapse
 
mcsee profile image
Maxi Contieri

Nice and clear article

That's why a wordle word is a wordle word and not a char(5)

Collapse
 
zoharp profile image
Zohar Pfeffer

Thanks for your feedback!