封装复杂度之批量接口

简介: 在平时项目开发过程中,难免需要作为接口提供方封装批量接口给上游调用;或者作为上游系统调用下游业务或者中间件的批量接口,执行某些操作。常见的批量操作有很多,比如批量查询内容详情,批量发送提醒;批量插入数据、批量更新、批量发送MQ消息等。不知道,大家想过没有。- 为什么要提供批量接口?- 作为批量接口的提供方和批量接口的使用方我们通常需要注意哪些问题?

一、背景

在平时项目开发过程中,难免需要作为接口提供方封装批量接口给上游调用;或者作为上游系统调用下游业务或者中间件的批量接口,执行某些操作。
在这里插入图片描述

常见的批量操作有很多,比如批量查询内容详情,批量发送提醒;批量插入数据、批量更新、批量发送MQ消息等。

不知道,大家想过没有。

  • 为什么要提供批量接口?
  • 作为批量接口的提供方和批量接口的使用方我们通常需要注意哪些问题?

二、 问题思考

2.1 为什么要提供批量接口?

通常最主要的一个原因是为了性能优化。

通常 IO 操作是性能的主要瓶颈,批量接口可以减少网络 IO 次数,从而达到降低耗时的目的。

2.2 批量接口的提供方我们要注意哪些问题?

【1】健壮性

很多人,尤其是新手,容易直线思考,批量接口嘛,直接传给我一个 List 作为参数,返回结果即可。

(1) 批量限制

如果上游传入集合中元素多,会不会有问题?

上游传入的元素过多,很容易对本系统造成很多压力,而且非常容易超时。

public List<Order> queryOrders(String userId, List<String> orderIds){
  // 省略
}

因此,批量接口通常需要增加分页参数,通常需要对集合长度进行检查。

public List<Order> queryOrders(String userId, List<String> orderIds, PageRequest page){

  // orderIds size 检查
}

FBI Warning:请在函数的注释中或者接口文档中必须显式标注集合长度限制!

(2)参数校验

上游传入的参数合法性也要进行校验,比如例子中 userId 是否有权限查看这些 order ?

public List<Order> queryOrders(String userId, List<String> orderIds, PageRequest page){
  // userId 合法性校验
  // orderIds size 检查
}

再比如传入日期,日期的格式是否正确,是否符合预期? 都是需要考量的事情。

(3)并发校验
有些批量操作不允许并发,要考虑加分布式锁。

(4)失败处理
失败该如何处理,也是一个需要考虑的问题

将失败的对象当做返回值返回给上游? 将失败的部分忽略掉?中间有数据失败,需要回滚?

【2】可拓展性

通常建议将主要参数甚至返回值定义成自定义对象,而不是使用封装类型在函数签名中铺开。

请看下面的案例,如果后续需要新增一些参数,就需要提供新的接口:

public List<Order> queryOrders(String userId, List<String> orderIds, PageRequest page){
  // 省略
}

可以参考以下写法,将参数定义为批量查询对象:

public List<Order> queryOrders(OrderBatchQuery query, PageRequest page){
  // 省略
}

这样如果需要新增参数时,不需要修改函数签名。

对于一些“写”操作,还可以考虑,提供失败处理策略,如失败抛异常、部分失败返回失败列表等。

【3】封装复杂度

通常提供批量接口的同学会理直气壮的认为,设置集合 size 限制,最多再给个 page 参数就可以了。

如果有批量的需求,自己去对集合进行分批,自己对分页进行处理呗!

其实最大的问题是,几乎所有上游都需要对当前自己拿到的整个 list 的所有内容都要进行查询!!每个使用方都要自己处理分批和分页,非常麻烦,气得直跺脚!!!

其实有时候可以多走一步,既能体现出自己的专业度,也能更容易赢得上游的信任和称赞。

可以考虑提供一个自动分批和处理分页的方法(需告知上游虽然可以自动分批,但是如果 size 过大仍然会有因数据量过大导致调用超时,甚至 OOM 的风险)。

还可以提供一个自动对参数接口进行分批执行调用拼接结果的工具类等。

对于带返回值的调用,可以参考下面工具方法的定义:

public static <T, V> List<V> partitionCall2ListAsync(List<T> dataList,
                                                         int size,
                                                         ExecutorService executorService,
                                                     Function<List<T>, List<V>> function) {
  
}
                                                         
                                                     

其中 dataList 即待分批的集合, size 即每一批的数量, executorService 线程池,Function<List, List> function 即单次调用。

可参考为的另外一篇博文:https://blog.csdn.net/w605283073/article/details/101399427

2.3 批量接口的使用方需要主要哪些问题?

【1】长度限制

不管是业务接口还是中间件的批量接口,通常参数中集合都会有 size 限制,一定仔细看函数说明、接口文档,甚至有条件拉下对方源码看看实现方式。

工作这几年,已经见到过身边同时多次因为使用下游提供的批量接口,而下游没有在接口上写 size 限制,导致上游在数据量大时报错,测试阶段通常数据量较小不容易发现该问题。

如果下游没有提供自动分批的批量调用方法,可以自己在本系统的外部依赖模块通过编写一个 XXX对应的 XXXXClient 进行二次封装,避免将复杂度再向上游暴露。

【2】 部分失败如何处理?

要核实下游对部分失败的情况的处理办法,是提供了失败策略,还是一起回滚,我们直接失败或者重试?

三、启发

本文希望通过批量接口的编写和使用,让大家意识到封装复杂性的必要性。

希望大家在设计方案或编写代码时,一定要带着“封装复杂度”的思想,尽量将复杂度封装在更底层的位置。

这也是“迪米特法则”即“最小知道原则”的要求,也是高内聚、低耦合的要求。

创作不易,如果你觉得本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励是我创作的最大动力。
相关文章
|
Java Spring
动态控制 Spring Boot 中的 @Scheduled 定时任务
Spring Boot 中的 @Scheduled 注解为定时任务提供了一种很简单的实现,只需要在注解中加上一些属性,例如 fixedRate、fixedDelay、cron(最常用)等等,并且在启动类上面加上 @EnableScheduling 注解,就可以启动一个定时任务了。 但是在某些情况下,并没有这么简单,例如项目部署上线之后,我们可能会修改定时任务的执行时间,并且停止、重启定时任务等,因为定时任务是直接写死在程序中的,修改起来不是非常的方便。所以,简单记录一下自己的一些解决方案,仅供参考。
3036 0
|
SQL Java 关系型数据库
JDBC Connection详解:连接到数据库的关键
在Java中,要与数据库进行交互,需要使用Java数据库连接(JDBC)。JDBC允许您连接到不同类型的数据库,并执行SQL查询、插入、更新和删除操作。在JDBC中,连接数据库是一个重要的步骤,而Connection对象是实现这一目标的关键。本篇博客将详细解释Connection对象的作用、创建和使用方法,以及与数据库连接相关的注意事项。
945 0
QT资源文件-QFile Write/WriteOnly/ReadWrite打开失败说明
在使用QT进行开发过程中,常常会使用qrc(资源文件),比如一些配置文件,图片,图标(icon)等,但是一些没有经验的小伙伴会想着将配置文件的数据进行修改,发现无法以代码的方式对资源文件进行修改。
965 0
|
测试技术 Apache Linux
怎么做服务器压力测试?
项目做好了,能不能顺利上线? 一般来说我们需要做一下压力测试来判断。比如该项目上线后预计每天一百万的接口访问量,并且访问时段主要集中在早八点到晚八点,那么平均下来 RPS 大约是 22 次左右·····
14642 0
|
6月前
|
Java 测试技术 数据库
Java 全链路测试体系:单元测试、集成测试与 TDD 从原理到落地
本文系统讲解Java测试体系,涵盖测试金字塔模型、单元测试(FIRST原则、JUnit5/Mockito/AssertJ实战)、集成测试(TestContainers真实环境)、TDD红-绿-重构实践,以及覆盖率认知、CI/CD集成等落地准则,助力开发者构建高质可维护代码。
710 1
|
Shell 开发工具 git
【Git】解决Untracked Files Prevent Checkout的问题
【Git】解决Untracked Files Prevent Checkout的问题
5414 0
|
前端开发 API UED
React 按需加载 Lazy Loading
随着 Web 应用复杂度增加,页面加载速度成为影响用户体验的关键因素。React 提供了按需加载(Lazy Loading)功能,通过 `React.lazy` 和 `Suspense` 实现动态加载组件,减少初始加载时间,提升性能。本文从基础概念入手,探讨常见问题、易错点及解决方案,并通过代码示例详细说明。
747 1
|
移动开发 前端开发 UED
React 音频进度条组件 Audio Progress Bar
在现代Web开发中,音频播放功能不可或缺。使用React构建音频进度条组件,不仅能实现播放控制和拖动跳转,还能确保代码的可维护性和复用性。本文介绍了如何利用HTML5 `&lt;audio&gt;`标签的基础功能、解决获取音频时长和当前时间的问题、动态更新进度条样式,并避免常见错误如忘记移除事件监听器和忽略跨浏览器兼容性。通过这些方法,开发者可以打造高质量的音频播放器,提升用户体验。
732 6
|
存储 NoSQL 关系型数据库
微服务Zipkin链路追踪原理,图解版,一文吃透!
本文重点讲解Zipkin链路追踪的原理与使用,帮助解决微服务架构下的服务响应延迟等问题,提升系统性能与稳定性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
微服务Zipkin链路追踪原理,图解版,一文吃透!
|
消息中间件 SQL 关系型数据库
分布式事务-seata
分布式事务-seata
1292 0

热门文章

最新文章