errors

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

DDD Error Management

DDD错误管理

1. Core Principles (RFC 2119)

1. 核心原则(RFC 2119)

Errors in this architecture are never "thrown" as standard exceptions during domain validation. They are instead strictly typed and returned as failures inside
neverthrow
's
Result<T, E>
. The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
  • No Enums: You MUST NOT use
    enum
    or string union types to represent different error cases or mappings natively within the Use Cases/Domain (Actions can, but Domain MUST NOT). Each domain error MUST be a distinct class extending
    Error
    .
  • TypeScript Error Branding: Every custom error class MUST include
    declare private readonly [unique]: never
    using a
    unique symbol
    . This strictly prevents duck-typing issues and allows 100% safe
    instanceof
    checks when exhaustively resolving the error via
    assertNever
    in the Server Action layer.
  • Payload Objects: Except for wrapping native errors (e.g., Node.js or Database
    Error
    instances), constructors MUST ALWAYS accept a single
    params: { ... }
    object argument for dependencies/properties, even if it only has one property.
  • Strict Encapsulation: Internal properties capturing the invalid value or context MUST be typed as Value Objects (or Branded primitives). They MUST be
    private readonly
    with explicit
    .get...()
    getters. You MUST NOT expose properties directly via
    public
    access modifiers.
  • Message Formatting: When building the
    super(...)
    message string inside the constructor, you MUST extract the primitive from the Value Object using its
    .toBranded()
    method (or its semantic equivalent like
    .getEncoded()
    ).
在本架构中,领域验证过程中绝不会将错误作为标准异常“抛出”。相反,它们会被严格类型化,并作为失败结果封装在
neverthrow
Result<T, E>
中。 本文档中的关键词“MUST(必须)”、“MUST NOT(禁止)”、“REQUIRED(必填)”、“SHALL(应)”、“SHALL NOT(不应)”、“SHOULD(建议)”、“SHOULD NOT(不建议)”、“RECOMMENDED(推荐)”、“MAY(可)”和“OPTIONAL(可选)”的解释遵循RFC 2119规范。
  • 禁止使用枚举:在用例/领域层(Actions层可以,但领域层绝对禁止),你不得使用
    enum
    或字符串联合类型来表示不同的错误场景或映射。每个领域错误必须是一个继承自
    Error
    的独立类。
  • TypeScript错误标记:每个自定义错误类必须使用
    unique symbol
    声明
    declare private readonly [unique]: never
    。这能严格防止鸭子类型问题,并允许在Server Action层通过
    assertNever
    完全解析错误时,进行100%安全的
    instanceof
    检查。
  • 负载对象:除了包装原生错误(如Node.js或数据库
    Error
    实例)之外,构造函数必须始终接受单个
    params: { ... }
    对象作为参数,用于传递依赖项/属性,即使它只有一个属性。
  • 严格封装:捕获无效值或上下文的内部属性必须被类型化为值对象(或标记化原始类型)。它们必须是
    private readonly
    的,并通过显式的
    .get...()
    获取器暴露。你不得通过
    public
    访问修饰符直接暴露属性。
  • 消息格式化:在构造函数中构建
    super(...)
    消息字符串时,必须使用值对象的
    .toBranded()
    方法(或语义等效的方法,如
    .getEncoded()
    )提取原始值。

2. Location & Granularity

2. 位置与粒度

  • Local Errors (Co-location): If an error is strictly limited to validating a single Value Object (e.g.,
    InvalidEmailError
    ), define the error in the exact same file as the Value Object. If an error is only emitted by a specific Use Case parsing logic, define it with the Use Case.
  • Shared Domain Errors: If an error spans multiple entities, use cases, or represents a core domain rule (e.g.,
    FolderAlreadyExistsError
    ,
    UserNotFoundError
    ), define it in the shared domain errors location as dictated by the architecture skill.
  • 本地错误(内聚式定义):如果某个错误仅用于验证单个值对象(如
    InvalidEmailError
    ),则在该值对象的同一文件中定义该错误。如果某个错误仅由特定用例的解析逻辑抛出,则与该用例一起定义。
  • 共享领域错误:如果某个错误涉及多个实体、用例,或代表核心领域规则(如
    FolderAlreadyExistsError
    UserNotFoundError
    ),则按照架构规范在共享领域错误目录中定义。

3. Creating Domain Errors (Business Rules)

3. 创建领域错误(业务规则)

This format strictly applies for any rules related to validation, business constraints, or entity invariants. Notice the object parameter payload
params: { ... }
.
typescript
import { FolderName } from "../value-objects/folderName";

declare const unique: unique symbol;

export class FolderAlreadyExistsError extends Error {
  // BRANDING: This ensures TypeScript treats this type strictly.
  declare private readonly [unique]: never;
  
  // MUST store the actual Value Object privately
  private readonly folderName: FolderName;

  constructor(params: { folderName: FolderName }) {
    // MUST extract the primitive value via `.toBranded()` for message formatting
    super(`Folder with name "${params.folderName.toBranded()}" already exists.`);

    this.folderName = params.folderName;
  }

  // MUST return the Value Object
  getFolderName(): FolderName {
    return this.folderName;
  }
}
此格式严格适用于任何与验证、业务约束或实体不变量相关的规则。注意参数负载
params: { ... }
的使用。
typescript
import { FolderName } from "../value-objects/folderName";

declare const unique: unique symbol;

export class FolderAlreadyExistsError extends Error {
  // BRANDING: This ensures TypeScript treats this type strictly.
  declare private readonly [unique]: never;
  
  // MUST store the actual Value Object privately
  private readonly folderName: FolderName;

  constructor(params: { folderName: FolderName }) {
    // MUST extract the primitive value via `.toBranded()` for message formatting
    super(`Folder with name "${params.folderName.toBranded()}" already exists.`);

    this.folderName = params.folderName;
  }

  // MUST return the Value Object
  getFolderName(): FolderName {
    return this.folderName;
  }
}

4. Creating Infrastructure Errors (Database/External)

4. 创建基础设施错误(数据库/外部服务)

When catching external exceptions (e.g., Drizzle/Postgres errors), the objective is to wrap the native
Error
inside our branded Domain Architecture. This is the only time the constructor argument should be an
Error
instance.
typescript
declare const unique: unique symbol;

export class RepositoryError extends Error {
  declare private readonly [unique]: never;

  constructor(error: Error) {
    const { message, ...rest } = error;
    super(message, rest);
  }
}
当捕获外部异常(如Drizzle/Postgres错误)时,目标是将原生
Error
封装到我们的标记化领域架构中。这是构造函数参数为
Error
实例的唯一场景。
typescript
declare const unique: unique symbol;

export class RepositoryError extends Error {
  declare private readonly [unique]: never;

  constructor(error: Error) {
    const { message, ...rest } = error;
    super(message, rest);
  }
}

5. Usage in the Application Flow

5. 在应用流程中的使用

In your business logic—like Repositories or Use Cases—these classes are always used inside pure functions returning
Result.err()
instead of
throw
.
typescript
// ✅ RECOMMENDED - Railway flow
if (folders.length > 0) {
  return err(new FolderAlreadyExistsError({ folderName }));
}

// ❌ MUST NOT - Never throw domain validation exceptions directly
if (folders.length > 0) {
  throw new FolderAlreadyExists({ folderName });
}
在业务逻辑中——如Repository或用例——这些类始终在纯函数中使用,返回
Result.err()
而非
throw
typescript
// ✅ RECOMMENDED - Railway flow
if (folders.length > 0) {
  return err(new FolderAlreadyExistsError({ folderName }));
}

// ❌ MUST NOT - Never throw domain validation exceptions directly
if (folders.length > 0) {
  throw new FolderAlreadyExists({ folderName });
}

6. References

6. 参考资料

For examples based on existing models, review real project implementations stored in the
.agents/skills/errors/references/
folder:
  • workspaceNotFoundError.md - Standard domain error taking a single Value Object parameter.
  • pathAlreadyInUseError.md - Domain error showcasing optional parameters and conditional messages.
如需基于现有模型的示例,请查看存储在
.agents/skills/errors/references/
文件夹中的真实项目实现:
  • workspaceNotFoundError.md - 接收单个值对象参数的标准领域错误示例。
  • pathAlreadyInUseError.md - 展示可选参数与条件消息的领域错误示例。