ModelCraft:低代码底座 - 数据模型的设计
从一入职就从事数据模型设计,到成长为负责人,再到离开。深度了解项目各种优缺点后,经历 8 个月 Vibecoding 重写一版。目标曾是国内版 Supabase,以 MySQL 为底座。
项目仓库:luker1228/modelcraft
背景
我做了很多年数据模型设计。从入行到项目负责人,完整经历过一个数据模型产品的全生命周期。离开后,又用 8 个月 Vibecoding 重写了一版。
分享这个项目的原因很简单:它不像商城那种烂大街的项目,这是一个很有意思的项目。
而且这里我一个人完成了全栈的设计。
为什么是 MySQL
在最开始,几乎所有人都在问同一个问题:为什么不是 PostgreSQL?Supabase 选 PG,国外所有低代码/Backend-as-a-Service 都在用 PG,你非要选 MySQL,是不是在给自己挖坑?
这个问题背后有一个假设:PG 比 MySQL 强,所以做新项目应该选 PG。
这个假设本身没什么问题。PG 在很多方面确实更强——扩展类型、查询能力、生态工具。如果这个项目是从零开始、面向技术选型自由的场景,PG 大概率是更好的技术选择。
但我的判断逻辑倒过来了:先不选数据库,先搞清楚用户是谁。
低代码平台的用户,到底是谁?
不是互联网大厂的 infra 团队,他们有能力自己搭。也不是纯技术创业者,他们倾向于用 Supabase 或 Firebase。
低代码平台最大的用户群,是国内的中小企业和传统企业里的开发团队。团队不大、技术栈偏保守、DBA 只会 MySQL——这是最常见的画像。
你去看国内企业的数据库格局:MySQL 占了绝对主导。Oracle 在大型国企,PG 在小部分技术先锋团队,但腰部以下的企业,几乎全是 MySQL。数据量不大、并发不高、但“稳定、有人会管”是第一需求。
这意味着:
- 这类企业的现有数据,大概率在 MySQL
- 他们的技术团队,最熟悉的是 MySQL
- 他们引入一个新系统时,“能不能接我现有的 MySQL 数据”几乎是必问题
所以选择 MySQL 不是一个技术决策,是一个市场决策。
架构的核心思路
表面上看有两个问题:和组件交互、让开发者用起来爽。但这两个问题的优先级完全不同。
组件是我们内部的系统,协议设计得多烂都可以兼容。 内部的东西总能改,总能加适配层,总能硬编码。这不是瓶颈。
真正难的是另一面——面向开发者的那一面。
开发者不是内部团队。他们没有义务适应你的协议。他们可以选择不用。所以面向开发者,这三样东西缺一不可:
- 完善的文档 — 你的模型有哪些能力、怎么用、边界在哪,必须清楚,不需要靠猜。比如“查询多条限制最大 1000 条”——开发者不需要等报错才知道,看一眼文档/协议就应该明确知道这个边界
- 完善的错误机制 — 出错了,错误信息必须告诉开发者“哪里错了、为什么错、怎么改”,而不是一个泛化的 500。比如模型插入重复数据,报错能不能和 MySQL 一样清晰,让 AI 一眼就看明白?
- 丰富的能力 — 查询、权限、校验、实时推送……开发者需要的东西,你得有,而且得好用
这三条,每一条单独拿出来都不难。但合在一起,就决定了这套系统的上限。
而且这里有一个很有意思的推论:当这三条做到位了,AI 自然就能用好它。
为什么?因为 AI 本质上也是一个开发者。它也是读文档、看错误信息、调 API。如果你的协议自解释、错误结构化、能力边界清晰,AI 不需要专门训练就能直接上手。这不只是为了“AI 时代”的噱头,而是“文档 + 错误 + 能力”这三条做到位后的自然结果。
所以核心思路很简单:面向开发者,把文档、错误、能力做透。 内部组件兼容性是次要的,它在架构上从来不是真正的瓶颈。
RLS 能力
RLS(Row-Level Security)是配套 HTTP 访问能力的核心安全设计。原因很直接:MySQL 不支持 Policy。
PostgreSQL 有原生 RLS——你可以在表上创建 policy,定义哪些行可见、哪些行可写,数据库引擎在查询层自动拦截。但 MySQL 没有,选 MySQL 意味着自己实现 RLS。
我的设计思路很直接:模仿 PostgreSQL 的 USING + WITH CHECK 语义,用 CEL(Common Expression Language)作为策略表达式语法,在中间件层执行策略评估。
策略模型
每一个 Policy 包含三个要素:
{ "name": "user_own_data", "action": "read", "role": "user", "usingExpr": "user_id == auth.user_id", "withCheckExpr": "user_id == auth.user_id"}USING 控制可见性(SELECT 时自动过滤),WITH CHECK 控制写入合法性(INSERT/UPDATE 时校验)。
为什么用 CEL
CEL 是 Google 开源的表达式语言,专为策略评估场景设计。类型安全、沙箱执行、生态成熟——不需要自己维护解析器。
一个典型的策略表达式:
dept_id == auth.dept_id && status != "deleted"通配角色与 RBAC
Policy 的 Role 是弱耦合设计——不是外键,只是一个匹配标签。这套系统既有 B2B 也有 B2C:
- B2B:正常配置角色体系,Policy 按角色精确匹配
- B2C:不需要 RBAC,Role 填
*(通配符),退化为表级权限。代码实现:HasRole("*")永远返回 true
所有命中的 Policy 都是 OR 叠加——这是 RBAC 的本质,用户的权限来自所有角色的并集。PostgreSQL 的 RLS 同时支持 AND 和 OR,更灵活,但业务层面 RBAC 只需要 OR。默认拒绝:无匹配 Policy 的操作直接拒绝。
设计态与运行态
模型的设计天然可以拆成两个阶段:设计态 和 运行态。
这两个阶段的核心区别,决定了整个系统的架构走向。
设计态:不做 DDL
设计态只做一件事:管理增强 meta。
这里有一个关键约束:模型本身不会触发任何 DDL 操作。
为什么?因为在 ToB 场景里,DDL 本质上是高危操作。
想想实际的企业环境:
- 数据库不是你一个人的,DBA 团队掌握着变更权限
ALTER TABLE可能导致锁表,锁表意味着线上服务会抖- 很多企业的变更窗口是固定的——每周二凌晨两点,还得先提审批单
- 有些核心表甚至不允许改结构,只能用新增表的方式“绕”
如果每次你在设计态拖一个字段、改一个类型,系统就直接 ALTER TABLE——那这套系统根本进不了企业的生产环境。
所以设计态的定位很清楚:它只管理 meta,不碰数据库结构。
那数据库的结构变化怎么处理?靠同步功能。
模型提供一条“同步”指令:将数据库当前的 meta 信息(表、字段、类型、约束)拉取上来,与模型中的增强 meta 做映射。用户在设计态调整增强信息(校验规则、展示组件、权限标签),但底层表结构保持不动。
这意味着:
- 数据库结构变更走企业自己的 DDL 流程(DBA 审批、窗口执行)
- 模型层在结构变更后执行一次“同步”,把新结构映射上来,补充增强信息
- 两者解耦,互不阻塞
运行态:GraphQL 是第一等接口
设计态处理完 meta 后,进入运行态——这才是真正对外暴露能力的阶段。
说实话,不是我们选择了 GraphQL,是 GraphQL 完美适配了这里。
因为 GraphQL 的核心能力和这套架构的需求几乎是一一对应的:
- 动态 Schema:增强 meta 实时解析为 GraphQL Schema,模型新增字段、Schema 自动包含——不需要手写和维护
- Introspection:调用方直接查 Schema 就能知道所有能力、参数、限制,不需要翻文档
- 类型安全:查询在请求层就校验,不会把错误的查询结构发到数据库层
- 结构化错误:GraphQL 的错误本身就是结构化的,能精确指出哪个字段、什么原因
这些特性恰好覆盖了我们前面说的“文档、错误、能力”三个维度。不是我们硬把业务塞进 GraphQL 的范式里,而是 GraphQL 的设计范式刚好能表达我们的需求。
但 GraphQL 本身不代表一切——它的设计也要合理。 同一个模型,GraphQL Schema 暴露得好不好,取决于 meta 设计得是否工整。我看遍了 GraphQL 的最佳实践,把文档反反复复读了不下十遍,才设计出这套协议。ModelCraft 的核心工作之一就是把“增强 meta → GraphQL Schema”这条映射链路做清楚,确保暴露出来的每个类型、每个字段、每个参数都是开发者不需要猜的。
所以从设计态到运行态的链路很清楚:设计态管理增强 meta → 运行态解析为 GraphQL Schema → 开发者(包括 AI)通过 introspection 直接理解模型。
本文是 ModelCraft 系列的总篇,后续会从 RLS 实现细节、查询 DSL 设计、Schema 映射链路、权限系统与审批流集成等方向逐一展开讲解。
关注我
如果你也在做数据模型、低代码平台、或者对如何用 AI 重构后端架构感兴趣,欢迎关注我的微信公众号,后续会持续分享 ModelCraft 的架构拆解和实践复盘。
