如何设计一个更通用的查询接口

临近放假,手头的事情没那么多,老是摸鱼也不好,还是写写博客吧。

今天来聊聊: 如何设计一个通用的查询接口

首先,我们从一个简单的场景开始。现在,我需要一个订单列表,用来查询【我的订单】,支持分页,且支持高级搜索。

整个查询流程

我们先来设计下整个查询的流程,我认为大致如下图。简单来说就是: 接收查询条件 -》 校验条件 -》添加条件 -》 执行查询 -》 转换 VO -》 返回结果

注意,因为不同公司用的语言或者代码分层可能不一样,所以,我们没必要纠结具体的代码实现,只要 关注一些更高抽象层级的共性就行了。

一些疑问

看到上图的流程,有的人可能会问一些问题,这里我简单回答下:

就拿【我的订单】来说,查询条件中肯定要有【订单所属人】这个条件吧,你放心把这个字段交给前端来设置吗?如果你选择这么做,那么不好意思,这篇文章可能在浪费了您的时间。

如果 VO 里的数据都来自同一个 DB,按理来说,我们可以使用联表的方法直接映射 VO,而不需要在代码中将实体转 VO,像 mybatis 这种类库就可以很轻易地做到这一点。但是,我不建议这么做。因为以后你的数据源可能会分库分表,甚至改成第三方接口、ES、redis 等,到时你还能联表吗?当然,我只是建议尽量不要。

我们的实体中的字段,有可能 太多,也有可能 太少。多指的是,我返回了一些不能返回的字段,例如用户密码;少指的是,前端要的字段,实体里不一定有。这时有人可能会问,如果实体里没有不能返回的字段,且能够完全满足前端的所有字段需求,是不是就可以直接返回。这个嘛,你真的能保证吗?

具体代码实现

这里提供一种简单的 java 实现。

Controller

@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource
    private OrderService orderService;

    @PostMapping("/queryPage")
    public DataResponse> queryPage(@RequestBody OrderQuery query) {
        return DataResponse.of(orderService.queryPage(query));
    }
}

Service

@Service
public class OrderService {
    @Resource
    private OrderGateway orderGateway;

    public Page queryPage(OrderQuery query) {
        // 校验
        validate(query);

        // 添加条件
        addCon(query);

        // 执行查询
        Page sourcePage = orderGateway.queryPage(query);

        // 转换为VO并返回结果
        return ConvertUtils.convertPage(sourcePage, OrderConverter::convert2MyListVO);
    }
}

好了,说完单个场景,我们再来说说多个场景的情况。我需要增加【商场的订单】、【下属的订单】等等。

加接口or不加?

这时,我们有两种选择:加接口 or 不加接口?如果加接口的话,随着场景的增加,我们的接口会越来越多。我相信更多的人会选择不加接口,即用一个查询接口来搞定所有场景。

如何区分不同场景?

那么问题来了,不加接口的情况下,我们应该怎么设计呢?

我们会发现, 不同的场景,查询的流程都是一样的,只是在校验条件、添加条件、转换 VO 三个节点的逻辑上有所区别。对应上图的步骤 2、3、8。于是,针对这三个节点,我们需要根据不同的场景走不同的逻辑,类似于大家常说的策略模式,当然,这样做要有一个前提,就是我们能够区分请求是来自哪个场景。

其中一个实现就是,在 query 中增加一个 scenarioFlag 字段,由调用方传值,当查【我的订单】时值为 OrderQryPage2Me,当查【商场的订单】时值为 OrderQryPage2Market······

如何实现?

这里我还是提供简单的 java 实现。实际使用的话会更复杂一些。

Controller

这时,返回值的泛型就不能写死了,因为同一个接口有可能返回不同的类型。这一点相信很多人都没法接受。

@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource
    private OrderService orderService;

    @PostMapping("/queryPage")
    public DataResponse queryPage(@RequestBody OrderQuery query) {
        return DataResponse.of(orderService.queryPage(query));
    }
}

Service

我用的是阿里的 Cola 框架来处理不同场景的策略分发,每个场景中差异化的逻辑都放在一个可插拔的的扩展点里,而扩展点根据【业务-用例-场景】来划分。具体实现如下。

前面说过,不同场景只是在校验条件、添加条件、转换 VO 三个节点的逻辑上有所区别,然而,还是存在某些场景,连执行查询这个节点的逻辑也不一样。这里也兼容了这种情况。

@Service
public class OrderService {
    @Resource
    private ExtensionExecutor extensionExecutor;
    @Resource
    private OrderGateway orderGateway;

    public Object queryPage(OrderQuery query) {
        // 设置场景
        BizScenario bizScenario = BizScenario.valueOf(
            ORDER, // 订单业务
            ORDER_QUERY, // 订单查询
            query.getScenarioFlag() // 具体场景
        );

        // 根据不同的场景走不同的逻辑:校验、加条件、转VO
        // 这里的转VO逻辑还没走,只是把逻辑作为Function设置到query里面
        Object result = extensionExecutor.execute(
                OrderQryExtPt.class,
                bizScenario,
                x -> x.extendQuery(query)
                );
        // 如果返回非空对象,则直接将结果返回,不再走通用查询
        if(result != null) {
            return result;
        }

        // 执行通用查询
        result = orderGateway.queryPage(query);

        // 这里才开始走转VO的逻辑
        if(query.getConvertMethod() != null) {
            return query.getConvertMethod().apply(result);
        }
        return result;
    }
}

具体的扩展点如下。里面一般就是差异化的三个节点逻辑。

@Extension(
        bizId = ORDER, // 订单业务
        useCase = ORDER_QUERY, // 订单查询
        scenario = OrderQryPage2Me // 我的订单
        )
public class OrderQryPage2MeExt implements OrderQryExtPt {

    @Override
    public Object extendQuery(OrderQuery query) {

        // 校验
        validate(query);

        // 添加条件 zzs001
        addCon(query);

        // 设置转换VO的逻辑
        Function> convertMethod = x -> {

            Page sourcePage = (Page)x;

            return ConvertUtils.convertPage(sourcePage, OrderConverter::convert2MyListVO);
        };
        query.setConvertMethod(convertMethod);

        return null;
    }

}

要不要万能VO?

上面的例子中,针对不同的场景,我会提供不同的 VO。但有些人会尝试用一个万能的 VO 来应对所有的场景,我认为,这是非常不利于维护的做法。随着场景的增加,你的 VO 字段会越来越多,你根本区分不出来哪些场景需要哪些字段,最重要的是,这种通用 VO 让很多场景不得不去查询一些不需要的字段,而耗费性能。

以上就是我对查询接口设计的一些想法,虽然不算成熟,但也不是纸上谈兵,因为我们的订单系统现在采用的就是这种方式,目前落地效果还是可以的。当然,可能是因为业务还没那么复杂吧。

最后,感谢阅读,欢迎交流、指正。

Original: https://www.cnblogs.com/ZhangZiSheng001/p/15822105.html
Author: 子月生
Title: 如何设计一个更通用的查询接口

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/575772/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 使用clipboard.js复制文字+图片到微信后图片不显示问题处理

    使用clipboard.js复制文字 +图片,粘贴到微信不显示图片,而QQ可以。 解决方案: 图片链接使用http,不要使用https。 使用clipboard.js实现复制功能 …

    数据库 2023年6月14日
    0115
  • Mysql-5.7主从部署-yum方式

    一、环境准备 rpm -qa |grep mariadb |xargs yum remove -y setenforce 0(临时关闭),(selinux配置文件:SELINUX=…

    数据库 2023年5月24日
    066
  • git配置报错fatal: Authentication failed for ”问题解决

    如果在git配置中报错fatal: Authentication failed for ”,其实就是 凭证失败的意思 接着输入一下命令行没有出现要求输入用户名或密码,并…

    数据库 2023年6月11日
    0206
  • 一、SQL高级语句

    摘抄别的博主的博客主要总去CSDN看不太方便自己整理一下加深记忆! 导入文件至数据库 #将脚本导入 source 加文件路径 mysql> source /backup/te…

    数据库 2023年5月24日
    0106
  • Java-课堂笔记

    用Typora重新排版了下, 发现还有挺多问题, 手动排版好像也不识别. 太麻烦就这样了, 课堂笔记只是证明自己没有上课摸鱼, 这里的笔记是当初疫情上网课写的. 以后会按照路线重新…

    数据库 2023年6月11日
    088
  • Win10系统链接蓝牙设备

    进入设备界面,删除已有蓝牙,如果蓝牙耳机已经链接其他设备,先断开链接 点击添加蓝牙或其他设备 Original: https://www.cnblogs.com/itcaimeng…

    数据库 2023年6月11日
    098
  • 翻译 | Kubernetes Operator 对数据库的重要性

    一些刚接触 Kubernetes 的公司尝试使用传统环境中运行数据库的方法在 Kubernetes 中运行数据库。但是,不建议这样做。因为这可能会导致数据丢失,并且也不建议这样管理…

    数据库 2023年5月24日
    0122
  • 商城限时秒杀功能系统

    我们在网购的时候常常会看到”限时””秒杀”等字眼,商家在产品的促销上除了发放优惠券,还喜欢用限时秒杀的方式, 让价格和原本的售价形成…

    数据库 2023年6月14日
    0105
  • Java 考试系统项目源码 springboot mybaits vue.js 支持手机端考试

    新增功能:培训学习模块, PDF电子课程、视频课程、直播课程(自己搭建直播流服务器) 人脸识别(考试时验证,有开关)、补考开关 组建试卷:创建试卷,题目、类型、总分、及格分数、时长…

    数据库 2023年6月6日
    084
  • django-ckeditor上传图片到阿里云OSS

    参考信息 资料1: django_ckeditor上传图片到七牛云OSS 使用 1. 安装django-ckeditor 【参考 资料1 】 2. setting.py配置INST…

    数据库 2023年6月9日
    0104
  • Could not initialize class org.apache.maven.plugin.war.util.WebappStructureSerializer

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    数据库 2023年6月6日
    089
  • Maven项目POM文件设置依赖

    https://www.cnblogs.com/stars-one/p/10958796.html 可以参考这个链接 这里个添加依赖 如果在如上界面找不到 请设置一下本地仓库 Or…

    数据库 2023年6月9日
    0118
  • MySQL 的日志:binlog

    前言:binlog 用于记录数据库执行 写入性操作的日志信息,以二进制的形式保留在磁盘中。它是由 Server 层进行记录的,使用任何存储引擎都会产生 binlog。 实验准备 我…

    数据库 2023年5月24日
    088
  • Linux–>shell

    shell是什么 Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序。 用户可以用Shell来启动,挂起,停止,编写一些程序。 S…

    数据库 2023年6月14日
    0109
  • MySQL——基础查询与条件查询

    基础查询 /* 语法: select 查询列表 from 表名; 类似于:System.out.println(打印东西); 1、查询列表可以是:表中的字段、常量值、表达式、函数 …

    数据库 2023年5月24日
    0117
  • zabbix监控配置流程

    1.0 zabbix监控配置流程详细 管理角度: 开发 由开发人员提供监控指标来监控 运营 让其找开发要监控指标 运维 直接加 配置角度: 创建主机 创建主机组并加入主机 添加监控…

    数据库 2023年6月14日
    0102
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球