Domain-Driven Design - Value Objects in TypeScript

Before you look at the Value Objects example in TypeScript, you can see at my post on what Value Objects are in Domain-Driven Design.

Value Object base class in TypeScript

abstract class ValueObject<T> {
  protected readonly value: T;

  protected constructor(value: T) {
    this.validate(value);
    this.value = Object.freeze(value);
  }

  protected abstract validate(value: T): void;

  public equals(other?: ValueObject<T>): boolean {
    if (other === null || other === undefined) {
      return false;
    }

    if (other.value === undefined) {
      return false;
    }

    return deepEqual(this, other);
  }
}

About this code snippet:

  • It is abstract: This prevents this class from being instantiated and allows to declare abstract methods.
  • It calls an abstract method called Validate in the constructor: So we are validating the value of the Value Object before creating it.
  • It use Object.freeze() so the value can no longer be changed.
  • It has a equals method that uses deepEqual, you can read about it in this post.

Value Object example in TypeScript

class FirstName extends ValueObject<string> {
  public static readonly MaxLength = 64;

  protected validate(value: string): void {
    if (!value)
      throw new InvalidFirstNameError();

    if (value.length > FirstName.MaxLength)
      throw new FirstNameIsTooLongError();
  }

  static create(value: string): FirstName {
    return new FirstName(value);
  }

  public get getFirstName(): string {
    return this.value;
  }
}

About this code snippet:

  • It overrides the Validate method with validation rules.
  • No public constructor: Other classes cannot create instances of this class.
  • To create instances of this Value Object we do not call the constructor directly, but it uses the Static Factory Method pattern instead.