Value Objects - DDD w/ TypeScript

Last updated Apr 12th, 2019
Value Objects are one of the primary components of Domain-Driven Design. Here's a simple Value Object class in TypeScript.

We cover this topic in The Software Essentialist online course. Check it out if you liked this post.

Also from the Domain-Driven Design with TypeScript article series.

In Domain-Driven Design, Value Objects are one of two primitive concepts that help us to create rich and encapsulated domain models.

Those concepts are Entities and Value Objects.

Value Objects are best understood by understanding how it's different from an Entity. Their main difference is in how we determine identity between two Value Objects and how we determine identity between two Entities.

Entity Identity

We use an Entity to model a domain concept when we care about the model's identity and being able to distinguish that identity from other instances of the model.

The way that we determine that identity helps us determine whether it's an Entity or a Value Object.

A common example is modeling a user.

In this example, we'd say that a User is an Entity because the way that we determine the difference between two different instances of a User is through it's Unique Identifier.

The Unique Identifier we use here is either a randomly-generated UUID or an auto-incremented SQL id that becomes a Primary Key that we can use for lookup from some persistence technology.


Value Objects

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

Structural Equality

Structural equality means that two objects have the same content. This is different from referential equality / identity which means that the two objects are the same.

To identify two Value Objects from each other, we look at the actual contents of the objects and compare based on that.

For example, there might be a Name property on the User Entity.

How can we tell if two Names are the same?

It's pretty much like comparing two strings, right?

"Nick Cave" === "Nick Cave" // true

"Kim Gordon" === "Nick Cave" // false

This is easy.

Our User entity could look like this:

domain/user.ts
interface UserProps {
  name: string
}

class User extends Entity<UserProps> {
  
  get name (): string {
    return this.props.name;
  }

  constructor (props: UserProps) {
    super(props);
  }
}

This is OK, but it could be better. Lemme ask a question:

What if we wanted to limit the length of a user's name. Let's say that it can be no longer than 100 characters, and it must be at least 2 characters.

A naive approach would be to write some validation logic before we create an instance of this User, maybe in a service.

services/createUserService.ts
class CreateUserService {
  public static createUser (name: string) : User{
    if (name === undefined || name === null || name.length <= 2 || name.length > 100) {
      throw new Error('User must be greater than 2 chars and less than 100.')
    } else {
      return new User(name)
    }
  }
}

This isn't ideal. What if we wanted to handle Editing a user's name?

services/editUserService.ts
class EditUserService {
  public static editUserName (user: User, name: string) : void {
    if (name === undefined || name === null || name.length <= 2 || name.length > 100) {
      throw new Error('User must be greater than 2 chars and less than 100.')
    } else {
      user.name = name;
      // save
    }
  }
}
  1. This isn't really the right place to be doing this.
  2. We've just repeated the same validation logic.

This is actually how a lot of projects start to spin out of scope. We end up putting too much domain logic and validation into the services, and the models themselves don't accurately encapsulate the domain logic.

We call this an Anemic Domain Model.

We introduce value object classes to strictly represent a type and encapsulate the validation rules of that type.

Value Objects

We had this before, a basic class for our User entity wiith a string-ly typed name property.

domain/user.ts
interface UserProps {
  name: string
}

class User extends Entity<UserProps> {

  get name (): string {
    return this.props.name;
  }

  constructor (props: UserProps) {
    super(props);
  }
}

If we were to create a class for the name property, we could co-locate all of the validation logic for a name in that class itself.

The upper bound (max length), the lower bound (min length), in addition to any algorithm that we wanted to implement in order to strip out whitespace, remove bad characters, etc- it could all go in here.

Using a static factory method and a private constructor, we can ensure that the preconditions that must be satisfied in order to create a valid name.

domain/name.ts
interface NameProps {
  value: string
}

class Name extends ValueObject<NameProps> {

  get value (): string {
    return this.props.value;
  }
  
  // Can't use the `new` keyword from outside the scope of the class.
  private constuctor (props: NameProps) {
    super(props);
  }

  public static create (name: string) : Name {
    if (name === undefined || name === null || name.length <= 2 || name.length > 100) {
      throw new Error('User must be greater than 2 chars and less than 100.')
    } else {
      return new Name({ value: name })
    }
  }
}

Then, in the User class, we'll update the name attribute in UserProps to be of type Name instead of string.

domain/user.ts
interface UserProps {
  name: Name;
}

class User extends Entity<UserProps> {

  get name (): Name {
    return this.props.name;
  }

  private constructor (props: UserProps) {
    super(props);
    this.name = props.name;
  }

  public static create (props: IUser) {
    if (props.name === null || props.name === undefined) {
      throw new Error('Must provide a name for the user');
    } else {
      return new User(props);
    }
  }
}

We apply the static factory method here as well.

Value Object class

Here's an example of a Value Object class.

shared/domain/valueObject.ts
import { shallowEqual } from "shallow-equal-object";

interface ValueObjectProps {
  [index: string]: any;
}

/**
 * @desc ValueObjects are objects that we determine their
 * equality through their structrual property.
 */

export abstract class ValueObject<T extends ValueObjectProps> {
  public readonly props: T;

  constructor (props: T) {
    this.props = Object.freeze(props);
  }

  public equals (vo?: ValueObject<T>) : boolean {
    if (vo === null || vo === undefined) {
      return false;
    }
    if (vo.props === undefined) {
      return false;
    }
    return shallowEqual(this.props, vo.props)
  }
}

Check out the equals method. Notice that we use shallowEquals in order to determine equality. This is one way to accomplish structural equality.

When it makes sense, subclasses of this Value Object base class can also be extended to include convenience methods like greaterThan(vo?: ValueObject<T>) or lessThan(vo?: ValueObject<T>). It wouldn't make sense in this example, but it might if we were talking about something like LoggingLevels or BusinessRatings.


In future articles, we'll talk about:

  • entity design
  • better error handling technique for object creation
  • moving anemic code out of services and into domain models
  • writing DTOs to create data contracts

This is part of the Domain-Driven Design with TypeScript series. If this was useful to you, let me know in the comments & subscribe to the newsletter to get notified when new articles come out. Cheers!

More in this series so far..

An Introduction to Domain-Driven Design - DDD w/ TypeScript

We cover this topic in The Software Essentialist online course. Check it out if you liked this post.



Discussion

Liked this? Sing it loud and proud 👨‍🎤.


19 Comments

Commenting has been disabled for now. To ask questions and discuss this post, join the community.

Elliott
4 years ago

Yo your articles are actually the bomb, helping me out so much with my research project right now!

Khalil Stemmler
4 years ago

Woo! Happy to hear that, man :)

Matteo
4 years ago

Really nice article. I have a question about the instantiation of the Name class, would you just inject it into the User class? Ex: User.create(Name.create(name)) or is there another DDD pattern to delegate its instantiation? Thanks

Khalil Stemmler
4 years ago

Thanks Matteo!


Good question. For the most part, yes! But you want to make sure that your Value Objects are valid. That can be remedied by using the Result<T> class + a private constructor + a factory method so that you never pass in an invalid Value Object to an Entity.


With respect to delegation, to reconstruct Domain entities and transform them between layers, I use the Data Mapper pattern. I use it to:


  • Create instances of a domain model from persistence (toDomain)
  • Map a Domain model to the persistence representation (toPersistence).
  • Map a Domain model to a DTO (toDTO).


That's a good separation of concerns, specifically delegating a construct to be responsible for converting domain objects to the appropriate representation.


You'll often find that you'll need to create Domain Objects from HTTP controller requests as well in order to pass input values to application layer Use Cases.


For a Use Case like Create User (UserEmail, Name, Password), try the `Result<T>.combine([])` method to check your objects are correct before passing them into your Use Case.

Maxim
4 years ago

Thanks for great explanation. What about case when we need to determine equality of passed and persisted passwords? Should we encapsulate hashing inside Password VO and check equality between recounsiled from persistence and passed passwords?

Khalil Stemmler
4 years ago

Absolutely. Check out an example here: https://github.com/stemmlerjs/ddd-forum/blob/master/src/modules/users/domain/userPassword.ts


And when you're reconstituting the `UserPassword` from persistence and turning it into a domain object again, you can signal to the value object that it's hashed.


```typescript

const userPasswordOrError = UserPassword.create({ value: raw.user_password, hashed: true });

```

Another example here: https://github.com/stemmlerjs/ddd-forum/blob/master/src/modules/users/mappers/userMap.ts



Rahul
4 years ago

Great stuff in here! I'm going through all your content to get up to speed on DDD and implement parts of it into my project. I'm currently using TypeORM which allows me to define entities like this:


```ts

@Entity()

export class User {

@PrimaryGeneratedColumn()

id!: number;


@Column("text")

@IsName()

name!: string;

}

```


In the above example, you can define a validator function called `IsName` directly on the entity. Would you recommend still extracting this validation into its own class?

Khalil Stemmler
4 years ago

Hey Rahul!


Ah, that's pretty cool :)


Well, if it was possible using TypeORM, I'd recommend you create a new type/class called `Name` and locate all the validation logic for `Name` there instead using a factory method, because:


  • Validation rules for `name` belong to name and not `User` (Single responsibility principle).
  • You might end up needing to create a `Name` elsewhere in your code, let's say in order to create a default `ProfileHandle`, like ProfileHandle.create(name: Name). If thats the case, we'd have to duplicate the validation rules for `name` in both `ProfileHandle` AND `User`. So, try to encapsulate the validation rules for `name` as close to the `Name` (class) as possible.


I hope that was helpful!

Nathan Johnson
4 years ago

Great article!


A few questions:


-Why do you store the value in a separate "props" object? Why not store the value directly on the value object class?


-Do you recommend using VOs for most values in entities, or is this more for just values that need validation?


-Are there any significant performance impacts of wrapping values in value objects in your experience? It seems like it could cause higher memory usage if there are a lot of objects (say a large list of value objects).

Khalil Stemmler
3 years ago

Thanks, Nathan! Good questions, btw.


I use a "props" object as a form of encapsulation. Placing the values directly on the object is equivalent to defining both a getter and a setter for each value. We don't to do that because if we did, anyone would be able to change the internal value of our value objects, circumventing any validation logic we've defined in the static factory method. Instead, let value objects be immutable, and prevent them from being changed using the equals (=) syntax.


Two reasons to use VO's in my opinion.

1) For validation and encapsulating that logic, as you've identified.

2) Encoding business requirements. Read the section "Wrapping primitives with domain specific types to express a common language and prevent misuse" from Make Illegal States Unrepresentable.


I haven't had performance issues returning lots of value objects (I typically implement some form of pagination), but I have had performance issues returning Aggregates as read models.


When implementing DDD, some of us choose to use a single model for reads and writes, while some of us choose to implement CQRS and delegate a representation of a particular model for reads (value object) and one for writes (aggregate). Typically, the aggregate involves a LOT more information in order to protect model invariants, so if we had to reconstitute several of these for reads (which are more frequent), it's quite possible to get hit by performance constraints. Worth looking into.


Jumpei
3 years ago

Thanks for your good articles. Not only the contents are good, but also English sentences are easy to understand and help for me (I'm not good at English).

Srivathsa Harish
3 years ago

Hi,


Thanks for a very nice article. However I think there is a typo in constructor in Name class (name.ts).

Zohar
3 years ago

Hey Khalil!

Thank you so much for this amazing content.

Few questions:

  1. In the base ValueObject example, why is `prop` public? Isn't its job is to prevent direct access to the value object's properties? I can see that you froze the object but I think this will result in a very confusing API... typescript will allow you to do ` name.props.value = 'John' ` but this assignment won't actually do anything.
  2. Why use a static factory method in this case instead of putting the validation logic in the constructor and using the plain old new keyword?
  3. Why is the arg in the `equals` method optional? (in both ValueObject and Entity)
  4. What is the reason the `equals` method contains this many checks instead of solely comparing the ids in an Entity comparison and simply checking for deep equality for a ValueObject?

Looking forward for your answer and for more brilliant articles!

Thomvaill
3 years ago

Very clear and concise article! Great job!

And I like your ValueObject base class. By the way: I always have to develop more or less the same base class and collections (ValueObjectMap, ValueObjectList, etc...) for value objects in my projects.

Have you open sourced a lib for this?

If not, do you mind if I create one and quote your article?

Sraleik
3 years ago

Thanks for your articles they are a great Help !


I have a question:


Is it possible (in typescript) to create dynamic Value Object (it's probably not call that ^^)


interface UserProps {
  firstname: LimitedText(2, 100);
  lastname: LimitedText(2, 50);
}


Where the only difference between the 2 value object would be the min and max of character.

Or do I have to do this


interface UserProps {
  firstname: Firstname;
  lastname: Lastname;
}


which is not very DRY beaucause FirstName & LastName are basically the same excepts for their min/max.


I would love to hear your thougths on that


Matheus
2 years ago

Sooo good, thank you very much for this...

João Carvalho
2 years ago

Thanks for this incredible article!

Jason
2 years ago

Greate article

Jessy
2 years ago

Khalil this reads very well; great work here and thanks for sharing the knowledge.

jbdemonte
a year ago

I might be wrong, but I guess there is a copy-paste error in the domain/user.ts constructor.

Is this piece of code legit?

this.name = props.name


Btw, really nice serie of article, thanks.

Leopoldo
a year ago

Hey dude, your posts are amazing, im a newbie in oop and i appreciate your Amazon work so thanks for your time. Cheers

Chema CLi
a year ago

Thank you so much. I've implemented it in this way


export class Entity<Properties> {
  protected properties: Partial<Properties> = {}

  constructor(properties: Properties) {
    this.assignProperties<Properties>(properties)
  }

  protected assignProperties<T>(fields: T) {
    Object.entries(fields).forEach(([key, value]) => {
      ;(this.properties as any)[key] = value
    })
  }
}


export class Student extends Entity<StudentProperties> {
  constructor(fields: StudentProperties) {
    // pass fields to the parent Entity class to load the properties globally :)
    super(fields)

    // now I have all my validations on a single place (UwU) I'm so happy with this
    fields.schoolEmail && Student.checkEmailFormat(fields.schoolEmail)
  }


Hamid
a year ago

Thank you Khalif for this article. It's really helpful.


In my project middleware (infrastructure layer), I use an npm validation package (Joi) to validate all inputs before they get to the application/business layer. However, my business layer deals only with "primitive" types. I am realizing that it's wasn't a great choice as I am heading to duplicate some validations that I defined in joi package schemas.


Is Joi package a false good solution?

Is there a clean way to use both a validation package (joi) and value object/entity validations?


Thank you again!


Ulisses Hen
a year ago

Is very helpul your article. Tks for share your knoledge.


Stay in touch!



About the author

Khalil Stemmler,
Software Essentialist ⚡

I'm Khalil. I turn code-first developers into confident crafters without having to buy, read & digest hundreds of complex programming books. Using Software Essentialism, my philosophy of software design, I coach developers through boredom, impostor syndrome, and a lack of direction to master software design and architecture. Mastery though, is not the end goal. It is merely a step towards your Inward Pull.



View more in Domain-Driven Design



You may also enjoy...

A few more related articles

How to Handle Updates on Aggregates - Domain-Driven Design w/ TypeScript
In this article, you'll learn approaches for handling aggregates on Aggregates in Domain-Driven Design.
Decoupling Logic with Domain Events [Guide] - Domain-Driven Design w/ TypeScript
In this article, we'll walk through the process of using Domain Events to clean up how we decouple complex domain logic across the...
Does DDD Belong on the Frontend? - Domain-Driven Design w/ TypeScript
Should we utilize Domain-Driven Design principles and patterns in front-end applications? How far does domain modeling reach from ...
An Introduction to Domain-Driven Design (DDD)
Domain-Driven Design (DDD) is the approach to software development which enables us to translate complex problem domains into rich...

Want to be notified when new content comes out?

Join 15000+ other Software Essentialists learning how to master The Essentials of software design and architecture.

Get updates