TypeScript中的DDD:复杂项目的整洁架构

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么普通的CRUD会毁掉你的项目,而DDD可以修复它

\n\n

每个开发者至少都遇到过这样的情况:一个看似简单的项目变成了由服务、控制器和散落在各处业务逻辑组成的“泥潭”。你开始写UserService,然后是OrderService,半年后OrderService里塞满了发送邮件、计算折扣和检查用户余额的逻辑。熟悉吗?

\n\n

问题在于,我们过早地考虑数据库、框架和REST API。我们忘记了最重要的事情——领域(domain)。领域驱动设计(DDD)提供了一种截然不同的方法:首先用专家的语言对业务流程建模,然后才是代码。

\n\n

在本文中,我们将通过真实示例探讨如何在TypeScript中应用领域驱动设计。你将看到如何构建整洁架构,使业务规则与基础设施分离,并且代码易于测试和修改。

\n\n

DDD基础:语言、上下文和模型

\n\n

通用语言(Ubiquitous Language)

\n

DDD的第一个原则是通用语言,它既能让开发者理解,也能让业务专家理解。如果经理说“客户激活了订阅”,那么代码中应该有一个activateSubscription()方法,而不是setStatus(1)。TypeScript凭借其强类型特性非常适合这一点——类、方法和变量的名称本身就是文档。

\n\n

限界上下文(Bounded Context)

\n

大型系统被划分为独立的上下文:“支付”、“用户管理”、“配送”。每个上下文都有自己的模型和语言。TypeScript模块和严格类型有助于在导入层面严格分离这些上下文。

\n\n

在TypeScript中构建领域模型

\n\n

值对象(Value Object)

\n

这是一个不可变的实体,由其属性而非标识符定义。例如:Email、Money、Coordinates。

\n\n
// value-objects/Email.ts\nexport class Email {\n  private readonly value: string;\n\n  constructor(email: string) {\n    if (!this.isValid(email)) {\n      throw new Error('Invalid email address');\n    }\n    this.value = email;\n  }\n\n  private isValid(email: string): boolean {\n    return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n  }\n\n  public getValue(): string {\n    return this.value;\n  }\n\n  public equals(other: Email): boolean {\n    return this.value === other.value;\n  }\n}\n
\n\n

请注意:Email自己验证其有效性。没有任何服务需要知道正确的电子邮件格式。这是纯粹的TypeScript中的领域驱动设计——业务规则存在于对象内部。

\n\n

实体(Entity)

\n

与值对象不同,实体具有标识符(ID)并且可以随时间变化。例如:User、Order。

\n\n
// entities/User.ts\nimport { Email } from '../value-objects/Email';\nimport { UserId } from '../value-objects/UserId';\n\nexport class User {\n  private readonly id: UserId;\n  private email: Email;\n  private isActive: boolean;\n\n  constructor(id: UserId, email: Email) {\n    this.id = id;\n    this.email = email;\n    this.isActive = true;\n  }\n\n  public getId(): UserId {\n    return this.id;\n  }\n\n  public changeEmail(newEmail: Email): void {\n    if (!this.isActive) {\n      throw new Error('Cannot change email for inactive user');\n    }\n    this.email = newEmail;\n  }\n\n  public deactivate(): void {\n    this.isActive = false;\n  }\n}\n
\n\n

聚合(Aggregate)

\n

聚合是一组实体和值对象,它们作为一个整体进行处理。聚合有一个根(Aggregate Root),通过它来访问内部对象。

\n\n

示例:订单(Order)

博客

书籍推荐