数据模型设计器

拆解低代码平台的数据模型设计器:实体与字段的类型系统、一对一与一对多与多对多的关系建模、从模型到物理表的 DDL 生成、迁移与演进策略、索引与约束、外部数据源抽象、校验规则,以及模型与 UI 的联动与多环境版本管理,给出可直接使用的模型 Schema 与建表 SQL,回答如何让业务人员安全地改数据结构。

引言

数据模型设计器是低代码平台里最「危险」的组件。它让不懂数据库的人也能定义实体、字段、关系,一键生成物理表。方便的另一面是风险:一次误操作可能删列、改类型、丢数据。所以设计器的核心不是「能建表」,而是「能安全地建表、可回滚地演进」。

另一个常被低估的问题是「模型的双重身份」。在低代码平台里,数据模型既是存储结构的定义(物理表),又是界面的绑定目标(表单字段、列表列、查询条件)。同一份模型要被数据库、后端 API、前端渲染三处消费。如果这三处各自解释模型,就会漂移;正确做法是模型作为唯一真相,物理表、API、UI 都由它派生。

本文按「类型系统 → 关系建模 → 物理表生成 → 迁移 → 索引约束 → 外部数据源 → 校验 → UI 联动 → 多环境」展开,每节给出具体的 Schema 定义与 SQL。读完后你应当能判断:一个数据模型设计器在哪些地方必须保守,在哪些地方可以激进。

目录

  1. 数据模型设计器的职责
  2. 实体与字段的类型系统
  3. 关系建模
  4. 从模型到物理表
  5. 迁移与演进
  6. 索引与约束
  7. 数据源抽象与外部表
  8. 校验规则与模型约束
  9. 模型与 UI 的联动
  10. 版本与多环境

1. 数据模型设计器的职责

负责:
  - 定义实体、字段、类型、约束
  - 定义实体间关系与级联行为
  - 生成 DDL 并执行迁移
  - 为 API 生成 CRUD 接口
  - 为 UI 提供字段元数据

不负责:
  - 复杂业务逻辑(那是后端服务)
  - 复杂查询与报表(那是查询层)
  - 跨库分布式事务

职责边界决定了设计器的复杂度上限:它只做「结构」,不做「逻辑」。

2. 实体与字段的类型系统

类型系统是模型的基础,也是最需要克制的地方。

type FieldType =
  | 'string' | 'text' | 'integer' | 'decimal'
  | 'boolean' | 'date' | 'datetime' | 'enum'
  | 'json' | 'file' | 'ref';

interface EntityField {
  id: string;
  name: string;            // 列名,snake_case
  label: string;
  type: FieldType;
  length?: number;         // string 长度
  precision?: number;      // decimal 精度
  scale?: number;
  nullable?: boolean;
  unique?: boolean;
  defaultValue?: unknown;
  enumValues?: string[];   // enum 取值
  refEntity?: string;      // ref 指向的实体
  comment?: string;
}
平台类型PostgreSQLMySQL说明
stringvarchar(n)varchar(n)需指定长度
texttexttext长文本
integerintegerint整数
decimalnumeric(p,s)decimal(p,s)金额
booleanbooleantinyint(1)布尔
datetimetimestamptzdatetime带时区
jsonjsonbjson结构化数据
enumvarchar + checkenum枚举

平台类型与物理类型必须有一层映射,不要让用户直接选 varchar(255)。这层映射让平台可以在换数据库时统一翻译。

3. 关系建模

关系是数据模型的核心,也是最容易做错的部分。

一对一(1:1):用户 ↔ 用户档案
  外键放在从表,加唯一约束

一对多(1:N):订单 → 订单项
  外键放在「多」的一方

多对多(M:N):学生 ↔ 课程
  需要中间表,含两个外键 + 联合唯一
{
  "entities": {
    "order": {
      "fields": [
        { "name": "id", "type": "string", "unique": true },
        { "name": "customer_id", "type": "ref", "refEntity": "customer" }
      ]
    },
    "order_item": {
      "fields": [
        { "name": "id", "type": "string" },
        { "name": "order_id", "type": "ref", "refEntity": "order", "nullable": false },
        { "name": "qty", "type": "integer" }
      ]
    }
  },
  "relations": [
    { "type": "1:N", "from": "order", "to": "order_item", "onDelete": "cascade" }
  ]
}

3.1 级联行为的默认值

onDelete 的默认值应该是 restrict 而不是 cascade。删除主表连带删除从表数据是最危险的操作,必须让用户显式选择,且在删除前提示影响行数。

4. 从模型到物理表

DDL 生成是把模型变成可执行 SQL 的过程。

-- 由模型生成的建表语句(PostgreSQL)
CREATE TABLE "app_1024"."order" (
  id           varchar(36) PRIMARY KEY,
  customer_id  varchar(36) NOT NULL REFERENCES "app_1024"."customer"(id),
  amount       numeric(12,2) NOT NULL DEFAULT 0,
  status       varchar(20) NOT NULL DEFAULT 'draft',
  created_at   timestamptz NOT NULL DEFAULT now(),
  updated_at   timestamptz NOT NULL DEFAULT now(),
  tenant_id    varchar(36) NOT NULL,
  CONSTRAINT chk_status CHECK (status IN ('draft','paid','shipped'))
);

CREATE INDEX idx_order_tenant ON "app_1024"."order"(tenant_id);
生成规则:
  1. 每个实体一张表
  2. 每个字段一列(类型映射)
  3. 主键固定为 id(varchar(36) 或 bigserial)
  4. 审计列固定:created_at / updated_at / created_by
  5. 多租户加 tenant_id 并建索引
  6. ref 字段生成外键约束
  7. 唯一约束与 check 约束按模型生成

4.1 系统列的约定

不要把所有系统列都暴露给用户编辑。created_at、updated_at、tenant_id 应由平台自动维护,用户可见但不可改。

5. 迁移与演进

模型改动的落地必须走迁移,且每一步可回滚。

迁移类型:
  新增表         → CREATE TABLE(安全)
  新增可空列     → ALTER TABLE ADD COLUMN(安全)
  新增非空列     → 需默认值或分步(先可空 → 回填 → 设非空)
  改列类型       → 可能锁表,需评估(用 USING 转换)
  改列名         → 破坏性,需同步更新引用
  删列           → 最危险,先软删(标记废弃)再物理删
interface Migration {
  id: string;
  version: number;
  up: string[];      // 正向 SQL
  down: string[];    // 回滚 SQL
  destructive: boolean;  // 是否破坏性
  requiresConfirm?: string; // 需用户输入确认短语
}

5.1 分步迁移非空列

-- 第 1 步:加可空列
ALTER TABLE "order" ADD COLUMN remark varchar(200);
-- 第 2 步:回填历史数据
UPDATE "order" SET remark = '' WHERE remark IS NULL;
-- 第 3 步:设为非空
ALTER TABLE "order" ALTER COLUMN remark SET NOT NULL;

对千万级表,第 3 步在 PostgreSQL 11+ 是元数据操作(快),但 MySQL 可能需要重建表,必须用在线 DDL 工具。

6. 索引与约束

索引由平台自动建议,但允许用户调整。

自动生成的索引:
  - 主键(隐含唯一索引)
  - 外键列(加速关联查询)
  - tenant_id(多租户隔离)
  - 高频过滤字段(基于查询统计推荐)

用户可加:
  - 唯一索引(业务唯一)
  - 复合索引(多列组合查询)
CREATE UNIQUE INDEX uk_order_no ON "order"(tenant_id, order_no);
CREATE INDEX idx_order_status_created ON "order"(tenant_id, status, created_at DESC);

约束分三类:主键约束、唯一约束、检查约束。外键约束在多租户平台要谨慎——跨租户的外键必须避免,且删除时的影响要可控。

7. 数据源抽象与外部表

设计器不只管「平台自建的表」,还要能挂接外部数据源。

数据源类型:
  1. 平台内建表(由设计器管理 DDL)
  2. 外部库只读(映射已有表,不管理 DDL)
  3. 外部 API(虚拟实体,无物理表)

统一抽象:
  Entity {
    kind: 'internal' | 'external' | 'virtual',
    ddlManaged: boolean,
    ...
  }
interface Entity {
  id: string;
  name: string;
  kind: 'internal' | 'external' | 'virtual';
  ddlManaged: boolean;     // 平台是否管理其 DDL
  fields: EntityField[];
  dataSourceId?: string;   // 外部数据源引用
}

对外部表,设计器只做「映射与元数据同步」,绝不允许改其结构——否则会破坏其他系统。

8. 校验规则与模型约束

模型层的校验是数据质量的最后一道防线。

三层校验:
  1. 数据库层:NOT NULL / UNIQUE / CHECK / FK
  2. 应用层:字段级校验(长度、范围、格式)
  3. 业务层:跨实体规则(如库存足够)

低代码设计器通常管前两层,第三层交给流程/服务。
{
  "name": "amount",
  "type": "decimal",
  "precision": 12,
  "scale": 2,
  "nullable": false,
  "validators": [
    { "type": "min", "value": 0 },
    { "type": "max", "value": 1000000 }
  ]
}

校验规则同时写入数据库约束与元数据,这样绕过平台直连数据库也受约束保护。

9. 模型与 UI 的联动

模型是 UI 的上游,改模型要能自动反映到界面。

联动链路:
  模型字段 → 表单字段(自动生成默认表单)
  模型字段 → 列表列(自动生成默认列表)
  模型关系 → 关联选择器(ref 字段渲染成下拉)
  模型枚举 → 选项源

重命名字段的联动:
  模型层改 name → 扫描所有引用 → 批量更新绑定 → 生成迁移

这要求平台维护一份「引用图」:哪个表单、哪个列表、哪个流程引用了哪个字段。没有引用图,重命名就会留下大量断链。

10. 版本与多环境

模型变更必须跨环境(开发 → 测试 → 生产)一致地推进。

环境流转:
  开发环境改模型 → 生成迁移脚本 → 版本化
  → 测试环境执行 → 验证
  → 生产环境执行 → 记录

要点:
  - 模型版本与应用版本绑定
  - 迁移脚本随版本归档,可重放
  - 生产迁移需审批 + 备份
  - 结构漂移检测(实际表结构 vs 模型定义)

10.1 结构漂移

有人绕过平台直接改了数据库,导致实际表结构与模型定义不一致。平台应定期比对并告警,必要时提供「反向同步」把实际结构拉回模型。

权衡取舍

决策点选项 A选项 B建议
类型暴露直接选物理类型平台类型映射映射,便于换库
级联删除默认 cascade默认 restrictrestrict,安全优先
删列立即物理删先软删后物理删软删,留回滚窗口
外部表可改结构只读映射只读,避免破坏他系统
迁移自动直改生成脚本 + 审批脚本,可回滚可审计

常见坑清单

  1. 默认级联删除:删主表连带删从表,需默认 restrict 并提示影响行数。
  2. 直接暴露物理类型:换数据库时全量返工,需平台类型映射层。
  3. 加非空列一步到位:大表锁表或失败,需先可空、回填、再设非空。
  4. 删列即物理删:无法回滚,应先标记废弃、观察后再删。
  5. 无引用图:重命名字段留下断链,必须维护引用关系并批量更新。
  6. 外部表可改结构:破坏其他系统,外部表必须只读映射。
  7. 校验只写在应用层:绕过平台直连数据库即可绕过校验,需下沉到数据库约束。
  8. 跨租户外键:多租户下数据越权,外键必须带 tenant_id。
  9. 无结构漂移检测:手改数据库后模型与实际不一致,需定期比对告警。
  10. 迁移脚本不归档:环境不一致时无法重放,脚本必须随版本归档。

小结

数据模型设计器的骨架是「类型系统 → 关系 → DDL 生成 → 迁移 → 索引约束 → 数据源抽象 → 校验 → UI 联动 → 多环境」。贯穿全文的一条主线是保守:默认不级联、删除先软删、外部表只读、迁移走脚本。数据模型是平台里最难回滚的部分,激进的设计在这里代价最高。

另一条主线是单一真相:模型同时驱动物理表、API 与 UI,三者必须由同一份模型派生,否则必然漂移。这与 元数据驱动架构设计 的核心原则一致。

模型定义好之后,下一步是让它「动起来」:数据如何被流程编排、如何在节点间流转,见 低代码与工作流引擎集成 。而模型在多租户下的隔离与扩展,见 多租户与 SaaS 化落地 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「低代码」更多文章

  1. 自定义代码与逃生舱
  2. 低代码应用测试与质量
  3. 连接器与 API 编排