solid-ddd

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

solid-ddd

solid-ddd

Language-agnostic catalog of SOLID design principles and Domain-Driven Design tactical patterns with concrete do/don't examples.
Triggers: Always loaded for non-documentation code changes. Load when writing or reviewing any class, module, domain object, service, or repository. Applicable across all languages and frameworks.

与语言无关的SOLID设计原则和领域驱动设计(DDD)战术模式目录,包含具体的正反示例。
触发条件:针对非文档类代码变更始终加载。编写或评审任何类、模块、领域对象、服务或仓库时加载。适用于所有语言和框架。

Patterns

模式

SOLID Principles

SOLID原则



SRP — Single Responsibility Principle

SRP — 单一职责原则

A class, module, or function has exactly one reason to change. One unit = one concern.
DON'T — one class handles both order persistence and email notification:
typescript
// [Illustrative — TypeScript]
class OrderService {
  save(order: Order): void { /* writes to DB */ }
  sendConfirmationEmail(order: Order): void { /* sends email */ }
  calculateDiscount(order: Order): number { /* discount logic */ }
}
DO — each class owns one responsibility:
typescript
// [Illustrative — TypeScript]
class OrderRepository { save(order: Order): void { /* DB only */ } }
class OrderNotifier { sendConfirmation(order: Order): void { /* email only */ } }
class DiscountCalculator { calculate(order: Order): number { /* pricing only */ } }
Signal — SRP violated: the class has multiple unrelated reasons to change (schema change AND email template change affect the same file).

一个类、模块或函数应该只有一个变更的理由。一个单元对应一个关注点。
错误示例 — 一个类同时处理订单持久化和邮件通知:
typescript
// [示例 — TypeScript]
class OrderService {
  save(order: Order): void { /* 写入数据库 */ }
  sendConfirmationEmail(order: Order): void { /* 发送邮件 */ }
  calculateDiscount(order: Order): number { /* 折扣逻辑 */ }
}
正确示例 — 每个类只负责一项职责:
typescript
// [示例 — TypeScript]
class OrderRepository { save(order: Order): void { /* 仅处理数据库操作 */ } }
class OrderNotifier { sendConfirmation(order: Order): void { /* 仅处理邮件操作 */ } }
class DiscountCalculator { calculate(order: Order): number { /* 仅处理定价逻辑 */ } }
信号 — SRP被违反:类存在多个不相关的变更理由(数据库schema变更和邮件模板变更都会影响同一个文件)。

OCP — Open/Closed Principle

OCP — 开闭原则

A unit is open for extension, closed for modification. Add behavior by adding code, not by editing existing code.
DON'T — every new payment method requires editing the same function:
typescript
// [Illustrative — TypeScript]
function processPayment(type: string, amount: number) {
  if (type === 'credit') { /* ... */ }
  else if (type === 'paypal') { /* ... */ }
  // Adding 'crypto' forces editing this function
}
DO — new behavior is added by adding a new implementation:
typescript
// [Illustrative — TypeScript]
interface PaymentProcessor { process(amount: number): void; }
class CreditProcessor implements PaymentProcessor { process(amount) { /* ... */ } }
class PaypalProcessor implements PaymentProcessor { process(amount) { /* ... */ } }
// Adding crypto: create CryptoProcessor — no existing code touched
Signal — OCP violated: adding a new variant requires touching a central switch/if chain that already exists.

单元应对扩展开放,对修改关闭。通过新增代码而非修改现有代码来添加行为。
错误示例 — 每新增一种支付方式都需要修改同一个函数:
typescript
// [示例 — TypeScript]
function processPayment(type: string, amount: number) {
  if (type === 'credit') { /* ... */ }
  else if (type === 'paypal') { /* ... */ }
  // 添加'crypto'支付方式必须修改此函数
}
正确示例 — 通过新增实现来添加新行为:
typescript
// [示例 — TypeScript]
interface PaymentProcessor { process(amount: number): void; }
class CreditProcessor implements PaymentProcessor { process(amount) { /* ... */ } }
class PaypalProcessor implements PaymentProcessor { process(amount) { /* ... */ } }
// 添加加密货币支付:创建CryptoProcessor — 无需修改现有代码
信号 — OCP被违反:新增变体时需要修改已有的中心分支判断逻辑。

LSP — Liskov Substitution Principle

LSP — 里氏替换原则

Subtypes must be substitutable for their base types without altering correctness. A subclass must honor the contract of its parent.
DON'T — subclass breaks the parent contract by throwing where parent succeeds:
typescript
// [Illustrative — TypeScript]
class Rectangle { setWidth(w: number) { this.width = w; } }
class Square extends Rectangle {
  setWidth(w: number) { this.width = w; this.height = w; } // Breaks area contract
}
DO — prefer composition or a shared interface with separate implementations:
typescript
// [Illustrative — TypeScript]
interface Shape { area(): number; }
class Rectangle implements Shape { area() { return this.width * this.height; } }
class Square implements Shape { area() { return this.side * this.side; } }
Signal — LSP violated: calling code needs to check the concrete type before using the abstraction (
if (shape instanceof Square)
).

子类必须能够替换其父类而不影响程序正确性。子类必须遵守父类的契约。
错误示例 — 子类通过抛出异常破坏父类契约,而父类原本可以成功执行:
typescript
// [示例 — TypeScript]
class Rectangle { setWidth(w: number) { this.width = w; } }
class Square extends Rectangle {
  setWidth(w: number) { this.width = w; this.height = w; } // 破坏面积计算契约
}
正确示例 — 优先使用组合或共享接口的独立实现:
typescript
// [示例 — TypeScript]
interface Shape { area(): number; }
class Rectangle implements Shape { area() { return this.width * this.height; } }
class Square implements Shape { area() { return this.side * this.side; } }
信号 — LSP被违反:调用代码需要先检查具体类型才能使用抽象(
if (shape instanceof Square)
)。

ISP — Interface Segregation Principle

ISP — 接口隔离原则

Clients must not be forced to depend on methods they do not use. Prefer narrow, focused interfaces over fat ones.
DON'T — one fat interface forces every implementor to stub unused methods:
typescript
// [Illustrative — TypeScript]
interface Worker {
  work(): void;
  eat(): void;   // Robots don't eat
  sleep(): void; // Robots don't sleep
}
class RobotWorker implements Worker {
  work() { /* real logic */ }
  eat() { throw new Error('Not supported'); }   // forced no-op
  sleep() { throw new Error('Not supported'); } // forced no-op
}
DO — split into narrow interfaces; each class implements only what it needs:
typescript
// [Illustrative — TypeScript]
interface Workable { work(): void; }
interface Feedable { eat(): void; sleep(): void; }
class HumanWorker implements Workable, Feedable { /* all methods real */ }
class RobotWorker implements Workable { work() { /* only real method */ } }
Signal — ISP violated: an implementor has one or more methods that throw
NotImplementedException
, return empty, or are no-ops.

客户端不应被迫依赖自己不需要的方法。优先使用窄而专注的接口,而非臃肿的接口。
错误示例 — 一个臃肿接口迫使所有实现者 stub 未使用的方法:
typescript
// [示例 — TypeScript]
interface Worker {
  work(): void;
  eat(): void;   // 机器人不需要吃饭
  sleep(): void; // 机器人不需要睡觉
}
class RobotWorker implements Worker {
  work() { /* 实际逻辑 */ }
  eat() { throw new Error('Not supported'); }   // 被迫空实现
  sleep() { throw new Error('Not supported'); } // 被迫空实现
}
正确示例 — 拆分为窄接口;每个类仅实现自己需要的接口:
typescript
// [示例 — TypeScript]
interface Workable { work(): void; }
interface Feedable { eat(): void; sleep(): void; }
class HumanWorker implements Workable, Feedable { /* 实现所有方法 */ }
class RobotWorker implements Workable { work() { /* 仅实现实际需要的方法 */ } }
信号 — ISP被违反:实现者存在一个或多个抛出
NotImplementedException
、返回空值或空实现的方法。

DIP — Dependency Inversion Principle

DIP — 依赖倒置原则

High-level modules must not depend on low-level modules. Both depend on abstractions. Abstractions must not depend on details.
DON'T — high-level service directly instantiates a concrete repository:
typescript
// [Illustrative — TypeScript]
class OrderService {
  private repo = new PostgresOrderRepository(); // concrete dependency
  placeOrder(order: Order) { this.repo.save(order); }
}
DO — high-level service depends on an abstraction; the concrete class is injected:
typescript
// [Illustrative — TypeScript]
interface OrderRepository { save(order: Order): void; }
class OrderService {
  constructor(private repo: OrderRepository) {} // depends on abstraction
  placeOrder(order: Order) { this.repo.save(order); }
}
// Caller injects: new OrderService(new PostgresOrderRepository())
Signal — DIP violated:
new ConcreteClass()
inside a service constructor or method body with no injection seam.

高层模块不应依赖低层模块。两者都应依赖抽象。抽象不应依赖细节。
错误示例 — 高层服务直接实例化具体的仓库:
typescript
// [示例 — TypeScript]
class OrderService {
  private repo = new PostgresOrderRepository(); // 依赖具体实现
  placeOrder(order: Order) { this.repo.save(order); }
}
正确示例 — 高层服务依赖抽象;具体类通过注入方式传入:
typescript
// [示例 — TypeScript]
interface OrderRepository { save(order: Order): void; }
class OrderService {
  constructor(private repo: OrderRepository) {} // 依赖抽象
  placeOrder(order: Order) { this.repo.save(order); }
}
// 调用方注入:new OrderService(new PostgresOrderRepository())
信号 — DIP被违反:服务构造函数或方法体内直接使用
new ConcreteClass()
,且没有注入入口。

DDD Tactical Patterns

DDD战术模式



Entity

Entity(实体)

An object defined by its identity, not its attributes. Two entities with the same ID are the same entity even if their data differs.
  • Has: a stable, unique identifier (ID) that persists across state changes.
  • Behavior: encapsulates domain logic relevant to its lifecycle.
  • Distinguishing signal vs. Value Object: ask "does it matter which one it is?" — if yes, it is an Entity.
// [Pseudocode]
Entity Order { id: OrderId; status: OrderStatus; items: Item[] }
// Two Orders with id=42 are the same order even if status changed

一种由身份而非属性定义的对象。即使数据不同,拥有相同ID的两个实体仍视为同一个实体。
  • 特性:拥有稳定、唯一的标识符(ID),在状态变更时保持不变。
  • 行为:封装与其生命周期相关的领域逻辑。
  • 与Value Object的区分:问自己“它是哪一个重要吗?”——如果是,它就是Entity。
// [伪代码]
Entity Order { id: OrderId; status: OrderStatus; items: Item[] }
// 两个id=42的Order视为同一个订单,即使状态不同

Value Object

Value Object(值对象)

An object defined entirely by its attributes. No identity. Immutable. Equality is structural.
  • Has: no ID field. Equality is based on all attribute values.
  • Immutable: replace, never mutate. Operations return new instances.
  • Distinguishing signal vs. Entity: ask "does it matter which one it is?" — if no, it is a Value Object.
// [Pseudocode]
ValueObject Money { amount: Decimal; currency: Currency }
// Money(10, USD) == Money(10, USD) — two instances are equal by value

完全由属性定义的对象。没有身份标识。不可变。基于结构判断相等性。
  • 特性:没有ID字段。相等性基于所有属性的值。
  • 不可变性:替换而非修改。操作返回新实例。
  • 与Entity的区分:问自己“它是哪一个重要吗?”——如果不是,它就是Value Object。
// [伪代码]
ValueObject Money { amount: Decimal; currency: Currency }
// Money(10, USD) == Money(10, USD) — 两个实例按值相等

Aggregate

Aggregate(聚合)

A cluster of domain objects (one Entity as root + optional child objects) treated as a single unit for data changes. All access to internal objects goes through the Aggregate Root.
  • Aggregate Root is the only public entry point. External code holds a reference only to the root.
  • Invariants that span multiple child objects are enforced by the root.
  • Transactions should not span multiple Aggregates — each Aggregate is a consistency boundary.
// [Pseudocode]
Aggregate Order (root) {
  addItem(product, qty) // enforces max-items invariant
  removeItem(itemId)
  confirm()             // guards: status must be DRAFT
}
// External code: order.addItem(…) — never order.items.push(…) directly

一组领域对象(一个Entity作为根 + 可选子对象),作为数据变更的单一单元。所有对内部对象的访问都必须通过聚合根。
  • 聚合根是唯一的公共入口。外部代码仅持有根对象的引用。
  • 跨子对象的不变量由聚合根强制执行。
  • 事务不应跨多个聚合——每个聚合都是一致性边界。
// [伪代码]
Aggregate Order (根) {
  addItem(product, qty) // 强制执行最大商品数不变量
  removeItem(itemId)
  confirm()             // 防护:状态必须为DRAFT
}
// 外部代码:order.addItem(…) — 绝不能直接调用order.items.push(…)

Repository

Repository(仓库)

An abstraction that provides collection-like access to Aggregates. Hides the persistence mechanism from the domain layer.
  • Interface lives in the domain layer. Implementation lives in the infrastructure layer (DIP applied).
  • Methods are domain-language methods (
    findById
    ,
    findByCustomer
    ,
    save
    ) — not SQL or ORM calls.
  • One Repository per Aggregate Root — not per entity or table.
// [Pseudocode]
interface OrderRepository {
  findById(id: OrderId): Order | null
  findByCustomer(customerId: CustomerId): Order[]
  save(order: Order): void
}

提供类似集合的方式访问聚合的抽象。向领域层隐藏持久化机制。
  • 接口位于领域层。实现位于基础设施层(应用DIP原则)。
  • 方法使用领域语言命名(
    findById
    ,
    findByCustomer
    ,
    save
    )——而非SQL或ORM调用。
  • 每个聚合根对应一个Repository——而非每个实体或表对应一个。
// [伪代码]
interface OrderRepository {
  findById(id: OrderId): Order | null
  findByCustomer(customerId: CustomerId): Order[]
  save(order: Order): void
}

Domain Service

Domain Service(领域服务)

A stateless operation that belongs to the domain but does not naturally fit inside a single Entity or Value Object.
  • Stateless: no mutable fields; takes input, returns output or raises domain events.
  • Belongs in the domain layer: contains business rules, not application orchestration.
  • Use when: the operation involves multiple Aggregates or the logic does not belong to any single object.
// [Pseudocode]
DomainService TransferService {
  transfer(from: Account, to: Account, amount: Money): void {
    from.debit(amount)
    to.credit(amount)
    // Invariant: total money in system unchanged
  }
}

属于领域但不适合放在单个Entity或Value Object中的无状态操作。
  • 无状态:没有可变字段;接收输入、返回输出或触发领域事件。
  • 位于领域层:包含业务规则,而非应用编排逻辑。
  • 适用场景:操作涉及多个聚合,或逻辑不属于任何单个对象时。
// [伪代码]
DomainService TransferService {
  transfer(from: Account, to: Account, amount: Money): void {
    from.debit(amount)
    to.credit(amount)
    // 不变量:系统总金额保持不变
  }
}

Application Service

Application Service(应用服务)

Orchestrates domain objects and services to fulfill a single use case. Lives in the application layer, not the domain layer.
  • Thin: no business logic — only coordination (load, call domain, save, emit events).
  • Transaction boundary: one use case = one transaction (typically).
  • Calls domain services and repositories — never bypasses the domain to manipulate entities directly.
// [Pseudocode]
ApplicationService PlaceOrderUseCase {
  execute(cmd: PlaceOrderCommand): OrderId {
    customer = customerRepo.findById(cmd.customerId)
    order = Order.create(customer, cmd.items)   // domain logic in Order
    orderRepo.save(order)
    eventBus.publish(order.domainEvents())
    return order.id
  }
}

编排领域对象和服务以完成单个用例。位于应用层,而非领域层。
  • 轻量化:无业务逻辑——仅负责协调(加载、调用领域逻辑、保存、发布事件)。
  • 事务边界:一个用例对应一个事务(通常)。
  • 调用领域服务和仓库——绝不绕过领域层直接操作实体。
// [伪代码]
ApplicationService PlaceOrderUseCase {
  execute(cmd: PlaceOrderCommand): OrderId {
    customer = customerRepo.findById(cmd.customerId)
    order = Order.create(customer, cmd.items)   // 领域逻辑在Order中
    orderRepo.save(order)
    eventBus.publish(order.domainEvents())
    return order.id
  }
}

Domain Event

Domain Event(领域事件)

A record that something meaningful happened in the domain. Published by Aggregates; consumed by other parts of the system.
  • Immutable: represents a past fact — never modified after creation.
  • Named in past tense:
    OrderPlaced
    ,
    PaymentReceived
    ,
    InventoryDepleted
    .
  • Carries only what happened: not a command, not a query — a fact with a timestamp and relevant data.
// [Pseudocode]
DomainEvent OrderPlaced {
  orderId: OrderId
  customerId: CustomerId
  occurredAt: Timestamp
}

记录领域中发生的重要事件。由聚合发布;被系统其他部分消费。
  • 不可变性:代表已发生的事实——创建后绝不修改。
  • 命名使用过去式
    OrderPlaced
    ,
    PaymentReceived
    ,
    InventoryDepleted
  • 仅包含事件内容:不是命令,不是查询——是带有时间戳和相关数据的事实记录。
// [伪代码]
DomainEvent OrderPlaced {
  orderId: OrderId
  customerId: CustomerId
  occurredAt: Timestamp
}

Anti-Patterns

反模式



God Class

God Class(上帝类)

What it is: one class that accumulates responsibilities across multiple unrelated concerns — persistence, business rules, HTTP handling, formatting, notification, etc.
Detection signals:
  • Class has > 10 public methods spanning unrelated concerns.
  • Multiple unrelated reasons to change (DB schema change AND email template change both affect this class).
  • Class name ends in
    Manager
    ,
    Handler
    ,
    Processor
    , or
    Helper
    and does everything.
Why it is a problem: violates SRP. Any change risks breaking unrelated functionality. Testing requires setting up the entire class even for a small feature.
Corrective direction: identify distinct responsibilities, extract each into a focused class. Apply the "one reason to change" test to each extract.

定义:一个承担了多个不相关职责的类——包括持久化、业务规则、HTTP处理、格式化、通知等。
检测信号
  • 类包含超过10个跨不相关关注点的公共方法。
  • 存在多个不相关的变更理由(数据库schema变更和邮件模板变更都会影响此类)。
  • 类名以
    Manager
    Handler
    Processor
    Helper
    结尾,且负责所有事务。
问题:违反SRP。任何变更都可能破坏不相关的功能。测试时即使是小功能也需要初始化整个类。
修正方向:识别不同的职责,将每个职责提取为专注的类。对每个提取的类应用“单一变更理由”测试。

Anemic Domain Model

Anemic Domain Model(贫血领域模型)

What it is: domain objects (entities, aggregates) are pure data containers — they have fields and getters/setters but no behavior. All business logic lives in service classes.
Detection signals:
  • Entity classes contain only fields, constructors, and getters/setters.
  • Service classes contain all conditionals, calculations, and state transitions that relate to entity data.
  • To understand business rules you must read the service, not the entity.
Why it is a problem: violates DDD. Domain logic leaks into the application layer, becomes scattered across services, and is hard to find, test, or enforce as invariants.
Corrective direction: move business behavior (state transitions, invariant enforcement, calculations) into the entity or value object that owns the data.

定义:领域对象(实体、聚合)是纯数据容器——只有字段和getter/setter,没有行为。所有业务逻辑都放在服务类中。
检测信号
  • 实体类仅包含字段、构造函数和getter/setter。
  • 服务类包含所有与实体数据相关的条件判断、计算和状态转换逻辑。
  • 要理解业务规则必须阅读服务类,而非实体类。
问题:违反DDD原则。领域逻辑泄漏到应用层,分散在多个服务中,难以查找、测试或作为不变量强制执行。
修正方向:将业务行为(状态转换、不变量强制执行、计算逻辑)移至拥有数据的实体或值对象中。

Service as God Object

Service as God Object(上帝服务对象)

What it is: a single application service (or domain service) that directly orchestrates all domain logic with no delegation — it reads entities, applies all business rules inline, and writes results.
Detection signals:
  • Service method is > 50 lines of business logic with no calls to domain object methods.
  • Service knows the internal structure of multiple unrelated aggregates.
  • Removing one service method would break unrelated features.
Why it is a problem: combines the God Class problem with the Anemic Domain Model — SRP is violated at the service level, and domain objects remain empty data bags.
Corrective direction: delegate business logic to domain objects and domain services. The application service should orchestrate (load → call domain → persist → publish) without containing rules itself.

定义:单个应用服务(或领域服务)直接编排所有领域逻辑,不进行委托——它读取实体、直接应用所有业务规则、写入结果。
检测信号
  • 服务方法包含超过50行业务逻辑,且未调用任何领域对象方法。
  • 服务了解多个不相关聚合的内部结构。
  • 删除一个服务方法会破坏不相关的功能。
问题:结合了上帝类和贫血领域模型的问题——服务层违反SRP,领域对象仍为空数据容器。
修正方向:将业务逻辑委托给领域对象和领域服务。应用服务应仅负责编排(加载 → 调用领域逻辑 → 持久化 → 发布事件),不包含业务规则。

Rules

规则

  • Apply SRP before adding a second responsibility to any class, module, or function — extract first, then implement.
  • Apply DIP at every layer boundary: domain → infrastructure, application → domain. Never instantiate a concrete dependency inside a high-level class.
  • Value Objects MUST be immutable. Mutation returns a new instance; it never modifies
    this
    .
  • Aggregate Roots are the sole entry points for state changes on their cluster. No direct mutation of child objects from outside the Aggregate.
  • Repositories are defined as interfaces in the domain layer. Concrete implementations belong in the infrastructure layer.
  • Application Services MUST be thin orchestrators. Business logic inside an Application Service is a violation — move it to the domain object or Domain Service.
  • Domain Events MUST be named in past tense and treated as immutable facts.
  • Code examples in this skill are illustrative only (labeled with language). They are not production templates.
  • 在给任何类、模块或函数添加第二个职责前应用SRP——先提取,再实现。
  • 在每个层边界应用DIP:领域层→基础设施层,应用层→领域层。绝不在高层类中实例化具体依赖。
  • Value Object必须是不可变的。修改操作返回新实例;绝不修改
    this
  • 聚合根是其集群状态变更的唯一入口。外部代码不得直接修改子对象。
  • Repository在领域层定义为接口。具体实现属于基础设施层。
  • Application Service必须是轻量化的编排者。Application Service中包含业务逻辑属于违规——应移至领域对象或Domain Service。
  • Domain Event必须使用过去式命名,并视为不可变的事实。
  • 本技能中的代码示例仅作说明(标注了语言)。它们不是生产模板。