Skip to content

typescript-eda/domain

Repository files navigation

TypeScript-EDA Domain

Core domain primitives for event-driven architecture in TypeScript.

Overview

TypeScript-EDA Domain provides the fundamental building blocks for implementing Domain-Driven Design (DDD) patterns in event-driven architectures. This package contains the essential abstractions and tools for modeling rich business domains with clear separation of concerns.

Features

  • Rich Domain Entities with identity and behavior
  • Immutable Value Objects with business rule validation
  • Domain Events for capturing business occurrences
  • Repository Abstractions for domain-friendly data access
  • Port Definitions for clean architecture boundaries
  • @listen Decorator for declarative event handling
  • Type-Safe with full TypeScript support

Installation

npm install @typescript-eda/domain
# or
pnpm add @typescript-eda/domain

Quick Start

Define a Value Object

import { ValueObject } from '@typescript-eda/domain';

export class Email extends ValueObject {
  constructor(private readonly value: string) {
    super();
    this.validate(value);
  }

  private validate(email: string): void {
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      throw new Error(`Invalid email: ${email}`);
    }
  }

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

  protected getEqualityComponents(): unknown[] {
    return [this.value];
  }
}

Create a Domain Event

import { Event } from '@typescript-eda/domain';

export class UserRegistered extends Event {
  public readonly type = 'UserRegistered';

  constructor(
    public readonly userId: string,
    public readonly email: Email
  ) {
    super();
  }

  public toJSON(): Record<string, unknown> {
    return {
      type: this.type,
      userId: this.userId,
      email: this.email.getValue(),
      timestamp: this.timestamp.toISOString(),
      id: this.id
    };
  }
}

Build a Domain Entity

import { Entity, listen } from '@typescript-eda/domain';

export class User extends Entity<UserId> {
  constructor(
    id: UserId,
    private email: Email,
    private status: UserStatus
  ) {
    super(id);
  }

  @listen(EmailVerified)
  public async verifyEmail(event: EmailVerified): Promise<void> {
    if (!this.id.equals(event.userId)) return;
    
    this.status = UserStatus.ACTIVE;
  }

  public changeEmail(newEmail: Email): EmailChangeRequested {
    if (!this.status.isActive()) {
      throw new Error('User must be active to change email');
    }
    
    this.email = newEmail;
    return new EmailChangeRequested(this.id, newEmail);
  }
}

Define a Repository

import { Repository } from '@typescript-eda/domain';

export abstract class UserRepository extends Repository<User, UserId> {
  public abstract findByEmail(email: Email): Promise<User | null>;
  public abstract findActiveUsers(): Promise<User[]>;
}

Core Concepts

Entities

Entities have identity that persists over time. They encapsulate business behavior and maintain consistency through their lifecycle.

export abstract class Entity<T extends ValueObject> {
  constructor(protected readonly _id: T) {}
  
  public get id(): T {
    return this._id;
  }
  
  public equals(other: Entity<T>): boolean {
    return this._id.equals(other._id);
  }
}

Value Objects

Value Objects are immutable and defined by their attributes. They encapsulate business rules and prevent invalid states.

export abstract class ValueObject {
  protected abstract getEqualityComponents(): unknown[];
  
  public equals(other: ValueObject): boolean {
    // Structural equality based on components
  }
}

Domain Events

Events capture important business occurrences and enable loose coupling between domain components.

export abstract class Event {
  public abstract readonly type: string;
  public readonly timestamp: Date;
  public readonly id: string;
  
  public abstract toJSON(): Record<string, unknown>;
}

Event Handling

The @listen decorator enables declarative event handling within entities:

export class Order extends Entity<OrderId> {
  @listen(PaymentProcessed)
  public async markAsPaid(event: PaymentProcessed): Promise<OrderPaid> {
    // Handle payment processed event
  }
}

Architecture

TypeScript-EDA Domain follows clean architecture principles:

┌─────────────────────────────────────┐
│           Application Layer         │ ← Uses domain abstractions
├─────────────────────────────────────┤
│            Domain Layer             │ ← This package
│  • Entities • Value Objects        │
│  • Events   • Repositories         │
│  • Services • Domain Rules         │
├─────────────────────────────────────┤
│         Infrastructure Layer        │ ← Implements domain contracts
└─────────────────────────────────────┘

Best Practices

Entity Design

  • Keep entities focused on business behavior
  • Use factories for complex creation logic
  • Return events from state-changing operations
  • Validate business rules within entities

Value Object Design

  • Make them immutable
  • Validate in constructor
  • Use factory methods for complex validation
  • Include business logic relevant to the value

Event Design

  • Use past tense names (UserRegistered, not RegisterUser)
  • Include all necessary data for event handlers
  • Make events immutable
  • Provide meaningful toJSON() implementations

Repository Design

  • Define interfaces in domain layer
  • Use domain language in method names
  • Return domain objects, not DTOs
  • Keep queries focused on business needs

Testing

Domain models are easy to test since they contain pure business logic:

describe('User', () => {
  it('should verify email when verification event occurs', async () => {
    const user = new User(userId, email, UserStatus.PENDING);
    const event = new EmailVerified(userId);
    
    await user.verifyEmail(event);
    
    expect(user.getStatus()).toBe(UserStatus.ACTIVE);
  });
});

Examples

See the examples directory for complete domain implementations:

  • User Management: Registration, verification, and profile management
  • E-commerce: Orders, products, and inventory
  • Content Management: Articles, authors, and publishing workflows

Documentation

Related Packages

TypeScript-EDA Domain is part of the TypeScript-EDA ecosystem:

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the GPL-3.0 License - see the LICENSE file for details.

Support

About

Domain component of typescript eda

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •