/ts-ddd

A Typescript Domain Driven Design library

Primary LanguageTypeScriptMIT LicenseMIT

Typescript Domain-Driven Design


A set of utilities for Domain-Driven Design

Packages

Packages Link
@ts-ddd/common Go to package
@ts-ddd/entity Go to package
@ts-ddd/state-machine Go to package
@ts-ddd/value-object Go to package

Common

UUID generator

import { uuidGenerator } from '@ts-ddd/common';

const uuid = uuidGenerator() // -> b9ebc3ea-bfbd-4a86-8b7b-39995b47c631

Interfaces

Interface Properties
WithActive isActive
WithAudit createdBy, updatedBy and deletedBy as date
WithDomain domain
WithLocale locale
WithNullable isNullable
WithOwner ownerId
WithScope scope
WithStatus status
WithTimestamps createdAt and deletedAt
WithUser userId
WithUuid uuid
WithVersion version

Example

interface Post extends WithActive, WithUuid {
    title: string;
    body: string;
}

/*
  {
    uuid: string;
    title: string;
    body: string;
    isActive: boolean;
  }
*/

Entity

Use to create domain entities using an interface as contract.

export interface IBook {
  id: Identifier;
  name: string;
  year: number;
  publisher: string;
}
import { uuidGenerator } from '@ts-ddd/common';
import { Identifier, Entity } from '@ts-ddd/entity';
import { IBook } from '../interfaces/book.interface';

export class Book extends Entity<IBook> {
  public static createToPrimitives(payload: Omit<IBook, 'id'>) {
    return new Book({
      id: uuidGenerator(),
      ...payload
    })
  }
  
  public static createFromPrimitives(payload: IBook): Book {
    return new Book(payload)
  }
}

Getting an object from the data source

class BooksService {
  constructor(private readonly booksRepository: BooksRepository) {}

  public async findBookById(id: string): Promise<Book | undefined> {
    const book = await this.booksRepository.findOne({ id });

    if (book) {
        return Book.createFromPrimitives(book)
    }

    return undefined;
  } 
}

Creating a book to store it in the data source

class CreateBookUseCase {
  constructor(private readonly booksService: BooksService) {}

  public async execute(payload: Omit<IBook, 'id'>): Promise<Book | undefined> {
    const book = await this.booksRepository.create(
        Book.createToPrimitives(payload)
    );

    return book;
  } 
}

State Machine

import { StateMachine } from '@ts-ddd/state-machine';

enum LightTransitions {
  RED = "red",
  GREEN = "green",
  PULSING_GREEN = "pulsing_green",
  AMBER = "amber",
}

const transitions = {
  [LightTransitions.RED]: [LightTransitions.GREEN],
  [LightTransitions.GREEN]: [LightTransitions.PULSING_GREEN],
  [LightTransitions.PULSING_GREEN]: [LightTransitions.AMBER],
  [LightTransitions.AMBER]: [LightTransitions.RED],
}

const light = StateMachine.instance({
  transitions,
  currentState: LightTransitions.RED,
});

try {
  light.setState(LightTransitions.GREEN);
} catch (error) {
  // InvalidStateTransitionException
}

Value Object

import { StringValue } from '@ts-ddd/value-object'
import { validate, v4 as uuid } from 'uuid';

export class Uuid extends StringValue {
  constructor(value: string) {
    super(value);
    this.ensureIsValidUuid(value);
  }

  static generate(): Uuid {
    return new Uuid(uuid());
  }

  private ensureIsValidUuid(value: string): void {
    if (!validate(value)) {
      throw new Error(`${value} is not a valid UUID`)
    }
  }
}
import { StringValue } from '@ts-ddd/value-object'

export class Title extends StringValue {}
import { PositiveNumberValue } from '@ts-ddd/value-object'

export class Year extends PositiveNumberValue {}
class BooksRepository {
  public async getBook(id: string): Promise<Book | undefined> {
    const book = await this.booksRepository.findOne({ id });

    if (!book) {
      return undefined;
    }

    return Book.createFromPrimitives({
      id: new Uuid(book.id),
      title: new Title(book.title),
      year: new Year(book.year)
    })
  }
}





github twitter linkedin youtube