Hasura 数据连接器指南:SQL OLTP 数据库的模型与命令抽象解析
【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine
本文以 dc-agents/guides/OLTP.md 为骨架,围绕"Hasura + SQL(OLTP)"这一主题展开:先梳理 OLTP 场景下人们对 SQL 数据库的核心诉求,再深入讲解 Hasura 如何把表、视图、函数抽象为模型(Model)、把事务抽象为命令(Command),并结合
v3-engine的 Open DDS 元数据定义与 NDC(Data Connector)代理协议,给出可落地的建模思路与源码级佐证。
为什么需要单独讨论 "Hasura + SQL(OLTP)"
在 dc-agents/guides 这一组数据连接器指南中,OLTP、OLAP、NoSQL、Redis、Cassandra、Kafka 各占一篇。把 OLTP 单独拎出来,是因为它在数据使用模式上与 OLAP、NoSQL 有本质区别。指南开篇就总结了人们对 SQL 数据库的三类典型诉求:
- 规范化数据(Normalize their data):把数据拆成多个表、消除冗余,从而"容易保证一致性"(easy to ensure consistency);
- 快速读取小规模关联数据(Read short amounts of related data quickly):通过指定过滤(filter)与排序(sort)谓词,在关联表中快速取回数据;
- 事务化写入(Write data in transactions):通过事务保证写入过程的一致性。
这三条诉求分别对应关系型数据库的三大能力:模式(schema)设计、索引与查询优化、ACID 事务。它们也正是下文"模型"与"命令"两种抽象的出发点——读多走模型,写走命令。
作为对照,OLAP 指南强调 OLAP 存储关注"用 SQL 从数据中提取业务洞察"、数据往往反规范化且一致性由上游保证;NoSQL 指南则聚焦嵌套文档建模与$lookup/$graphLookup等 MongoDB 特性。理解这些差异有助于读者判断:什么时候用"模型 + 命令"的组合来建模 OLTP 数据源是合适的。
Hasura + SQL(OLTP)的总体叙事
指南给出了 Hasura 对这一类数据库的核心抽象方式,全文只有两点,却是理解 v3 引擎数据建模的钥匙:
- 表、视图和返回关系的函数(tables, views and functions that return relations)成为模型(Models);
- 事务成为命令(Commands):命令接收一个输入模型(input model),其"身体"是对该事务的调用(invocation),而输出关系(output relation)则作为输出模型(output model)。
也就是说,读路径上,任何"能返回一张关系表"的东西——不管是物理表、视图,还是返回结果集的函数——都可以统一建模为可查询的"模型";写路径上,凡是需要在数据库里以事务方式执行的逻辑,都被封装为"命令",命令的入参用输入模型描述、出参用输出模型描述。这种划分与 CQRS(命令查询职责分离)的思想一脉相承,在 rfcs/v3/command-mutations.md 中也有明确表述:"reads 非常适合 models 抽象,而 writes 更适合 command graph"。
模型(Model):把表、视图、函数统一为可查询抽象
模型在 Open DDS 元数据中的定义
在 v3 引擎中,"模型"是 Open DDS(Open Data Definition Schema)元数据的核心概念之一。从源码结构看,v3/crates/metadata-resolve/src/stages/models/types.rs 中定义的Model结构包含:
name:限定名(qualified name),如subgraph.model_name;data_type:该模型每一行对应的自定义类型(CustomTypeName),即"行"的结构化类型;type_fields:字段定义集合,决定模型在 GraphQL schema 中暴露哪些字段;arguments:模型级参数(例如按参数过滤的源参数);source:数据来源(ModelSource),指向某个数据连接器(Data Connector Link)上的集合(collection);unique_identifiers:能唯一定位一行记录的字段集合;aggregate_expression:可选的聚合表达式,用于聚合查询。
其中ModelSource(models/types.rs)进一步说明:一个模型的数据源由一个data_connector(NDC 代理链接)、一个collection(集合名)和collection_type(集合的对象类型)构成。这印证了指南中的表述——"表、视图、返回关系的函数"在连接器侧统一表现为 NDC 的 collection,在引擎侧统一表现为 Model。
从 SQL 对象到模型的映射
结合 dc-agents/DOCUMENTATION.md 中对/schema端点的描述:NDC 代理向引擎返回的 schema 中,每个实体都有一个type字段,取值可以是"table"或"view",并带有insertable/updatable/deletable三个可变性标志;每个字段(列)也带有insertable/updatable标志,以及value_generated(如auto_increment自增主键)等元数据。也就是说:
- 表:通常
insertable/updatable/deletable全为 true; - 视图:通常不可写(三者为 false),只能作为只读模型查询;
- 返回关系的函数:在 NDC schema 中以
functions形式出现(type: "read"),在 v3 中被建模为可带参数的模型(parameterized model)或命令。
参考实现 dc-agents/reference/src/data/Functions.schema.json 中就有Fibonacci这类函数示例,说明函数确实是数据连接器 schema 的一等公民。
模型参数化:OLTP 场景的价值
对 OLTP 数据库而言,"读短量关联数据"往往需要带参数:SELECT * FROM orders WHERE customer_id = $1。v3 的模型天然支持参数(arguments+source_arguments),这正好对应 OLAP 指南里提到的"parameterized models"能力。在 v3 中,模型参数可以映射到 NDC collection 的参数(argument_mappings),也可以来自数据连接器链接的预设值(data_connector_link_argument_presets)。
命令(Command):用输入/输出模型封装事务
命令在 Open DDS 元数据中的定义
命令是 OLTP 写路径的核心抽象。v3/crates/metadata-resolve/src/stages/commands/types.rs 中定义的Command结构包含:
name:命令的限定名;output_type:命令的输出类型(即"输出模型"对应的自定义类型);arguments:命令的参数(即"输入模型"的字段);graphql_api:命令如何暴露为 GraphQL 根字段(GraphQlRootFieldKind、根字段名、弃用标记);source:命令的数据源(CommandSource),其中source字段类型为DataConnectorCommand,而DataConnectorCommand在 open_dds::commands 中被定义为FunctionName或ProcedureName二选一。
关键点在于:NDC 侧的"函数(function)"被映射为只读命令,NDC 侧的"过程(procedure)"被映射为写命令。CommandsIssue中的FunctionArgumentMappingIssue与ProcedureArgumentMappingIssue两条错误分支(commands/types.rs)也佐证了这一区分。这与指南"事务成为命令"的表述完全吻合:一个事务性的 SQL 过程(stored procedure)在连接器侧是 procedure,在引擎侧就是一个可调用、可带输入/输出模型的命令。
命令与模型的关系:输入模型与输出模型
指南原文说得很精炼:命令"takes an input model, whose body is the invocation of the transaction, and the output relation is the output model"。拆开看:
- 输入模型:即命令的
arguments集合。调用方以"结构化对象"的形式传入参数,而不是裸的 SQL 拼接——这是 Hasura 把事务安全地暴露给 GraphQL 的关键; - 输出模型:即命令的
output_type。事务执行后返回的关系(result set)被映射为一个可查询的模型结构,调用方可以像查询模型一样选择返回字段。
这种"命令 = 输入模型 + 事务调用 + 输出模型"的封装,把数据库事务变成了 GraphQL 中的一个 mutation 根字段,同时保留了事务的原子性语义。
权限与命令:角色注解机制
OLTP 场景中"谁可以执行哪个事务"至关重要。v3 引擎通过 Open DDS 的命令权限(v3/crates/metadata-resolve/src/stages/command_permissions/types.rs)和角色注解机制实现。在 v3/docs/roles-and-annotations.md 中有完整说明:引擎在编译期为每个命令构建带角色注解的 schema,例如给user-1角色预设user_id参数为x-hasura-user-id会话变量,从而让该角色只能删除自己;而admin角色则保留完整的命令签名。这为"命令如何被安全地暴露给终端用户"提供了可验证的实现路径。
从协议视角看模型与命令的落地
NDC 代理的端点分工
无论是模型还是命令,最终都要落到 NDC(Data Connector)代理协议上执行。dc-agents/DOCUMENTATION.md 规定的核心端点恰好对应读与写两条路径:
GET /capabilities:声明代理能力(是否支持关系、主外键、插值查询、标量类型及其比较运算符等);POST /schema:返回数据 schema(表/视图/函数及其列定义)——这是"哪些对象可以成为模型"的来源;POST /query:执行查询——承载模型的读路径,接收结构化的QueryRequest(target、where、order_by、limit、offset、fields、aggregates、relationships),返回rows/aggregates;POST /mutation:执行变更——承载命令的写路径;GET /health:健康检查。
其中/query的请求结构(QueryRequest)在参考实现 dc-agents/reference/src/query.ts 中有完整的 TypeScript 类型导入,包括Query、Expression、ExistsExpression、OrderBy、Aggregate等——这些正是引擎把 GraphQL 查询翻译为连接器可执行结构时的中间语言。
查询能力:OLTP"短量关联读取"的协议支撑
指南强调 OLTP 用户"通过指定 filter/sort 谓词快速读取短量关联数据"。这在 NDC 协议中对应:
- 过滤(filter):
where是一个递归表达式结构,支持and/or/not/exists/binary_op(less_than、equal等)/binary_arr_op(in)/unary_op(is_null),且exists可以基于related(关联表)或unrelated(无关表)进行子查询——这为"带关系的谓词过滤"提供了表达能力; - 排序(order_by):支持按列排序及跨关系排序(
OrderByRelation); - 分页(limit / offset / aggregates_limit):注意
limit只限制返回行数,aggregates_limit只限制聚合统计范围,两者相互独立,且引擎会把表级 select 权限的 row limit 合并进limit——这是 OLTP 场景下细粒度访问控制的一个具体体现; - 关系(relationships):
relationship类型字段支持 object(多对一)与 array(一对多)两种关系,引擎会在记录级加上列映射等值谓词后递归执行子查询。
这些能力共同支撑了"规范化数据 + 关联读取"这一 OLTP 典型模式,也解释了为什么 NDC 协议要把关系、过滤、排序、分页都结构化地表达出来,而不是让连接器暴露裸 SQL。
变更与原子性:命令的事务语义保障
NDC 的/mutation协议为"命令"提供了原子性分级声明(capabilities 中的mutations.atomicity_support_level):
row:单操作内部分行失败时只回滚失败行;single_operation:单操作内任一行失败则整个操作回滚;homogeneous_operations:同一请求内同类型多操作,任一失败全部回滚;heterogeneous_operations:同一请求内任意类型多操作,任一失败全部回滚(最高级别,最接近 ACID 事务语义)。
代理还可以声明returning能力,即变更后返回受影响行——这正是命令"输出模型"的协议基础。连接器按自己的真实能力声明级别,引擎据此决定能否把多个变更组织成具有事务语义的"命令"。
更进一步:命令集的扩展方向
指南本身只给出了"模型 + 命令"的静态框架,但 rfcs/v3/command-mutations.md 补充了围绕命令的未来演化方向,可以作为理解这一抽象边界的参考:
- 非阻塞写入:为命令增加可选的
on_complete/on_error回调,形成"continuation"风格的事件驱动写入,适合事件溯源(event sourcing)类应用; - 事务性命令集(transactional_command_set):引擎在请求校验阶段确保命令集内所有命令落在同一数据源且该数据源支持事务,从而把多个命令打包成一个原子执行单元;
- 多连接器原子性:一般后端无法跨连接器保证原子性,但可以通过连接器层的互斥(mutex)来近似实现;
- 任意校验:参考 Docker 网络模型,权限在进入 Hasura 集群边界时校验一次,集群内部的连接器互调不再重复校验,从而允许"只能经 TS 连接器间接调用某命令"的编排模式。
这些方向都建立在"命令 = 输入模型 + 事务调用 + 输出模型"这一基础抽象之上,也再次印证了指南将 OLTP 写路径统一为命令的设计初衷。
小结
回到 dc-agents/guides/OLTP.md 的核心结论:
| SQL OLTP 能力 | Hasura 抽象 | 协议/元数据落点 |
|---|---|---|
| 规范化数据(表/视图/函数) | 模型(Model) | Open DDSModel+ NDC/schema、/query |
| 短量关联读取(filter/sort) | 模型查询 | NDCQueryRequest(where/order_by/relationships) |
| 事务化写入 | 命令(Command) | Open DDSCommand+ NDC/mutation原子性分级 |
对开发者而言,理解这套抽象的关键收益在于:读路径上,表、视图、函数在引擎侧被统一为"模型",获得一致的过滤、排序、分页、关系与权限能力;写路径上,事务被封装为"命令",以输入模型描述参数、以输出模型描述结果,并通过 NDC 的原子性分级获得可声明的保障。当你需要为新的 OLTP 数据源编写数据连接器(Data Connector Agent)时,dc-agents/reference 参考实现与 dc-agents/DOCUMENTATION.md 规范是继续深入的最佳入口。
【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考