convex-ddd-architecture
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex DDD Architecture
Convex DDD 架构
Reference skill for organizing Convex projects with DDD and Hexagonal architecture. It keeps domain logic isolated from database and external API concerns so changes remain local and safer to evolve.
这是使用DDD(领域驱动设计)和六边形架构组织Convex项目的参考指南。它将领域逻辑与数据库和外部API相关关注点隔离开来,使得变更仅影响局部区域,演进更安全。
When to Use
适用场景
Use this skill when work includes one or more of these signals:
- New Convex sub-domain design (,
schema,queries,mutations,domain)adapters - Legacy Convex code migration toward DDD/Hexagonal boundaries
- Business rules drifting into handlers instead of aggregates
- Direct access spreading outside repositories
ctx.db - External API calls requiring retries, orchestration, or translation layers
- Team-level need for consistent file layout and naming in Convex projects
Do not use this as a strict template for tiny prototypes where speed matters more than architectural boundaries.
当工作包含以下一种或多种情况时,可使用本指南:
- 新的Convex子域设计(、
schema、queries、mutations、domain)adapters - 将遗留Convex代码迁移至DDD/六边形架构边界
- 业务规则逐渐渗透到处理器而非聚合根中
- 直接调用的代码扩散到仓库之外
ctx.db - 外部API调用需要重试、编排或转换层
- 团队需要在Convex项目中保持一致的文件结构和命名规范
在小型原型项目中,如果速度比架构边界更重要,则不建议使用本指南。
Project Shape
项目结构
./convex/
_generated/ # Auto-generated by Convex (do not edit)
_shared/ # Cross-domain utilities
_libs/
aggregate.ts # Base aggregate interface
repository.ts # Base repository interface
_triggers.ts # Central trigger registry
customFunctions.ts # Wrapped mutation/query exports
schema.ts # Composed schema from all sub-domains
[subDomainName]/ # Each sub-domain folder (camelCase)
_libs/
stripeClient.ts # Libs or helpers
_tables.ts # Database schema tables
_triggers.ts # Sub-domain trigger handlers
_seeds.ts # Seeds for models
_workflows.ts # Convex workflows
queries/
[queryName].ts # One query per file, export default
mutations/
[mutationName].ts # One mutation per file, export default
domain/
[modelName].model.ts # Model schema, types, Aggregate
[modelName].repository.ts # Repository interface
adapters/
[actionName].action.ts # External API actions
[modelName].repository.ts # Repository implementation./convex/
_generated/ # Convex自动生成(请勿编辑)
_shared/ # 跨域工具类
_libs/
aggregate.ts # 基础聚合根接口
repository.ts # 基础仓库接口
_triggers.ts # 中央触发器注册表
customFunctions.ts # 封装后的mutation/query导出文件
schema.ts # 由所有子域组合而成的总schema
[subDomainName]/ # 每个子域文件夹(小驼峰命名)
_libs/
stripeClient.ts # 工具类或助手函数
_tables.ts # 数据库schema表定义
_triggers.ts # 子域触发器处理器
_seeds.ts # 模型种子数据
_workflows.ts # Convex工作流
queries/
[queryName].ts # 每个文件对应一个query,默认导出
mutations/
[mutationName].ts # 每个文件对应一个mutation,默认导出
domain/
[modelName].model.ts # 模型schema、类型、聚合根
[modelName].repository.ts # 仓库接口
adapters/
[actionName].action.ts # 外部API操作
[modelName].repository.ts # 仓库实现Naming Rules
命名规则
- Files: Use camelCase (,
contactRepository.ts)sendInvoice.action.ts - Underscore prefix: For non-domain files (,
_tables.ts)_triggers.ts - Directory vs file: Start with a file (for example ), split into a directory after growth
_workflows.ts
- 文件:使用小驼峰命名法(如、
contactRepository.ts)sendInvoice.action.ts - 下划线前缀:用于非领域文件(如、
_tables.ts)_triggers.ts - 文件与目录的转换:初始使用单个文件(如),当内容增长后再拆分为目录
_workflows.ts
Quick Reference
快速参考
| Concern | Rule |
|---|---|
| Convex imports | Import |
| Function exports | One function per file with |
| Domain model shape | Include |
| Persistence boundary | Access DB through repositories in |
| External integrations | Keep translation in actions; business decisions stay in mutations/aggregates |
| Schema | Compose root schema from each sub-domain |
| 关注点 | 规则 |
|---|---|
| Convex导入 | 从 |
| 函数导出 | 每个文件对应一个函数,使用 |
| 领域模型结构 | 包含 |
| 持久化边界 | 通过 |
| 外部集成 | 转换逻辑放在操作中;业务决策保留在mutation/聚合根中 |
| Schema | 根Schema由每个子域的 |
Core Patterns
核心模式
1) Custom Functions Boundary
1) 自定义函数边界
Always import , , from , not from . See custom-functions.md.
mutationqueryinternalMutationcustomFunctions.ts_generated/servertypescript
// ✅ Correct
import { mutation } from "../../customFunctions";
// ❌ Wrong - bypasses trigger integration
import { mutation } from "../../_generated/server";始终从导入、、,而非从导入。详见custom-functions.md。
customFunctions.tsmutationqueryinternalMutation_generated/servertypescript
// ✅ 正确
import { mutation } from "../../customFunctions";
// ❌ 错误 - 绕过了触发器集成
import { mutation } from "../../_generated/server";2) API Path Convention
2) API路径约定
One function per file with named definition and default export:
typescript
// convex/combat/mutations/createBattle.ts
import { mutation } from "../../customFunctions";
import { v } from "convex/values";
const createBattle = mutation({
args: { heroId: v.id("heroProfiles") },
handler: async (ctx, args) => {
// ...
},
});
export default createBattle;Frontend usage with suffix:
.defaulttypescript
import { api } from "@/convex/_generated/api";
useMutation(api.combat.mutations.createBattle.default);
useQuery(api.economy.queries.getHeroProfile.default);Avoid named exports like - this creates redundant paths like .
export const createBattleapi.combat.mutations.createBattle.createBattle每个文件对应一个函数,包含命名定义和默认导出:
typescript
// convex/combat/mutations/createBattle.ts
import { mutation } from "../../customFunctions";
import { v } from "convex/values";
const createBattle = mutation({
args: { heroId: v.id("heroProfiles") },
handler: async (ctx, args) => {
// ...
},
});
export default createBattle;前端使用时需添加后缀:
.defaulttypescript
import { api } from "@/convex/_generated/api";
useMutation(api.combat.mutations.createBattle.default);
useQuery(api.economy.queries.getHeroProfile.default);避免使用命名导出如 - 这会产生冗余路径,例如。
export const createBattleapi.combat.mutations.createBattle.createBattle3) Schema Composition
3) Schema组合
Compose schema from sub-domain tables:
typescript
// convex/schema.ts
import { defineSchema } from "convex/server";
import { combatTables } from "./combat/_tables";
import { economyTables } from "./economy/_tables";
export default defineSchema({
...combatTables,
...economyTables,
});从子域表定义组合根Schema:
typescript
// convex/schema.ts
import { defineSchema } from "convex/server";
import { combatTables } from "./combat/_tables";
import { economyTables } from "./economy/_tables";
export default defineSchema({
...combatTables,
...economyTables,
});4) Domain + Repository + Adapter Roles
4) 领域 + 仓库 + 适配器角色
- Domain models and aggregates define invariants (domain-models.md)
- Repositories isolate persistence logic (repositories.md)
- Actions adapt external DTOs and call mutations for business transitions (adapters.md)
- Triggers and workflows orchestrate reliable side effects (triggers.md)
- 领域模型和聚合根定义不变量(详见domain-models.md)
- 仓库隔离持久化逻辑(详见repositories.md)
- 适配器转换外部DTO并调用mutation完成业务流转(详见adapters.md)
- 触发器和工作流编排可靠的副作用(详见triggers.md)
5) Workflow and Trigger Safety
5) 工作流与触发器安全性
- Prefer one-way flow: UI mutation -> scheduled action/workflow -> mutation -> reactive query
- Keep trigger handlers lightweight; schedule async work when possible
- Treat trigger code as transaction-sensitive
- 优先采用单向流:UI mutation -> 调度操作/工作流 -> mutation -> 响应式query
- 保持触发器处理器轻量化;尽可能调度异步工作
- 将触发器代码视为事务敏感代码
Common Mistakes
常见错误
- Importing handlers directly from and bypassing shared wrappers
_generated/server - Writing business rules in actions or handlers instead of aggregates
- Updating records with ad-hoc field mutations rather than aggregate transitions
- Returning raw records where aggregate behavior is expected
- Introducing required schema fields without staged migration strategy (migrations.md)
- 直接从导入处理器,绕过共享封装
_generated/server - 在操作或处理器中编写业务规则,而非聚合根中
- 使用临时字段变更更新记录,而非通过聚合根流转
- 在需要聚合根行为的场景返回原始记录
- 未采用分阶段迁移策略就引入必填Schema字段(详见migrations.md)
Supporting References
参考文档
- custom-functions.md
- domain-models.md
- value-objects.md
- repositories.md
- adapters.md
- triggers.md
- migrations.md
- learnings.md
- eslint-rules.md
- examples.md
- custom-functions.md
- domain-models.md
- value-objects.md
- repositories.md
- adapters.md
- triggers.md
- migrations.md
- learnings.md
- eslint-rules.md
- examples.md