关于 convex-ddd-architecture
convex-ddd-architecture 是一项参考技能,指导团队用领域驱动设计和六边形架构组织 Convex 项目。它通过约定、模式和参考文档,帮助隔离业务规则、持久化逻辑及外部集成。
主要功能
- 01DDD 与六边形架构指引
- 02领域、仓储和适配器边界
- 03Convex Schema 与文件约定
- 04自定义函数和触发器模式
- 05迁移与命名参考
适用场景
- 01规划新的 Convex 子领域
- 02将旧 Convex 代码迁向 DDD
- 03将业务规则从处理器中分离
- 04统一 Convex 项目结构
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sebas5384/ agentic-stuff --skill convex-ddd-architecture安装后在 Claude Code 中输入/convex-ddd-architecture确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/adapters.mdcustom-functions.mddomain-models.mdeslint-rules.mdexamples.mdlearnings.mdmigrations.mdnaming.mdrepositories.mdtriggers.mdvalue-objects.md详细介绍
Convex DDD 架构
用于通过 DDD 和六边形架构组织 Convex 项目的参考技能。它将领域逻辑与数据库及外部 API 相关逻辑隔离开来,让变更局限于局部,演进时也更安全。
适用场景
当工作涉及以下一个或多个信号时,使用此技能:
- 设计新的 Convex 子领域(
schema、queries、mutations、domain、adapters) - 将旧版 Convex 代码迁移到 DDD/六边形架构边界
- 业务规则逐渐进入处理器,而不是聚合
- 直接访问
ctx.db的代码扩散到仓储之外 - 外部 API 调用需要重试、编排或转换层
- 团队需要在 Convex 项目中统一文件布局和命名
- 选择或重构标识符(变量、函数):导出项/模型使用领域名词;模块/函数内部若作用域已能明确含义,则使用较短名称(naming.md)
对于速度比架构边界更重要的小型原型,不要将此规范作为严格模板。
项目结构
./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
命名规则
- 文件:使用 camelCase(
contactRepository.ts、sendInvoice.action.ts) - 下划线前缀:用于非领域文件(
_tables.ts、_triggers.ts) - 目录与文件:从文件开始(例如
_workflows.ts),扩展后再拆分为目录 - 标识符:导出项、模型和 API 使用领域词汇;在函数内部,若父级名称/类型已提供上下文,则优先使用简短名词——不要在每个局部名称中重复文件名或父级名称。详情请参阅:naming.md。
快速参考
| 关注点 | 规则 |
|---|---|
| Convex 导入 | 从 customFunctions.ts 导入 mutation、query、internalMutation |
| 函数导出 | 每个文件一个函数,并使用 export default |
| 领域模型形状 | 包含 _id、_creationTime,并通过 withoutSystemFields 创建不含系统字段的 New<Model> |
| 持久化边界 | 通过 adapters/ 中的仓储访问数据库 |
| 外部集成 | 在操作中进行转换;业务决策留在 mutations/聚合中 |
_tables.ts | 仅包含 defineTable(NewXModel.fields) 和索引——字段定义放在领域模型文件中,绝不内联定义 |
| Schema | 从各子领域导出的 _tables 组合根 schema |
核心模式
1) 自定义函数边界
始终从 customFunctions.ts 导入 mutation、query、internalMutation,而不是从 _generated/server 导入。请参阅 custom-functions.md。
// ✅ Correct
import { mutation } from "../../customFunctions";
// ❌ Wrong - bypasses trigger integration
import { mutation } from "../../_generated/server";
2) API 路径约定
每个文件一个函数,使用命名定义和默认导出:
// 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;
前端使用时添加 .default 后缀:
import { api } from "@/convex/_generated/api";
useMutation(api.combat.mutations.createBattle.default);
useQuery(api.economy.queries.getHeroProfile.default);
避免使用 export const createBattle 这样的命名导出——这会产生 api.combat.mutations.createBattle.createBattle 这样的冗余路径。
3) Schema 组合
通过子领域表组合 schema:
// 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-models.md)
- 仓储隔离持久化逻辑(repositories.md)
- 操作适配外部 DTO,并调用 mutations 执行业务状态转换(adapters.md)
- 触发器和工作流编排可靠的副作用(triggers.md)
5) 工作流与触发器安全性
- 优先采用单向流程:UI mutation -> 定时操作/工作流 -> mutation -> 响应式查询
- 保持触发器处理器轻量;尽可能安排异步工作
- 将触发器代码视为对事务敏感的代码
常见错误
- 局部变量名称过于冗长,只是重复文件或函数的上下文,而非缩小作用范围或提取更小的单元(naming.md)
- 直接从
_generated/server导入处理程序,绕过共享封装 - 在操作或处理程序中编写业务规则,而非放在聚合中
- 通过临时修改字段更新记录,而非使用聚合状态转换
- 在需要聚合行为的场景中返回原始记录
- 添加必填架构字段时未制定分阶段迁移策略(migrations.md)
- 在
_tables.ts中内联定义字段,而不是使用defineTable(NewXModel.fields)——这会导致架构重复,并破坏模型作为事实来源的不变性 - 开始新的子领域时,将现有(可能并非基于 DDD 的)模块用作参考——务必从
examples.md中借鉴模式,而不要参考可能早于这些指南的相邻代码
参考资料
配套文档位于 references/ 目录中。
编写任何新的子领域代码前请先阅读:
- examples.md — 完整的实践示例(发票领域、RPG 游戏),展示完整技术栈:模型 →
_tables.ts→ 仓储接口 → 适配器 → 变更。从这里开始。 - learnings.md — 从迁移中总结出的重要规则;涵盖
_tables.ts的陷阱、DTO 的放置位置以及渐进式重构策略。
任务涉及以下特定主题时请阅读:
- domain-models.md —
*Model验证器结构、withoutSystemFields、聚合类结构和状态转换 - repositories.md —
createRepository工厂、接口约定、方法命名(by、list、with) - adapters.md — 外部 API 适配器结构、DTO 转换边界
- triggers.md — 触发器注册、安全规则、调度副作用
- migrations.md — 架构变更策略、扩展-迁移-收缩
- custom-functions.md — 函数封装详情和认证变体
- naming.md — 标识符约定、通用语言
- value-objects.md — 不可变值对象模式(无
_id、静态目录) - eslint-rules.md — 针对导入边界的强制 lint 规则
评论
支持 Markdown