UNPKG

@typescript-eda/domain

Version:

Core domain primitives for event-driven architecture

287 lines (209 loc) 8.03 kB
# 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 ```bash npm install @typescript-eda/domain # or pnpm add @typescript-eda/domain ``` ## Quick Start ### Define a Value Object ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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. ```typescript 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. ```typescript 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. ```typescript 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: ```typescript 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: ```typescript 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](./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 - **[Getting Started Guide](./docs/getting_started.org)** - Step-by-step tutorial - **[Domain Story](./docs/story.org)** - The philosophy and evolution of domain design - **[Development Journal](./docs/journal.org)** - Design decisions and lessons learned - **[Specifications](./docs/specs/)** - Complete domain examples ## Related Packages TypeScript-EDA Domain is part of the TypeScript-EDA ecosystem: - **[@typescript-eda/infrastructure](https://github.com/rydnr/typescript-eda-infrastructure)** - Infrastructure adapters and implementations - **[@typescript-eda/application](https://github.com/rydnr/typescript-eda-application)** - Application layer and dependency injection - **[@web-buddy/core](https://github.com/rydnr/web-buddy)** - Web automation built on EDA principles ## Contributing We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. ## License This project is licensed under the GPL-3.0 License - see the [LICENSE](LICENSE) file for details. ## Support - **Documentation**: [typescript-eda.org/domain](https://typescript-eda.org/domain) - **Issues**: [GitHub Issues](https://github.com/rydnr/typescript-eda-domain/issues) - **Discussions**: [GitHub Discussions](https://github.com/rydnr/typescript-eda-domain/discussions)