(十二)漫谈分布式之接口设计上篇:写出一个优秀的接口我们需要考虑什么?

2,689 阅读26分钟

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

引言

作为一名后端开发,每天不是在写接口、就是在写接口的路上,那接口要怎么写?有人说你这不废话吗,定义好入参/出参结构、实现好业务需求不就得了。

诚然,事实也的确如此,但这只是写好一个接口的基本要求,但是想要写出一个优秀的接口设计远不止于此。它需要我们具备前瞻性思维,全面考量安全性、性能优化、可扩展性、易用性乃至未来维护的便捷性。每一次深思熟虑,都是向接口完美形态迈进的坚实步伐,真正的高手,更致力于在接口的每一个细节上做到尽善尽美!

PS:后续内容会糅合不同类型的接口来一起讲述,包括但不限于读取、写入、开放API、分布式接口等。

那么,在确保业务需求得到充分满足的基础上,如何进一步雕琢我们的接口呢?这正是接下来探讨的核心议题。鉴于话题的广度与深度,我们将采取循序渐进的方式:首先,聚焦于所有接口编写时共通的考量因素,为后续深入探讨读写接口特有的关注点奠定坚实基础,下面开始一场关于接口艺术深度探索旅程!

PS:个人编写的《技术人求职指南》小册已完结,其中从技术总结开始,到制定期望、技术突击、简历优化、面试准备、面试技巧、谈薪技巧、面试复盘、选Offer方法、新人入职、进阶提升、职业规划、技术管理、涨薪跳槽、仲裁赔偿、副业兼职……,为大家打造了一套“从求职到跳槽”的一条龙服务,同时也为诸位准备了七折优惠码:3DoleNaE,近期需要找工作的小伙伴可以点击:s.juejin.cn/ds/USoa2R3/了解详情!

一、完善注释与接口文档

编程里有句名言:

我写代码最讨厌两件事,一是别人的代码不写注释,二是别人叫我写代码的时候写注释!

这句话非常形象,在匆忙的工期中,能尽量写好代码满足业务就已经是极限了,结果你还让我写注释,我哪有时间啊!

所以,许多人写代码并不喜欢写注释,但这种情况就引发了一个问题,当代码需要交接给其他人时,满屏的英文夹杂着各种业务逻辑,谁来看都是一看一个不吱声。我记得以前项目有个接口,业务逻辑全都在写在一个方法里,并且一行注释都没有,结果那段代码辗转了好几人之手,全都看的一脸懵逼。

有人说这不是好事嘛?防御式编程,增强了自己的不可替代性,我写的代码除了我之外,任何人来了都不行

确实,这的确是个增强不可替代性的好办法,但尴尬的是:某一天需要改个小细节时,自己回来看自己的代码,从头读到尾也看不明白了……。没错,这也是我的亲身经历,有次我改一段自己的代码,自己都没看明白当初为啥要这么写。

和写注释类似的,还有写接口文档,尤其是开放API的文档一定要认真写!不然最后苦的还是自己,因为联调时,调用方会跟个麻雀一样问个不停。

吸取这样的教训后,之后无论工期多赶,代码都会适当了加一些注释,不一定要写的多细,但至少要有,至少逻辑复杂的地方得粗略描述下。写接口文档也一样,想要以后轻松,那就认真写好,不然自己挖坑还得自己填。

二、合理的执行日志

注释关乎着代码能不能读懂,而日志则关乎着问题排查,如果日志打印的不合理,当线上出现问题就只能靠猜。所以合理的日志非常重要,但不仅只是打印日志就够了,包括输出日志的位置、级别、内容都要合理

一亿多条日志

来看这张截图,这是一个跑批接口所产生的日志,起初我只是听这个接口执行缓慢,所以着手排查性能问题,结果通过traceId一搜,好家伙,调用一次接口打了一亿多条日志还不见停。回到代码一看,结果一行日志藏在循环的……循环的某个方法里面,这就是典型的位置不合理造成的问题。

有些人打日志,对于引用类型的对象,还喜欢转json输出,但对于大多数情况来说大可不必,因为每次输出日志都要序列化对象,这在高并发场景下是极其消耗资源的,非必要场景打印核心的几个字段就好,这是所谓的内容要合理。

日志级别是指info、debug、error……这些,大部分小伙伴输出日志喜欢无脑log.info(...),就算try/catch了异常还是用info,可有些系统在不同的环境下,会选择性的调整日志级别,这时就算报错也查不到日志,最终日志加了跟没加一样。

2.1、日志链路追踪

熟悉分布式、微服务技术的小伙伴,相信对链路追踪这个概念并不陌生,因为系统分散部署在不同节点上,每个节点都有各自的日志文件输出路径,这时会通过日志收集技术来将所有服务的日志收集在一起。可是当所有服务的日志糅合在一起后,不同请求之间的日志杂乱无章,会排查问题造成较大干扰,你很难将这么多节点的日志,按请求调用链路的顺序先后串联起来

链路追踪

正因如此,为了方便日常排查功能Bug、性能问题、系统故障……,必须想办法将日志串联起来,而这种技术则被称为链路追踪。所谓的链路追踪很容易理解,以调用接口举例,当客户端调用某个接口发出请求时,系统就会记为日志的开始,而请求在系统内经过的任意节点,产出的所有日志都会被串联起来,直至该请求出站为止。

对于分布式、微服务系统来说,如果想要加入链路追踪技术,市面上有非常多开源组件可选,如如Zipkin、SkyWalking、Jaeger、SpringCloud-Sleuth、PinPoint、Elastic、CAT、OpenTelemetry……,这些组件都是基于APM等技术实现,对业务代码基本都是零侵入,接入成本也很低。

可如果你在一个单体项目中,也想要有一种链路追踪技术,将一个请求产生的所有日志串联到一起怎么办?其实非常简单,你只需要在请求入站的时候,自动生成一个全局唯一的值,并塞入MDC里,然后在日志配置文件里获取输出即可,以SpringBoot项目为例:

@Component
public class LoggingInterceptor implements HandlerInterceptor {
 
    private static final String TRACE_ID = "traceId";
 
    @Override
    public boolean preHandle(HttpServletRequest request, 
            HttpServletResponse response, Object handler) {
        // 生成一个全局唯一的值作为链路ID
        String traceId = generateTraceId();
        MDC.put(TRACE_ID, traceId);
        return true;
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, 
            HttpServletResponse response, Object handler, Exception ex) {
        // 请求结束后清除MDC
        MDC.clear();
    }
 
    private String generateTraceId() {
        // 生成traceId的逻辑,这里直接使用了UUID
        return UUID.randomUUID().toString();
    }
}

这里定义了一个日志拦截器,在请求入站的预处理方法中,生成了一个traceId并塞进了slf4jMDC上下文里面(实际上是个ThreadLocal对象)。同时在请求出站的afterCompletion()方法里,自动清除了MDC防止对其他请求造成干扰。

有了这个拦截器,就只需注册一下:

@Configuration
public class WebConfig implements WebMvcConfigurer {
 
    @Autowired
    private LoggingInterceptor loggingInterceptor;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册日志拦截器
        registry.addInterceptor(loggingInterceptor);
    }
}

以及配置下日志输出格式:

logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %X{traceId} - %msg%n

加上这段配置后,%X{traceId}将会被替换为MDC中的traceId,后续则可以根据这个值来串联一个请求的所有日志。不过我的建议是最好再在接口返回结果里,加上一个traceId字段,这也方便找到对应请求的入口。

三、接口风格统一

除开较老的系统外,目前新系统的都会遵循RESTful风格来定义接口,这也编写接口时的标准,即接口路径相同、通过请求方法来区分不同的操作,如:

  • POST:对应新增操作;
  • GET:对应查询列表操作;
  • GET/{id},对应查询详情操作;
  • PUT:对应修改操作;
  • DELETE:对应删除操作。

遵循着这样的共识,前后端在联调接口时也会格外轻松。当然,这也并非强制要求,毕竟不同的场景下,接口交互方式也不一样,比如系统内部接口交互可能会使用RPC方式、对外暴露的接口只提供一个路径,不同业务操作通过code来区分等等。

3.1、统一返回结构

对于单个系统来说,不管是什么业务板块的接口,调用后返回的结构都是统一的,比如:

{
  "code": 200,
  "msg": "success",
  "success": true,
  "data": {},
  "traceId": "xxx"
}

但对于综合性的平台来说,比如统一对外的技术开放平台,又或者XXX中台,因为内部聚合了多个子系统的API,而不同子系统返回的结构又不一样,这时就会给调用方带来很大困扰。

回想我之前对接某国企的中台接口时,分明是同一个域名下的接口,结果返回的格式五花八门,有的响应码字段叫code、有的叫returnCode,成功状态有0、200、SUCCESS……,到最后不得不针对每个接口单独写一套校验逻辑,这无疑十分令人痛苦。

所以,如果你在负责技术平台建设时,就算聚合的接口来自于不同子系统,那也要保证对外返回的结构体一致,怎么实现呢?很简单,内部通过API网关,统一转换不同系统的出参结构即可。当然,也别忘了把错误码转换成同一套体系的错误码~

四、跨域问题

跨域问题的产生背景是浏览器的同源策略(Same-Origin Policy),同源策略是浏览器的一种安全机制,它限制了从同一个源加载的文档或脚本,如何与来自不同源的资源进行交互。这里的源(origin)是指协议、域名、端口号组成的唯一标识,如果出现两个地址,这三者组合起来对比不一致,就代表着不同源。

如何解决不同源的跨域问题呢?也很简单,在SpringBoot里通过一个@CrossOrigin注解就能搞定,如果嫌挨个Controller加注解麻烦,也可以通过实现WebMvcConfigurer接口,并重写addCorsMappings()方法来全局配置跨域,即:

@Configuration  
public class CorsConfig implements WebMvcConfigurer {  
  
    @Override  
    public void addCorsMappings(CorsRegistry registry) {  
        registry
        // 代表允许所有路径跨域
        .addMapping("/**")
                // 允许来自http://xxx.com的请求跨域  
                .allowedOrigins("http://xxx.com")
                // 允许这些HTTP方法跨域
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                // 允许任何头跨域
                .allowedHeaders("*")
                // 允许发送Cookie
                .allowCredentials(true)
                // 预检请求的缓存时间(秒)
                .maxAge(3600);
    }  
}

当然,解决跨域问题的本质,是往响应头里塞几个字段,即:

response.setHeader("Access-Control-Allow-Origin", "*");  
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");  
response.setHeader("Access-Control-Max-Age", "3600");  
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization"); 

因此,只要能够改写响应头的地方都可以解决跨域问题,所以市面上才会出现那么多解决跨域的方式:

  • 使用@CrossOrigin注解解决跨域问题;
  • 实现WebMvcConfigurer接口解决跨域问题;
  • 通过自定义Filter过滤器解决跨域问题;
  • 通过Response对象实现跨域问题;
  • 通过实现ResponseBodyAdvice接口解决跨域问题;
  • 通过设置Nginx配置解决跨域问题;
  • ……

这么多种跨域解决方案听起来很唬人,但只要是服务端解决的跨域问题,殊途同归都是改写响应头罢了。

五、安全性设计

一个优秀的接口,自然得考虑安全性方面的设计,毕竟除开少数部署在内网的系统外,大多数接口都能通过公网访问,而每天都有无数程序在扫描公网资源,如果你的接口没有做好安全性保障,这将称为“不法分子”的绝佳攻击口。

安全性设计是个沉重且庞大的话题,细分下来有许多可以聊的内容,但这里就先聊些常规、通用的设计,如身份鉴权、接口验签、数据加密、黑白名单等。

5.1、身份鉴权机制

对于接口安全方面,出现时间最早的方案就是身份鉴权机制,在现在的系统中也被称为权限管理。

如果接口没有任何权限控制,我完全可以绕开前端界面,通过抓包工具抓住接口地址后,直接对系统接口发起调用,从而实现绕开表面的限制,举个例子:

现在有一个付费才能看的资源,如果只是前端做了校验,那完全可以直接调用后端接口来绕开付费校验。

接口权限管理,即是指具备一定权限才能调用的接口,如果调用者本身不具备对应权限,系统则会拒绝访问,如何设计呢?权限的载体是具体的用户,最简单的例子,就是已登录/未登录,某些接口必须要登录后才能访问,这就是一种权限具象化的表现。

因此,身份鉴权机制实际上只需处理好系统用户和权限的关系,这也有开源的成熟框架,如Shiro、Spring Security等,为了便于管理所有用户的权限,通常都会加入“角色”的概念,用户与角色关联、角色与权限关联,从而形成一整套身份体系。

有了身份体系后,就可以基于该体系来实现接口粒度的权限控制,比如后台业务对应的接口,只有具备“运营”角色以上的用户才能访问。当然,身份鉴权的前提是用户已经登录,如果每调用一个接口就要登录一次,这会验证影响用户体验,所以又衍生出了Token令牌的概念,即用户登陆后给其颁发一个临时通行证,在这个通行证有效期间内可以无需登录

PS:这里不做具体展开了,毕竟这是每个系统都具备的基础功能,大家也都接触过。

5.2、接口签名机制

签名的身份鉴权机制,尽管能够在一定程度上避免非法请求,但也提到了,咱们会给登陆用户颁发Token令牌,而这个令牌是有一定时效期的。所以,非法请求也完全可以伪装用户登录,然后拿着对应的Token调用接口,Token都有了调用接口不是很合理吗?

正常情况下是合理的,可是非正常情况呢?比如订单提交接口,正常流程是用户登录、系统颁发Token、用户提交订单,这没有任何问题,可如果我登录拿到Token后,本来正常提交订单需要支付888.88元,我抓到请求信息后,直接将金额改写成0.01,岂不是就乱套了?

所以,为了防止参数被篡改,就出现了一种名为签名的安全机制,签名机制的核心是双方约定一个密钥,接口调用方会先对进行参数排序,然后结合时间戳、密钥,生成一个sign值,并将时间戳、sign值一起放在请求参数中携带。

后端收到请求后不会直接处理,而是会先取出sign、时间戳,再以相同的规则对参数进行排序,接着也以相同规则生成一个sign值,再与参数的sign值进行对比,如果一致说明参数未被篡改,这时再进行具体的业务处理

将签名机制套入签名的例子中,这时就算我抓住了提交订单请求,并将支付金额改为0.01,由于参数值发生了变化,服务端生成的sign值,自然与请求参数里的sign值对不上,这时系统就会忽略这些被篡改的请求。

签名机制绝对安全吗?如果是两个后端系统交互,可以说是安全的,毕竟后端系统只会在内部交互,并不会将约定好的密钥暴露到公网,没有密钥自然无法破解sign值。但如果是前端调用后端接口,因为前端也需要生成sign值,这时就有安全风险,有句话叫做:前端任何加密、安全设计都是不可靠的,毕竟前端会直接暴露给用户,手段足够强自然也能拿到密钥生成sign

5.3、出入数据加密

好了,签名机制能在一定程度上防止篡改和请求重放,但我偏偏就不改你的数据,我只看看总行吧?当然可以。

在有些业务场景中,难免会需要用户的一些隐私数据,如手机号、身份证号等,建立在前面的基础上,因为数据是以明文形式传输,完全可以通过伪站、劫持等手段,拦截所有通向后端域名的请求,从而低成本窃取用户数据。

为了避免数据在传输过程中泄漏隐私数据,这时就会对请求参数进行加密或混淆,比如常见的BASE64也能被称为一种混淆手段。但真正的加密传输过程中,会选择专业的加密算法,如:

  • 哈希散列加密算法:MD5、SHA1、SHA256等。
  • 对称加密算法:AES-CBC、AES-GCM、DES、3DES等。
  • 非对称加密算法:RSA、DSA、ECC、DH等。

一般与前端的数据交互过程,通常会选非对称加密算法,特点为:公钥加密的数据,只有用对应的私钥才能解密;如果用私钥加密的数据,那只有用对应的公钥才能解密

非对称加密

这时会将公钥给到前端使用,就算公钥泄漏了也不影响,毕竟公钥加密的数据公钥解不开,第三者拿到了公钥和加密报文,也无法逆向解析出真正的数据。

这里具体不做展开,感兴趣可以看看之前《HTTP/HTTPS原理-SSL加密层实现》,这里面有这方面的知识分享。

好了,聊完数据传输加密后,再来看看响应参数加密,为啥要加密呢?答案是防爬虫,不过因为数据会给到前端使用,使用非对称加密不太合适,私钥存在前端,第三者完全可以拿到去解析。

正因如此,出参加密通常会使用数据混淆的手段,这种手段称之为蜜獾机制,大家感兴趣可以去看看携程、同程、12306……这些网站的接口数据,比如一家酒店的价格在页面显示“888.88元/晚”,而接口返回的数据完全不是这么回事,这就是用到了混淆机制来反制爬虫程序。

比如X对应1Y对应8DAS对应88Z对应.YDASZDAS这个值就对应着888.88

5.4、黑白名单机制

黑白名单是安全性领域的元老级方案,比如你的数据库为了防止被人攻击,但又要暴露在公网来本地连接调试,这该怎么办?答案就是使用IP白名单,除了内网环境外,公网上只允许名单内的IP来访问。黑名单则完全相反,只要在黑名单内的都不允许访问。

换到接口设计领域,白名单的应用范围通常是API开放平台,比如有个第三方需要接入你们平台,但你又担心安全性,这就可以让对方提供固定IP,你将其配置在白名单内,此时就只有指定IP可访问啦。

不过黑白名单只是一种思想,你可以根据实际情况来选择实现的粒度:

  • IP级别:最常见的粒度,配置后只对指定IP的机器生效;
  • 用户级别:配置后只对固定用户生效(证书、appSecret就是这个维度);
  • 地域级别:IP名单变种,可以基于IP来实现地域过滤,如上海可访问;
  • ……

白名单用于限制特定的群体访问,而黑名单则完全相反,在接口领域的运用,更多还是防爬虫,比如检测到sign被篡改的请求时,直接将发起请求的IP加入黑名单。又或者是请求频率过快、登录设备异常等各种不合理的情况,都可以将其加入黑名单禁止访问。

六、资源隔离

资源隔离这个话题在之前百万级报表导出篇提到过,而在设计接口时同样要注意,和社会上的财富一样,接口与接口之间并不是平等的,业务之间存在优先级关系,来看两个例子。

案例一:之前我封装了一个线程池,用来并行查询数据,从而提升接口性能、缩短响应时间,结果另一位同事也有个异步需求需要用到多线程,看到有个自定义的线程池后,直接往里面塞长IO型任务,平均每个任务就得跑五六分钟,最终导致另外一边使用多线程并行查数据的接口,反而比单线程的方式更慢了。

现在有一个发送短信的功能,在许多场景下都会用到,比如下单成功、订单发货、订单退款、用户注册……
结果小竹认为同步调用发送接口太慢了,而且发短信并非核心流程,完全可以使用异步处理,所以,就将调用发短信接口的动作,全交了给线程池去异步执行。
而有一天订单量较大,这时出现一个用户来注册,需要填写手机验证码,可当系统生成验证码后,调用发短信接口。在此之前线程池里已经堆积了十万八千个任务等待处理。
最终的结果就是,直到几分钟后,才轮到这个发短信验证码的任务,可这时早已超出注册的验证码有效期了……

上述两个例子,都是没有做资源隔离导致的问题,所以写接口时,一定要考虑要不要做资源隔离!并且不仅要考虑线程池资源,也包括各种连接池等有限的珍贵资源。再以数据库连接池为例,如果某项业务执行时间非常久,该业务就会持续蚕食公共的连接池资源,直至其余业务无法获取到可用连接,引发系统瘫痪。

那么啥叫资源隔离呢?比如前面发短信的场景,用户注册的短信验证码,显然并订单通知的优先级要高很多,所以应该将这些场景区分开来,定义高优先级、低优先级的线程池,分别用来处理不同类型的异步任务。

七、版本兼容性

对于常规系统来说,如果只是系统本身的前后端交互,那么接口改造没有任何问题,只需改完后跟前端说一声,前端配合着一起改造就好啦。但是针对于开发平台、中台这类系统来说,因为它们被不知道多少三方接入了,如果接口逻辑要优化改造,贸然在原接口上变更,就会导致外部接入方发生不可预知的错误。

正因如此,如果你的接口被其他服务、三方所依赖,任何改造都要保证向前兼容,而为了实现版本兼容性,通常会有两个做法:

  • ①每次改造都用if来区分不同版本的参数,确保每次改造不会影响已有的接入方;
  • ②每次改造都独立出新的接口,通过在接口路径上添加版本号来区分,从而实现多版本并行。

究竟选择哪种方式呢?这得看原本的代码设计,如果提前考虑到了后续拓展性,那么在原接口上改造是最好的。否则每次最好独立出新接口,从而确保版本向前兼容。

八、支持动态配置

在分布式项目中,有一句话叫做“约定大于配置,配置大于编码”,后半句的含义是:尽可能的通过配置来代替硬编码,啥意思呢?比如超时熔断时间,最开始是1000ms,直接以硬编码形式写死在代码里,一旦需要延长时,就只能修改代码重新发版了。

而这种可能存在变化的信息项,就可以通过配置形式注入,支持运行期间动态改变配置,来举些例子:

  • 超时熔断时间:可以根据线上的实际运行情况,动态调整调用外部接口的熔断时间;
  • 线程池参数:可以根据业务的需求,动态调整最大线程池,来创建更多的线程处理任务;
  • 消费者数量:面对消息堆积场景时,可以动态调整消费者数量来增大消费能力;
  • 灰度开关与阈值:新老功能替换时,可以动态调整灰度开关及切流比例,实现无感发布;
  • 重试次数:依赖外部资源时,可以动态调整失败的重试次数,确保满足特殊场景的需求;
  • 锁的时间:运行期间允许动态调整分布式锁的等待时间与过期时间,满足突然状况;
  • ……

总之,如果某个配置项可能会对功能造成不同的影响,你就可以将其做成动态配置的形式。并且如今实现动态配置并不难,Nacos、Apollo等配置中心,都能实现运行期间的动态配置~

九、面向失败设计

这一条是针对分布式接口来写的,在实际项目中,我们总会依赖一些外部资源,这个外部可能是其他服务,也有可能是其他系统。当你的接口依赖于这些外部资源时,一定要面向失败去设计,因为所有外部资源都是不可靠的

比如你需要在订单状态变更时,给CRM、OMS之类的外部系统推送数据,直接以同步形式调用它们的接口,这时一旦出错就有可能造成整个订单回滚,但这时你本身的业务流程没有出错呀!所以,面向失败设计的第一点,就要通过异步去解耦,避免外部错误影响核心流程,造成不可逆转的错误数据出现。

再来看个例子,你现在需要调用某个外部接口来实现业务流转,比如需要查询OMS系统的库存数据来进行校验,如果毫无保留的全面信任外部接口,就有可能因为网络波动、对端抖动等各种意外,造成预料之外的问题发生。所以,依赖外部资源时,要适当考虑加上重试机制,以及兜底的补偿机制

外部接口调用失败,这时可以重试几次,万一是网络之类的原因,重试说不定就可以啦。重试次数达到阈值后,可以视情况来决定要不要记录异常表,方便后续执行补偿措施。

最后再来看个场景,目前有三条支付通道P1、P2、P3,业务希望的是优先走P2通道,因为它的手续费更低,那这时,你的代码应该围绕着P2通道去展开。可是,当有一天P2通道发生未知错误,导致支付一直无法拉起,咋办?如果设计时没有充分考虑,就会导致P2故障期间无法付款,最终给业务带来惨重损失。

更好的做法是啥?设计降级方案,如果P2通道故障,那可以将P2熔断,降级走P1、P3通道,尽管它们的手续费更高,可是至少能保障业务正常运转。期间不断对P2通道进行检测,一旦恢复正常就切回P2即可。

所以,这里所谓的面向失败设计,就是指对依赖的外部资源做容错设计,从而使得你的接口更健壮,但容错设计的手段有很多,如前面提到的异步解耦、失败重试、熔断降级,也包括回滚机制、失败补偿等等。

十、做好接口限流

对于中台系统、开放平台来说,这一条格外重要,因为你无法确保接入方的业务体量,如果不对接口做限流,可能会导致己端被接入方冲跨。不过如何实现限流,这里就不展开细说了,之前有聊到过,大家可以参考《反爬虫手段-限流方案》

十一、总结

好了,到这里我们已经阐述了大大小小N多个设计接口时的考虑点,但这些也仅仅只是写好一个接口的冰山一角,毕竟本篇只抽出了不同接口类型中的共性考量罢了,后面会单独去讲述如何写好一个读取、写入接口,那时需要考虑的问题更多,比如怎么改善性能?怎么防重?怎么保障一致性?如何处理大数据……,那么我们后面的篇章见~

所有文章已开始陆续同步至微信公众号:竹子爱熊猫,想在手机上便捷阅读的小伙伴可搜索关注~