errors
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDDD 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 's .
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.
neverthrowResult<T, E>- No Enums: You MUST NOT use 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
enum.Error - TypeScript Error Branding: Every custom error class MUST include using a
declare private readonly [unique]: never. This strictly prevents duck-typing issues and allows 100% safeunique symbolchecks when exhaustively resolving the error viainstanceofin the Server Action layer.assertNever - Payload Objects: Except for wrapping native errors (e.g., Node.js or Database instances), constructors MUST ALWAYS accept a single
Errorobject argument for dependencies/properties, even if it only has one property.params: { ... } - Strict Encapsulation: Internal properties capturing the invalid value or context MUST be typed as Value Objects (or Branded primitives). They MUST be with explicit
private readonlygetters. You MUST NOT expose properties directly via.get...()access modifiers.public - Message Formatting: When building the message string inside the constructor, you MUST extract the primitive from the Value Object using its
super(...)method (or its semantic equivalent like.toBranded())..getEncoded()
在本架构中,领域验证过程中绝不会将错误作为标准异常“抛出”。相反,它们会被严格类型化,并作为失败结果封装在的中。
本文档中的关键词“MUST(必须)”、“MUST NOT(禁止)”、“REQUIRED(必填)”、“SHALL(应)”、“SHALL NOT(不应)”、“SHOULD(建议)”、“SHOULD NOT(不建议)”、“RECOMMENDED(推荐)”、“MAY(可)”和“OPTIONAL(可选)”的解释遵循RFC 2119规范。
neverthrowResult<T, E>- 禁止使用枚举:在用例/领域层(Actions层可以,但领域层绝对禁止),你不得使用或字符串联合类型来表示不同的错误场景或映射。每个领域错误必须是一个继承自
enum的独立类。Error - TypeScript错误标记:每个自定义错误类必须使用声明
unique symbol。这能严格防止鸭子类型问题,并允许在Server Action层通过declare private readonly [unique]: never完全解析错误时,进行100%安全的assertNever检查。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., ), 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.
InvalidEmailError - Shared Domain Errors: If an error spans multiple entities, use cases, or represents a core domain rule (e.g., ,
FolderAlreadyExistsError), define it in the shared domain errors location as dictated by the architecture skill.UserNotFoundError
- 本地错误(内聚式定义):如果某个错误仅用于验证单个值对象(如),则在该值对象的同一文件中定义该错误。如果某个错误仅由特定用例的解析逻辑抛出,则与该用例一起定义。
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 inside our branded Domain Architecture. This is the only time the constructor argument should be an instance.
ErrorErrortypescript
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错误)时,目标是将原生封装到我们的标记化领域架构中。这是构造函数参数为实例的唯一场景。
ErrorErrortypescript
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 instead of .
Result.err()throwtypescript
// ✅ 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()throwtypescript
// ✅ 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 folder:
.agents/skills/errors/references/- 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 - 展示可选参数与条件消息的领域错误示例。