万能查询契约设计

467 阅读3分钟

低代码平台的元数据查询是最核心的功能之一,而低代码平台面向的查询场景非常复杂,最普通的有:分页、排序、按需、字段条件以及关联对象条件、多查询操作符等。

条件分页排序查询+按需返回

基于上面场景,可以设计查询契约如下:

interface SortConfigItem {
  field: string;
  order: 'asc' | 'desc';
}
interface EntityConfig {
  fields: string[];
  entities: { code: string; fields: string[] }[];
}
interface FieldCondition {
  code: string;
  operation: SearchOperation;
  value: any
}
interface ConditionsConfig {
  fields?: Array<FieldCondition>;
  entities?: Array<{ code: string, fields: FieldCondition[] }>
}
interface QueryConfig {
  sort: SortConfigItem[],
  entity: EntityConfig,
  pageNo: number,
  pageSize: number,
  conditions: ConditionsConfig,
}
  • pageNo/pageSize: 分页参数
  • sort: 排序参数,可以支持多字段升序降序
  • entity: 需要返回的字段,其中fields是当前元数据对象需要返回的字段,entities是当前元数据对象的关联对一
  • conditions: 当前查询条件,其中fields表示查询的当前对象的字段条件,entities表示关联对象的字段条件

demo:

{
  sort: [{ field: 'applys', order: 'asc' }],
  entity: {
    fields: ['classification', 'name', 'applys', 'remark', 'id'],
    entities: [{ code: 'template', fields: ['name', 'id'] }],
  },
  pageNo: 1,
  pageSize: 20,
  conditions: {
    fields: [
      { code: 'classification', operation: 'eq', value: ['1'] },
      { code: 'applys', operation: 'in', value: ['f', 'd'] },
    ],
    entities: [
      {
        "code": "template",
        "fields": [
          {
            "code": "name",
            "operation": "eq",
            "value": [
              "模板1"
            ]
          }
        ]
      }
    ],
  },
};

分组查询

分组查询是要支持多组条件or/and组合,并且需要支持多层嵌套。组在逻辑中的表示是一对括号,这里理解的一个难点是,怎样算一个组。

比如下面的组合,最佳处理是将a and b看成一组,c or d 看成一组,最终两个条件看成一组。组处理的原则是,最终形成的括号越少越好。

image.png

group的定义

group实际上是一个括号,并且该括号内部必定有一个操作符:or/and。该操作符定义了group内部多个条件之间的关系。

ts定义如下:

interface FieldsConditinGroup extends ConditionsConfig{
  operation: SearchOperation.OR | SearchOperation.AND;
  }

type QueryConditionGroup = {
  operation: SearchOperation.OR | SearchOperation.AND;
  groups: QueryConditionGroup[];
}| FieldsConditinGroup

interface QueryConditionConfig {
  sort: SortConfigItem[];
  entity: EntityConfig;
  pageNo: number;
  pageSize: number;
  conditions: ConditionsConfig|QueryConditionGroup;
}

这里需要将原本普通查询条件放入一个group中,该group的操作符是and

demo:

const queryBody:QueryConfig = {
  sort: [],
  entity: {
    fields: ['classification', 'name', 'applys', 'remark', 'id'],
    entities: [{ code: 'template', fields: ['name', 'id'] }],
  },
  pageNo: 1,
  pageSize: 20,
  conditions: {
    operation: SearchOperation.AND,
    groups: [
      { operation: SearchOperation.AND, fields: [], entities: [] },
      {
        operation: SearchOperation.OR,
        groups: [
          {
            operation: SearchOperation.AND,
            fields: [
              { code: 'classification', operation: SearchOperation.EQUAL, value: ['1'] },
              { code: 'name', operation: SearchOperation.EQUAL, value: ['电脑'] },
            ],
          },
          {
            operation: SearchOperation.AND,
            fields: [
              { code: 'classification', operation: SearchOperation.EQUAL, value: ['2'] },
              { code: 'name', operation: SearchOperation.EQUAL, value: ['测试'] },
            ],
          },
        ],
      },
    ],
  },
};

实践中的问题

实践中这套契约基本满足了所有场景的查询需求,甚至包括了统计查询,limit查询,并且导出接口也是基于以上契约。

然而,这套契约过于复杂。

在实践中,最理想的场景是,使用元数据sdk原生的查询接口+元数据低代码视图。但现实是,很多项目会组合低代码视图+元数据sdk+自身后端服务+外部接口。

因为低代码视图强绑定了这套复杂契约,那么如果查询接口要改为非sdk提供的查询,要么后端适配前端契约,要么前端通过拦截器将这套复杂契约改为对应接口的契约,这个过程相当痛苦,然而灵活和易用不可兼得。

另外,分组查询时的结构和普通查询的结构不一致,如果原本拦截器中按照普通查询的结构处理,当开启分组查询后,该拦截器代码需要随之更新。但这一点不管如何设计都无法避免,只能通过配置时的说明提示项目组该风险点。