type-ddd

repository·main·Indexed 43 days ago

https://github.com/4lessandrodev/type-ddd

A collection of utilities and interfaces for implementing Domain-Driven Design (DDD) patterns in Node.js and TypeScript applications. Version 4.2.0 provides tools for creating Entities, Value Objects, Aggregates, and Domain Events, utilizing a Result class for predictable error handling and an ID class for UUID v4 generation. Includes specialized packages for Brazilian document validation such as @type-ddd/cnpj and @type-ddd/cpf, as well as @type-ddd/date.

Tokens
28.7K
Snippets
113
Records
124
Agent score
53%

What's inside type-ddd

  1. Overview of types-ddd

    main
    The types-ddd library is designed to support developers in building domain-rich applications by providing tools and patterns aligned with Domain-Driven Design (DDD) principles. It utilizes rich-domain as its core engine to facilitate the creation of expressive and robust domain models.
  2. What is an Aggregate and how to use it

    main

    An Aggregate is a cluster of associated objects (Entities and Value Objects) that are treated as a single unit for data changes. It encapsulates a business context.

    To implement an Aggregate using @type-ddd/core:

    1. Extend Aggregate<Props>.
    2. Use a private constructor.
    3. Implement static factory methods (like begin() or create()) to ensure the aggregate starts in a valid state.
    4. Use the aggregate to manage domain events via .addEvent() or .dispatchEvent().
    5. Aggregates can compose multiple entities and value objects to maintain consistency within a transaction.
    import { Aggregate, Ok, Result } from '@type-ddd/core';
    
    interface Props {
        id?: any;
        status: any;
    }
    
    export default class Order extends Aggregate<Props> {
        private constructor(props: Props){
            super(props);
        }
    
        public static begin(customer: any): Order {
            const order = new Order({ status: 'begin', customer });
            order.addEvent('ORDER_HAS_BEGUN', (o) => console.log('Started'));
            return order;
        }
    }
  3. What is an Entity and how to create one

    main

    An Entity represents a concept with a unique identity and attributes. Unlike Value Objects, Entities are defined by their identity.

    To implement an Entity using @type-ddd/core:

    1. Extend Entity<Props>.
    2. Use a private constructor.
    3. Implement a static create(props: Props): Result<Entity> factory method.
    4. Use this.props to access and modify the entity's state through business methods.
    5. Use .toObject() to export the entity's state, including its unique id, createdAt, and updatedAt timestamps.
    import { Entity, Ok, Result, UID } from '@type-ddd/core';
    
    interface Props {
        id?: UID;
        total: any; // e.g. Money
    }
    
    export default class Payment extends Entity<Props> {
        private constructor(props: Props){
            super(props);
        }
    
        public static create(props: Props): Result<Payment> {
            return Ok(new Payment(props));
        }
    }
    
    // Usage
    const payment = Payment.create({ total: someMoney }).value() as Payment;
    console.log(payment.toObject());
  4. What is a Value Object and how to create one

    main

    A Value Object is a simple object representing a single characteristic (like a monetary amount or date) that has no identity of its own. Two value objects are considered equal if their values are equal.

    To implement a Value Object using @type-ddd/core:

    1. Extend ValueObject<Props>.
    2. Use a private constructor to prevent direct instantiation via new.
    3. Implement a static create() factory method that returns a Result<ValueObject | null> to handle validation.
    4. Implement a static isValidProps(props: Props): boolean method to encapsulate business validation rules.
    5. Use this.validator and this.util within methods to perform checks and calculations.
    import { ValueObject, Ok, Fail, Result } from '@type-ddd/core';
    
    interface Props {
        amount: number;
    }
    
    export default class Money extends ValueObject<Props> {
        private constructor(props: Props) {
            super(props);
        }
    
        public static isValidProps({ amount }: Props): boolean {
            const { number: Check } = this.validator;
            return Check(amount).isPositive();
        }
    
        public static create(amount: number): Result<Money | null> {
            const isValid = this.isValidProps({ amount });
            if(!isValid) return Fail("Invalid amount for money");
            return Ok(new Money({ amount }));
        }
    
        public get(key: keyof Props) {
            return this.get(key);
        }
    }
    
    // Usage
    const resA = Money.create(500);
    if (resA.isOk()) {
        const moneyA = resA.value() as Money;
        console.log(moneyA.get('amount')); // 500
    }
  5. DDD Concepts: Domain Events and Anti-Corruption Layers

    main

    Domain Events

    • Used to decouple models by notifying other parts of the system when a state change occurs.
    • Characteristics: Immutable, named in the past tense (e.g., UserCreated), and include the timestamp and the ID of the entity that raised the event.
    • Handling: Can be subscribed to via callbacks or a pub/sub interface. Events are raised synchronously; asynchronous tasks should be handled within the event handler.

    Anti-Corruption Layer (ACL)

    • Used to translate models between your bounded context and external systems (legacy apps or third-party services).
    • Helps prevent external data models from polluting your clean domain model using service facades or model adapters.
  6. Implement Aggregates and Domain Events

    main

    Aggregates are transactional boundaries composed of one or more Entities and Value Objects. They ensure data consistency within a business transaction.

    Core Concepts

    • Aggregate Root: The main Entity that serves as the entry point to the aggregate.
    • Domain Events: Aggregates can manage domain events. Events are stored in memory and can be dispatched to notify other parts of the system about state changes.

    Domain Event Workflow

    1. Create an event class implementing IHandle<AggregateType>.
    2. Implement the dispatch(event) method to define the action.
    3. Use aggregate.addEvent(eventInstance) to register the event.
    4. Use aggregate.dispatch("EventName") to trigger the execution.
    export class ProductCreatedEvent implements IHandle<Product>{
    	public eventName = 'ProductCreated';
    
    	dispatch(event: IDomainEvent<Product>): void {
    		const { aggregate } = event;
    		console.log(`EVENT DISPATCH: ${aggregate.hashCode().value()}`);
    	}
    }
    
    // Usage
    const product = Product.create(props).value();
    product.addEvent(new ProductCreatedEvent());
    product.dispatch("ProductCreated");
  7. Implement Entities

    main

    Entities are objects with a unique identity (id) that persist through time and application restarts. They orchestrate Value Objects to perform business logic.

    Core Concepts

    • Identity: Entities are compared by their id. Use isEqual(other) for comparison.
    • State Management: Use change(key, value) or set(key).to(value) to modify attributes. These operations trigger validation via the validation(value, key) method.
    • Immutability/Setters: You can disable setters in the constructor using super(props, { disableSetters: true }).
    • History: Like Value Objects, Entities track state changes in a history log.
    • New vs Existing: An entity is considered isNew() if no id was provided during creation.

    Key Methods

    • toObject(): Transforms the entity and its internal Value Objects into a plain object.
    • clone(): Creates a new instance of the entity.
    • isEqual(other): Compares identity and properties.
    • change(key, value): Updates a property, subject to validation() rules.
    interface UserProps { id?: UID, name: Name, age: Age };
    
    export class User extends Entity<UserProps>{
    	private constructor(props: UserProps){
    		super(props);
    	}
    
    	// Validation logic for property changes
    	validation(value: any, key: keyof UserProps): boolean {
    		if (key === 'name') return (value as Name).isValid();
    		return true;
    	}
    
    	public static create(props: UserProps): IResult<User> {
    		return Result.Ok(new User(props));
    	}
    }
  8. DDD Concepts: Repositories and Domain Services

    main

    Repositories

    • Used to persist and read Aggregates to/from data sources (DB, file system).
    • Interface: Should reside in the Core Domain Layer, while the implementation resides in the Infrastructure Layer.
    • Pattern: Should be specific to an aggregate type (not generic) and should handle ACID transactions by committing changes (e.g., calling SaveChanges()) within the repository operation.
    • Avoid: Do not use lazy loading (use eager loading to avoid N+1 problems) and do not use repositories inside domain models.

    Domain Services

    • Used for logic that does not fit cohesively within a single Entity or Value Object.
    • Constraint: Should be stateless. They sit in the Core Domain layer and expose domain models through their interfaces.
  9. Implement Value Objects

    main

    Value Objects are immutable objects identified only by their values. They are used to describe or measure things (e.g., Name, Money, Address) and can be part of an Entity.

    Core Concepts

    • Immutability: While the library allows state changes via set or change, it is highly recommended to create a new instance instead of modifying an existing one.
    • Validation: Use isValidProps (static) for creation validation and the validation method for validating property changes during set or change operations.
    • History: If setters are enabled, the library tracks changes in a history log, allowing you to revert to previous states using history().back().
    • Creation: Use the static create method, which returns a Result to ensure the object is valid before instantiation.

    Key Methods

    • create(props): Returns IResult<ValueObject>.
    • get(key): Retrieves a specific property.
    • set(key).to(value): Updates a property (if not disabled).
    • clone(): Creates a new instance with the same properties.
    • toObject(): Transforms the value object into a plain object (useful for nested objects).
    • createMany(items[]): A static method to instantiate multiple value objects at once.
    • disableSetters: Pass { disableSetters: true } to the super constructor to enforce strict immutability.
    import { Ok, Fail, Result, ValueObject } from "types-ddd";
    
    export interface NameProps {
    	value: string;
    }
    
    export class Name extends ValueObject<NameProps>{
    	private constructor(props: NameProps) {
    		super(props, { disableSetters: true });
    	}
    
    	public static isValidProps({ value }: NameProps): boolean {
    		return value.length >= 3 && value.length <= 30;
    	}
    
    	public static create(value: string): Result<Name> {
    		if (!this.isValidProps({ value })) return Fail('Invalid length');
    		return Ok(new Name({ value }));
    	}
    }
    
    const result = Name.create('Jane');
    if (result.isOk()) {
        const name = result.value();
        console.log(name.get('value'));
    }
  10. DDD Concepts: Entities, Value Objects, and Aggregates

    main

    The library is designed around the following core DDD building blocks:

    Entities

    • Identified by a unique ID (preferably a GUID).
    • Have entity semantics (equality based on class name + ID).
    • Behavior should orchestrate Value Objects rather than just holding data.
    • Constraint: Do not use public property setters; use behavior methods to change state. Avoid bidirectional relations and holding direct references to other entity classes (use IDs instead).

    Value Objects

    • Identified only by their values (e.g., Money, Address).
    • Immutable: Behaviors should not change the state of the object, but return a new instance instead.
    • Have value semantics (equality based on property values).
    • Can be part of an Entity.

    Aggregates

    • A transactional graph of model objects (Entities and Value Objects) that change together.
    • The Aggregate Root must be an Entity.
    • Aggregates can hold references to other Aggregate Roots, but should not hold references to internal entities of other aggregates.
    • Use Aggregates to manage relations between different entities within a business transaction.
  11. Install @type-ddd/core and rich-domain

    main

    To use the @type-ddd/core library for handling value objects in Domain-Driven Design contexts, you must install both @type-ddd/core and rich-domain. Use npm or yarn to add these dependencies to your project.

    npm i rich-domain @type-ddd/core
    
    # OR
    
    yarn add rich-domain @type-ddd/core
  12. Install @type-ddd/core

    main

    To install the full suite of core utilities and interfaces for building Domain-Driven Design (DDD) applications with Node.js and TypeScript, install the @type-ddd/core package.

    $ npm i @type-ddd/core
    
    # or 
    
    $ yarn add @type-ddd/core