吐槽博主上新了。
以下是总结的在实际项目中遇到过的几种接口设计中的奇葩问题。如果读者们还遇到过哪些,欢迎在底部留言。
1、接口入参类型不清晰,如Map/ JSON String作为入参(沟通问题)
在某个项目组刚转到微服务架构时,延续了之前Oracle 存储过程的设计思路,后端微服务的接口的入参都设计成了 Map。笔者拿到接口清单之后都蒙圈了,问了几个开发都不清楚具体怎么调用,最后找到了这个团队的前端开发人员,对方终于用几个前端调用的样例让笔者顺利地把接口调起来了。
近些年,随着JSON格式地广泛使用,一个类似的问题又出现了。虽然某个接口定义为String类型,但其实是某个对象序列化的结果。服务端拿到接口入参后,controller中会首先做反序列化再交给后续的Service方法进行处理。这种方式的问题和Map其实是类似的。
2、接口出参类型不清晰,如Response<T>、Response<Object> (沟通问题)
在不少公司中,为了便于沟通、治理,统一将接口返回定义为Response, 实际返回数据保存在 T data当中。如果接口平台只是根据语义将接口出参类型定义为Response, 用户无法得知正确的实际类型。因此,需要做一些额外的代码解析或者运行时切面等方式来获取真正的data类型。
// 通用响应类(T为泛型,未明确具体类型)
class Response<T> {
private int code;
private String message;
private T data;
// 实际数据存储在data中,但类型不确定
// 省略getter/setter
}
3、返回类型为void的C/U/D类接口 (可测试性)
在调用Update/Delete类的接口后,如果服务端没有返回任何报错信息就默认为提交成功。这在业务上好像没啥问题,又不是不能用对吧。但是更为过分的是Create类的接口的返回类型也被定义为void。
这场景就像孩子出生之后到警察局报户口,材料递上去之后,警察局会给孩子分配一个身份证号,(并在你递上去的户口本上新增一页,打上这个新分配的身份证号)。如果警察只是告诉你说办完了,可以走了。原先的户口本原样退回,你会有什么样的感受?
类似的,没有ID之类的数据返回给客户端或者调用方,后续的更新、查询、删除等操作就没法做了。最绝的是笔者曾经因为此类接口开发拒绝修改,只能先查询得到ListA,再操作新增,然后再查询得到ListB, 最后ListB-listA作为实际运行结果进行断言。
当然也有CQRS架构下,反而推荐C/U/D类的接口定义为void。但是仔细去看一下这个架构下的接口设计,ID都是通过ID服务(发牌机)提前申请好,然后在C类接口入参中直接指定的,也就是调用方其实是持有ID的。不能只看外表,得看实质。
4、查询类接口返回数据顺序不固定(可测试性问题)
某些查询类的接口,虽然粗看一下返回了相同的内容,但是不同环境下返回数据的相对顺序会有差异。虽然业务上可能没有影响(前端再排序),但是这些数据如果用来断言的话,就折腾测试人员了。通常要对这些数据找一个排序方式进行排序后再进行断言。
产生此类问题的原因通常是程序员直接用HashMap接了数据库查询时返回的数据,未经排序直接返回,而每个服务器的hash结果可能有差异。
5、接口实现中存在循环外部调用(性能问题)
笔者曾经通过代码阅读,发现过在for循环中逐一对数据进行检查并插库等(骚)操作。并在《微服务治理之三维度检测措施》中提到了由此建设微服务治理平台来发现和整治此类问题。 因为此类问题是造成慢接口的一种典型原因。
最近在利用ServiceMock迁移接口自动化用例到代码库时,又意外发现了一个遗留系统中存在的类似问题。在一个表单的提交过程中,服务端接口在整个处理过程中,竟然存在3个服务间调用各出现了2次,而且它们的入参和返回结果都是相同的。类似于在学生选课系统中,在处理选课时,从学生服务中两次拉取了所有学生清单,以判断该学生是否有效在册以及所在年级、班级等数据。
6、缺少必要的参数校验,过于依赖数据库进行错误处理(性能问题)
一些接口未对入参进行严格校验,直接将用户输入传递给下游逻辑处理。例如,在用户注册接口中,未对手机号、邮箱等字段进行格式验证,直接往数据中插入。如果遇到字段超长,就直接抛出数据库异常。反正是把错误数据挡住了,"又不是不能用+1"。 笔者也只好用性能问题来规劝对方。经过测试,通过数据库抛出异常,这次的接口调用耗时就从顺利插入时的几十毫秒飙升到了几百毫秒,接口调用耗时产生了明显的毛刺。如果提前做一下校验,就是几个微秒的时间而已。
7、接口职责模糊,功能过度聚合(沟通问题) 部分接口设计为 "大而全" 的模式,将多个关联性不强的功能集中在同一接口中。例如,某订单管理接口同时承担创建订单、修改订单状态、查询订单详情三个功能,通过传入不同的操作标识来区分。这种设计不仅增加了接口的调用复杂度,也违背了单一职责原则。当接口出现异常时,难以快速定位具体功能模块的问题,且不同功能间的参数耦合可能导致相互影响,降低接口的可维护性和扩展性。早先看到的一个案例是某个工作流调用接口,工作流服务提供方直接将最底层的通用接口暴露出来,所有工作流公用同一个接口进行调用。而每个工作流的创建/状态跳转所需的数据各不相同。使用者只能自行维护各自使用的工作流的文档,以便于能正确调用。
“1又不是不能用、
2都已经上线了、
3线上又没有业务问题,不会为了测试改代码”
这是笔者最经常听到的当提出这些问题之后的反馈。
你看到过哪些问题?又得到过什么样的反馈呢? 欢迎留言讨论。