用于领域驱动设计 (DDD) 的 TypeScript

领域驱动设计 (DDD) 是一种强大的方法,通过专注于核心业务领域及其相关逻辑来处理复杂的软件系统。TypeScript 具有强大的类型和现代功能,是有效实现 DDD 概念的绝佳工具。本文探讨了 TypeScript 和 DDD 之间的协同作用,提供了实用的见解、策略和示例,以弥合设计和代码之间的差距。

理解领域驱动设计

**核心概念**

**1. 无处不在的语言**

开发人员和领域专家使用共享语言进行协作,以减少沟通错误。

**2. 有界上下文**

明确分离领域的不同部分,确保特定环境中的自主性和清晰度。

**3. 实体和值对象**

  • 实体:具有唯一身份的对象。
  • 值对象:由其属性定义的不可变对象。
  • **4. 聚合**

    域对象集群被视为数据变更的单个单元。

    **5. 存储库**

    抽象持久性逻辑,提供对聚合的访问。

    **6. 域事件**

    当域内发生重大动作时发出的信号。

    **7. 应用服务**

    封装业务工作流和编排逻辑。

    为什么 TypeScript 适合 DDD

    **1. 静态类型**:强类型检查有助于明确地建模域逻辑。

    **2. 接口**:强制执行组件之间的契约。

    **3. 类:**自然地表示实体、值对象和聚合。

    **4. 类型保护**:确保运行时的类型安全。

    **5. 实用类型**:为动态域启用强大的类型转换。

    实际实施

    **1. 建模实体**

    实体具有唯一的身份并封装行为。

    class Product {
      constructor(
        private readonly id: string,
        private name: string,
        private price: number
      ) {}
    
      changePrice(newPrice: number): void {
        if (newPrice <= 0) {
          throw new Error("Price must be greater than zero.");
        }
        this.price = newPrice;
      }
    
      getDetails() {
        return { id: this.id, name: this.name, price: this.price };
      }
    }

    **2. 创建值对象**

    值对象是不可变的,并且通过值进行比较。

    class Money {
      constructor(private readonly amount: number, private readonly currency: string) {
        if (amount < 0) {
          throw new Error("Amount cannot be negative.");
        }
      }
    
      add(other: Money): Money {
        if (this.currency !== other.currency) {
          throw new Error("Currency mismatch.");
        }
        return new Money(this.amount + other.amount, this.currency);
      }
    }

    **3. 定义聚合**

    聚合确保边界内的数据一致性。

    class Order {
      private items: OrderItem[] = [];
    
      constructor(private readonly id: string) {}
    
      addItem(product: Product, quantity: number): void {
        const orderItem = new OrderItem(product, quantity);
        this.items.push(orderItem);
      }
    
      calculateTotal(): number {
        return this.items.reduce((total, item) => total + item.getTotalPrice(), 0);
      }
    }
    
    class OrderItem {
      constructor(private product: Product, private quantity: number) {}
    
      getTotalPrice(): number {
        return this.product.getDetails().price * this.quantity;
      }
    }

    **4. 实现存储库**

    存储库抽象数据访问。

    interface ProductRepository {
      findById(id: string): Product | null;
      save(product: Product): void;
    }
    
    class InMemoryProductRepository implements ProductRepository {
      private products: Map = new Map();
    
      findById(id: string): Product | null {
        return this.products.get(id) || null;
      }
    
      save(product: Product): void {
        this.products.set(product.getDetails().id, product);
      }
    }

    **5. 使用领域事件**

    领域事件通知系统状态变化。

    class DomainEvent {
      constructor(public readonly name: string, public readonly occurredOn: Date) {}
    }
    
    class OrderPlaced extends DomainEvent {
      constructor(public readonly orderId: string) {
        super("OrderPlaced", new Date());
      }
    }
    
    // Event Handler Example
    function onOrderPlaced(event: OrderPlaced): void {
      console.log(`Order with ID ${event.orderId} was placed.`);
    }

    **6. 应用服务**

    应用服务协调工作流程并执行用例。

    class OrderService {
      constructor(private orderRepo: OrderRepository) {}
    
      placeOrder(order: Order): void {
        this.orderRepo.save(order);
        const event = new OrderPlaced(order.id);
        publishEvent(event); // Simulated event publishing
      }
    }

    7. 使用有界上下文

    利用 TypeScript 的模块化功能来隔离有界上下文。

  • 对每个上下文使用单独的目录。
  • 明确定义跨上下文通信的接口。
  • **示例结构:**

    /src
      /sales
        - Product.ts
        - Order.ts
        - ProductRepository.ts
      /inventory
        - Stock.ts
        - StockService.ts
      /shared
        - DomainEvent.ts

    高级功能

    **灵活建模的条件类型**

    type Response = T extends "success" ? { data: any } : { error: string };

    **用于验证的模板文字类型**

    type Currency = `${"USD" | "EUR" | "GBP"}`;