自己的模块,常常要对接上下游,如何在对接的过程中,保证正确性呢?
一般和上下游对接的有两种方式:

一:接口对接

在接口对接中,需要注意下面的点。
1,首先和上下游约定接口的功能是什么,这往往和承载的业务有关系,需要提前在产品宣讲阶段+产品评审阶段定义清楚,每个接口预期的功能都应该准确的描述在接口注释上。

2,约定好接口的功能后,紧接着就是约定好接口的入参和返回值数据结构,确定参数数据结构中哪些是必传的,哪些是不必传的,他们的传值范围是什么?
确定返回值的数据结构是什么。什么时候失败,什么时候成功等等,都需要定义清楚。这里比较推荐常用的yapi管理接口。
另外注意字典维护,比如sex中的"未知" "男" "女",是统一使用中文表示,还是0 1 2编码表示,还是英文表示。如此这类的,都要标准化。另外字段名称也尽量统一。
对于接口返回个人建议使用下面这种通用的数据结构

public class Message<T> {
 private boolean success
 private String errcode
 private String errmsg
 private T data

public Message(boolean success,String errcode,String errmsg,T data){
this.success=success;
this.errcode=errcode;
this.errmsg=errmsg;
this.data=data;
}

// 返回成功
 public Message success(T  data){
  return new Message(true,"0","",data);
 }

// 返回失败
 public Message fail(String msg){
   return new Message(false,"400","param is illegal",null);
 }
}

对于返回成功还是失败,这里有一些案例值得思考

pubilc User findOneUserById(Long userId){
// 这个userId应该是之前系统生成的,那么根据这个userId,在数据中应该一定能查到数据,但如果查不到呢?此时应该返会失败。
}

pubilc User findOneUserByName(String selectName){
//根据selectName去搜索的时候,在数据库中可能查不到,这种情况是正常的,因此要返回成功。提示没有数据。
}

还有比如根据用户手机号作为唯一标识,插入用户信息,如果第一个请求已经插入成功,当第二个请求携带相同的参数插入时,接口是返回成功,还是返回失败?这在上下游对接的过程中比较重要

3,接口处理
3.1在接口impl层面的第一件事情,先打印请求参数日志。在接口实现的关键点也需要打印日志。这样方便后续定位问题边界。

3.2紧接着第二件事就是按照约定的数据结构进行参数校验,对错误的参数请求做好拦截,防止他们捣乱系统,破坏数据。这一点可能很多人忽略,但其实是最重要的

3.3对于非必传参数,接口应该如何处理,也需要明确,这一点一般反应在该接口在不同的场景中使用。

3.4 处理异常,不要使用抛异常的方式退出某个方法,并毫无保留的把异常信息传递给上下游,除非方法有捕获异常,并且知道怎么处理。

3.5 明确返回值和错误信息:如果处理成功返回success=true。如果失败,不能仅仅返回失败就完事了,必须在返回信息明确的告知上下游是什么原因导致这次请求处理失败,是参数校验不通过,缺省哪些值?还是要操作的数据,在数据库中不存在?等等。

3.6 接口开发完毕,对接口进行单元测试,我个人认为这是很重要的一步:根据步骤1的注释,必须按照这些预期功能进行测试,如果预期提供的功能,在单元测试中成功通过,那么说明该接口提供的功能基本上是可靠的,可以和上下游对接了。但这还不够,单元测试的参数最好分各种各样的情况,如果你模拟的参数情况比真实用户发出的参数组合多,并且返回都按照预期响应,那么该接口可以说是合格的了。这样,你就可以拍着胸脯说没有bug了,可以去开发另外的接口了。

一般来说,接口对接做好上面就足够了,但是还不够 !接口设计的时候还必须考虑以下事情:

a,幂等:也就是说相同的请求来了(可能是多线程并发请求,也可能是重复请求),该接口如何实现这一点,这一般反应在增删改等接口。
接口该怎样支持幂等?一种方式是利用数据库的唯一索引来。还有利用分布式锁(为什么不用jvm锁?)

b,分布式事务:比如该接口是一个扣减库存的接口,那么在和上下游对接的过程中,需要注意如何处理分布式事务。谁负责解决分布式事务?
在众多的项目经验,我得出一个结论,分布式事务,一定是利用最终一致性+ack的思想解决,两阶段,三阶段不适合大规模的系统

c,并发:这可能是最容易忽略的,很多神奇的问题,都是由于这个而找不到原因,跟个鬼一样。特别是在soa和微服务架构,往往会利用k8s部署多个pod情况下。所以对于提供的写操作接口,一定要研究有没有并发问题。如果有,往往是利用分布式锁解决(除非只运行一个pod)

d,性能:有些业务处理可能比较复杂,一次响应需要太久的时间,这对使用者来说,都是一次不好的体验,在上下游对接中,我们提供的接口可能仅仅是整个业务中的一环,因此需要注意接口的性能。
有书籍说"如果能够正常运行,就不要进行性能优化,否则会带来灾难",这句话不全对。
首先需要分析是慢在哪里了。其中我觉得需要快速得出结论,是否因为数据组织,系统架构不合理而导致查询本身就很慢,如果是,那么就需要快速的商讨对策了。为什么这一点很重要?如果在接口上线一段时间才发现这个问题,再去调整数据,调整系统架构那往往是灾难。
如果不是这些问题,那么可以建一些todo,或许是加缓存,或许是算法优化等等

e,请求量限制:
有的时候,可能时间很紧迫,上下游随便扔过来一个查询需求,如果没有时间做好需求调研,那么功能实现后,一定要加上限流的策略。这些组件有很多比入sentinel。一定要保护好自己的系统。

f,模块:
相同功能的接口,最好放在统一的模块中进行管理

二:消息对接

利用mq等中间件消息
消息对接和接口对接大部分都是相同的。

不同之处在于,消息处理完毕不会立刻返回上下游,此次消息的处理结果是什么。因此当前系统,必须对处理失败的消息,做好响应。

1,首先处理消息有个小技巧,接消息的类,不要进行进行任何业务逻辑处理,仅仅打印消息日志就好了。应该有单独的一个类的方法,来实现消息的处理。这样做的好处,在于把消息也看成一次普通的接口请求,这样也方便后续做单元测试。所以设计消息的处理,大部分可以参照接口的设计。

2,对重要的消息,一定做好记录,后续可查,中间件上的消息一般保存7天,如果某天被告知前7天的某条数据处理有问题,如果没有记录,就不得而知了。如果找不到原因,用户可不认这一点。

3,消息处理做好幂等。

4,处理失败的消息,记录好失败的原因,根据业务判断是否需要进行重试。

5,做好单元测试

如果做到以上,消息对接不会有太大的问题,但是还需要考虑以下事情:
a,消息是否有序:消息是否需要按照顺序来处理,如果和发送没有约定好这一点,可能会出现莫名奇妙的bug。可以利用kafka的分区有序性来实现

b,消息量大小:消息的数据级是什么?如果数据量太大,是按照pod扩容,还是说把相关代码单独维护出去,组成一个集群

c,延迟:一般来说,消息处理都是异步处理,所以它存在一定的并发问题,可能在消息处理的过程中,需要的数据还没有完成。那么该消息可能要进行延迟处理,而不是直接丢掉。

最后,记录点非技术的问题:
1,在和上下游对接中,要和同事搞好关系,在生产环境遇到问题不要甩锅,要及时帮忙排查问题出现的原因和提供技术解决方案。

2,以自己负责的业务为中心,梳理好所有相关的功能,这样当出现问题时,才能第一时间察觉到是哪个环节出现问题了。


Martin
5 声望0 粉丝

后端