convex-ddd-architecture

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex 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
    ctx.db
    access spreading outside repositories
  • 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
    _workflows.ts
    ), split into a directory after growth
  • 文件:使用小驼峰命名法(如
    contactRepository.ts
    sendInvoice.action.ts
  • 下划线前缀:用于非领域文件(如
    _tables.ts
    _triggers.ts
  • 文件与目录的转换:初始使用单个文件(如
    _workflows.ts
    ),当内容增长后再拆分为目录

Quick Reference

快速参考

ConcernRule
Convex importsImport
mutation
,
query
,
internalMutation
from
customFunctions.ts
Function exportsOne function per file with
export default
Domain model shapeInclude
_id
,
_creationTime
, plus
New<Model>
without system fields
Persistence boundaryAccess DB through repositories in
adapters/
External integrationsKeep translation in actions; business decisions stay in mutations/aggregates
SchemaCompose root schema from each sub-domain
_tables
export
关注点规则
Convex导入
customFunctions.ts
导入
mutation
query
internalMutation
函数导出每个文件对应一个函数,使用
export default
导出
领域模型结构包含
_id
_creationTime
,以及不含系统字段的
New<Model>
持久化边界通过
adapters/
目录下的仓库访问数据库
外部集成转换逻辑放在操作中;业务决策保留在mutation/聚合根中
Schema根Schema由每个子域的
_tables
导出组合而成

Core Patterns

核心模式

1) Custom Functions Boundary

1) 自定义函数边界

Always import
mutation
,
query
,
internalMutation
from
customFunctions.ts
, not from
_generated/server
. See custom-functions.md.
typescript
// ✅ Correct
import { mutation } from "../../customFunctions";

// ❌ Wrong - bypasses trigger integration
import { mutation } from "../../_generated/server";
始终从
customFunctions.ts
导入
mutation
query
internalMutation
,而非从
_generated/server
导入。详见custom-functions.md
typescript
// ✅ 正确
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
.default
suffix:
typescript
import { api } from "@/convex/_generated/api";
useMutation(api.combat.mutations.createBattle.default);
useQuery(api.economy.queries.getHeroProfile.default);
Avoid named exports like
export const createBattle
- this creates redundant paths like
api.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;
前端使用时需添加
.default
后缀:
typescript
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 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
    _generated/server
    and bypassing shared wrappers
  • 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