数据服务如何做成可配置查询,而不是一堆统计接口
约 3092 字大约 10 分钟
2026-05-16
很多团队做后台系统时,最先失控的往往不是核心交易链路,而是各种“顺手就做一个”的统计接口、报表接口和看板接口。业务方每提一个口径,后端就加一个 API;筛选条件变了,加一个变种接口;权限范围多了一层,再复制一套查询逻辑。短期看这很快,长期看却会让数据服务变成一堆难以维护的 SQL 入口。
问题的根源通常不在于需求太多,而在于系统没有把“查询”当成一种独立能力来设计。很多实现里,查询逻辑和接口、权限、数据源、字段格式化全部耦合在一起,导致每个新需求都只能靠新增代码承接。接口数量越来越多,真正复用的能力却越来越少。
更可持续的做法,是把“我要查什么”和“这次请求怎么查”拆开。前者是稳定的查询配置,后者是运行时参数。这样一来,后端不必为每个统计需求都开发一条专用 API,而是提供一个统一的查询执行面,把数据源管理、查询定义、权限过滤和结果整形收敛到一套模型里。联犀的数据服务就是沿着这个方向演进的:用 DataSource 管理连接能力,用 DataQueryConfig 定义查询意图,再通过统一执行器完成关系库与时序库的查询落地。
从“统计接口”切换到“查询模型”
如果用传统方式做统计接口,后端常见的结构是 controller 收参数、service 拼 SQL、repo 执行查询、再在 service 里补权限条件。这样的实现不是不能工作,但它有两个明显问题。第一,查询模型无法复用。相似的列表、聚合、分组统计,只要口径略有差异,就会变成另一段新的逻辑。第二,权限容易分散。租户过滤写在 A 接口,项目过滤写在 B 接口,区域过滤又藏在 C 接口,最后没有人能快速回答“这个查询到底受哪些权限影响”。
可配置查询的价值,是把这些不该散落在业务接口里的共性能力提炼出来。一个查询配置至少需要描述几件事:它来自哪个数据源,是表查询还是 SQL 查询,默认字段和排序是什么,允许哪些过滤器,是否需要驼峰转换,以及最关键的,它受哪些权限规则约束。这样,后端新增一个查询能力时,不再默认等价于“新增一个接口实现”,而更像是“注册一个查询模型”。
这个变化看起来只是把 SQL 从代码里搬到配置里,实际上背后是职责的重新划分。接口层的职责缩小为接收请求和转发;执行层负责理解查询配置并生成实际查询;权限层负责把用户上下文翻译成不可绕过的过滤条件;数据源层负责屏蔽底层数据库差异。真正稳定的是这四层之间的边界,而不是某一条具体 SQL。
配置化的关键,不是把一切开放,而是把变化收口
一听到“配置化查询”,很多人会担心系统会不会走向另一个极端:把数据库暴露成一个危险的通用查询器。这个担心是合理的,所以好的可配置查询并不是“任意 SQL 平台”,而是“受约束的查询能力平台”。
联犀的数据服务里,一个重要设计是把数据源配置和查询配置分开。DataSource 解决的是连接谁、用什么驱动、有哪些数据库方言差异;DataQueryConfig 解决的是这个查询对外暴露什么能力。这样做的好处是,底层存储连接可以复用,查询定义可以独立演进,平台也能针对不同数据库做函数模板适配,而不是让上层业务自行处理语法差异。
例如,同样是按天聚合,在 MySQL、PostgreSQL 和 TDengine 上的函数形式并不一样。如果把这些差异放到每个业务接口里,代码会迅速变成一堆数据库特判。更合理的方式,是把日期截断、时间分桶、首值末值这类高频能力抽象成列函数模板,由执行器按数据库类型转换成对应表达式。这样,业务看到的是统一的查询意图,底层处理的是数据库方言。团队真正复用的,不再是某一段 SQL 字符串,而是“时间聚合”这种稳定语义。
同样的思路也适用于过滤器。很多查询并不需要开放完整的 where 语法,但又必须支持常见的筛选操作,例如等于、范围、集合、前缀匹配,以及按函数结果过滤。把这些能力设计成受控的过滤器语法,比开放一堆自定义接口更可控,也比让业务方反复提“再加一个筛选条件”更高效。这里的重点不是追求绝对灵活,而是把 80% 高频变化收进统一模型。
权限不能靠接口自觉,必须内建在查询层
如果一个数据服务只负责执行查询、不负责权限,它很快就会退化成另一个内部 SQL 中转站。因为真正麻烦的从来不是查出数据,而是查出“当前用户应该看到的数据”。尤其在 SaaS 和物联网平台里,租户、项目、区域、角色、设备范围往往同时存在,任何一个查询如果把权限拼装留给业务接口,最终都会变成不可验证的隐式约定。
所以可配置查询要成立,权限必须成为查询模型的一部分,而不是调用方的自律要求。联犀这里的做法,是把权限收敛进 PermConfig。它不只描述“是否按租户过滤”,还定义了软删除字段、角色限制以及基于 Hook 的动态过滤入口。这里最重要的设计取舍,是承认权限并不是一组固定布尔开关,而是需要和业务上下文协同的动态能力。
仅靠 tenant_code 并不能解决同租户内的细粒度权限问题。比如设备数据往往还要按项目和区域继续收口,而这些范围不是查询配置静态写死就能覆盖的。因此数据服务并没有把所有权限逻辑内嵌成固定 SQL,而是通过 dataFilter 这类标准 Hook,把用户上下文中的项目、区域、角色信息翻译成最终 where 条件。这样设计的好处是,查询服务仍然拥有统一入口,但不需要知道每一种业务权限的全部细节。
这也是为什么“权限内建”并不等于“查询服务包办一切”。更准确地说,查询服务负责定义权限注入点和执行顺序,业务侧负责提供可复用的过滤能力。两者之间通过稳定的 Hook 契约协作,而不是在每个接口里重新拼一遍条件。对外看,这仍然是一套统一查询服务;对内看,它已经具备了跨业务域复用的权限翻译能力。
统一查询面的真正价值,是跨存储而不是跨字段
很多团队在做数据服务时,会先从关系库开始,等接入时序库、日志库、分析库后,再额外加几套“专用查询接口”。这样做的问题是,接口表面虽然都叫查询,背后却是完全不同的调用模型。使用方需要分别学习不同的 API、不同的过滤语义和不同的结果结构,平台很快又回到“能力分散”的老路。
联犀在数据服务上的另一个重点,是让关系库和 TDengine 这类时序库共享尽量一致的查询入口。这里的目标不是抹平底层数据库的一切差异,而是统一调用方式、配置方式和权限模型。调用方不用关心这次查询最终走的是 GORM 还是 TDengine 原生执行器,只需要知道自己在调用一个被平台治理过的查询能力。
这件事的价值不只体现在开发效率上,还体现在系统边界更清晰。关系型表适合承载主数据和业务对象,时序库适合承载设备历史和高频指标。以前它们往往意味着两套世界:两套 API、两套权限、两套聚合语法。统一查询面之后,差异仍然存在,但被压缩到了执行层和函数模板层,而不是暴露给每一个业务调用方。
这也是可配置查询优于“统计接口合集”的根本原因。统计接口的复用单位太粗,往往是一整个业务场景;可配置查询的复用单位更细,是数据源连接、函数模板、过滤器、权限钩子和执行器。这些能力一旦沉淀下来,新增一个数据看板所需的工作量就会显著下降,因为团队复用的不再是某个旧接口,而是整套查询基础设施。
这种设计适合什么,不适合什么
把数据服务做成可配置查询,并不意味着所有查询都应该走这条路。它非常适合列表、聚合、分组统计、趋势分析、跨数据源看板等“结构稳定、口径可配置”的场景。尤其当系统里已经出现大量相似接口,只是数据表、过滤条件或聚合维度略有不同,这类能力最值得收口。
但它并不适合承载复杂业务事务,也不适合替代领域服务本身。凡是涉及跨实体状态变更、强业务约束、复杂编排决策的逻辑,仍然应该留在清晰的业务接口里。查询模型擅长的是读侧抽象,不是把所有业务都表达成 SQL 配置。换句话说,可配置查询是为了减少查询类接口爆炸,而不是为了把平台变成低代码数据库前台。
还有一个边界容易被忽略:配置化会提升平台能力密度,也会提高治理要求。查询配置需要审计,权限模型需要一致,函数模板要可维护,缓存策略要可解释。如果团队没有这些基础设施意识,贸然推进配置化,最后可能只是把原本分散在代码里的复杂度搬进数据库配置表。真正有效的前提,是先把模型边界定义清楚,再用配置承载稳定变化。
结语
数据服务是否会演化成一堆统计接口,本质上取决于团队如何理解“查询”这件事。如果把查询只看成业务接口的一部分,那么每个需求都会推着代码继续膨胀;如果把查询提升为一层独立能力,就有机会把数据源、语义、权限和执行方式沉淀成平台基础设施。
可配置查询的意义,不只是少写几个接口,而是让查询能力从“需求驱动的临时实现”变成“可治理、可复用、可扩展的系统部件”。当一个团队开始用这种方式建设数据服务时,后续面对的就不再是“下一个统计接口怎么写”,而是“这类查询是否已经可以被现有模型表达”。这两种问题的维护成本,长期看完全不是一个量级。
更新日志
2026/5/18 10:48
查看所有更新日志
43ef3-docs(blog): 新增后端与架构系列技术博客 18 篇,更新原有 7 篇于
