后端接口设计开发经验分享

前言

作为后端研发人员,平时需要经常做服务接口设计及开发,需要与前端进行接口联调,排查生产环境线上问题。因此,后端工程师,核心基本工作就是如何把一个接口设计好,以下梳理一些接口设计开发规范及注意事项,希望对大家有所帮助。

后端接口设计细节.png

1、接口参数校验(入参和出参)

接口入参和出参都需要进行校验,
① 例如入参是否不能为空,入参数据长度,入参是否符合预期规则,很多bug由于未做参数校验导致,对于可能改变的参数建议设计为对象类型;
② 对于返回值,当返回值为空时是否返回为空串、空对象、空数组,需要与前端约定好。

2、接口老版本兼容性

C端服务接口,可能移动端发版不会强升级或者存在前后端上线时间差异,就会导致线上环境存在使用老版本的用户,如果新添加了参数,需要考虑前端未传入时给默认值情况;例如

//老接口
void oldMethod(A,B){
  //兼容新接口,传个null或其他默认值代替参数C
  newService(A,B,null);
}

//新接口,暂时不能删掉老接口,需要做兼容。
void newMethod(A,B,C){
  ...
}

3、接口扩展性考虑

① 例如业务中,在用户调用拨打电话接口之后会进行消息推送,是直接就开发一个消息推送功能,还是将消息推送梳理为一个通用流程,在所有需要使用的地方进行调用即可,保留扩展性;
② 消息推送流程,设计为通用流程,同时采用接口定义,可以扩展实现多种消息推送方式。

graph LR
传入参数 --> 构建目标用户群 --> 调用消息中心接口发送 --> 记录发送结果

4、接口防重处理

① 对于查询类型、删除类型接口,不论调用多少次,都是不会产生错误的业务数据,因此不用做防重处理;
② 对于新增和修改,例如转账或者提现类接口,重复提交就会多次转账和提现,影响业务需要做防重处理,让前端传入请求序列号,可以采用redis、LRUMap、数据库防重表、分布式锁等处理。

graph LR
id获取全局请求token --> 写入redis缓存 --> 请求时带上token --> 后端删除 --> 再次请求提示重复

5、核心接口,线程池隔离

登录接口、首页数据接口、转账提现接口等,都可能使用到线程池,某些普通接口也会使用线程池,如果不做线程池隔离,普通接口出bug线程池打满,会导致登录等主要业务受到影响。

graph TB
线程池隔离
subgraph 核心接口
登录接口 --- 首页加载
end
subgraph 普通接口
写入日志 --- 消息推送
end

6、关键接口,日志打印

关键业务代码,需要打印日志进行保驾护航,在入参和出参位置或者其他关键位置,良好的日志打印具有如下好处:
① 方便排查定位线上问题,划清问题责任;
② 生产环境不能直接debug,必须依靠日志查问题和具体异常。

7、三方接口异常、重试、超时

如果调用第三方接口,或者分布式远程服务的的话,需要考虑:
① 异常处理
比如,你调别人的接口,如果异常了,怎么处理,是重试还是当做失败还是告警处理。
② 接口超时
没法预估对方接口一般多久返回,一般设置个超时断开时间,以保护你的接口。之前见过一个生产问题,就是http调用不设置超时时间,最后响应方进程假死,请求一直占着线程不释放,拖垮线程池。
③ 重试次数
你的接口调失败,需不需要重试?重试几次?需要站在业务上角度思考这个问题

8、接口功能单一性原则

单一性是指接口做的事情比较单一、专一。比如一个登陆接口,它做的事情就只是校验账户名密码,然后返回登陆成功以及userId即可。但是如果你为了减少接口交互,把一些注册、一些配置查询等全放到登陆接口,就不太妥。
其实这也是微服务一些思想,接口的功能单一、明确。比如订单服务、积分、商品信息相关的接口都是划分开的。将来拆分微服务的话,是不是就比较简便啦。

9、接口部分场景采用异步处理

举个简单的例子,比如你实现一个用户注册的接口。用户注册成功时,发个邮件或者短信去通知用户。这个邮件或者发短信,就更适合异步处理。因为总不能一个通知类的失败,导致注册失败吧。
至于做异步的方式,简单的就是用线程池。还可以使用消息队列,就是用户注册成功后,生产者产生一个注册成功的消息,消费者拉到注册成功的消息,就发送通知。

graph LR
id2生产者生产消息 -- 注册成功消息 --> 存储端 --发送通知-->消费者消费

10、接口查询优化,串行改为并行

假设我们设计一个APP首页的接口,它需要查用户信息、需要查banner信息、需要查弹窗信息等等。那你是一个一个接口串行调,还是并行调用呢?
可以使用CompletableFuture 并行调用提高性能。

 // 查询获奖经历
        LambdaQueryWrapper<RewardExp> rewardExpQuery = new LambdaQueryWrapper<RewardExp>()
                .eq(RewardExp::getResumeId, resume.getId())
                .eq(RewardExp::getDelFlag, NORMAL)
                .orderByDesc(RewardExp::getDate);
        CompletableFuture<List<RewardExp>> rewardExpFuture = CompletableFuture.supplyAsync(() ->
                rewardExpMapper.selectList(rewardExpQuery)
        );

        // 查询资格证书
        LambdaQueryWrapper<Credential> credentialQuery = new LambdaQueryWrapper<Credential>()
                .eq(Credential::getResumeId, resume.getId())
                .eq(Credential::getDelFlag, NORMAL)
                .orderByDesc(Credential::getDate);
        CompletableFuture<List<Credential>> credentialFuture = CompletableFuture.supplyAsync(() ->
                credentialMapper.selectList(credentialQuery)
        );

        // 查询工种
        LambdaQueryWrapper<ResumeJobs> jobsQuery = new LambdaQueryWrapper<ResumeJobs>()
                .eq(ResumeJobs::getResumeId, resume.getId()).eq(ResumeJobs::getDelFlag, NORMAL);
        CompletableFuture<List<ResumeJobs>> jobsFuture = CompletableFuture.supplyAsync(() ->
                resumeJobsMapper.selectList(jobsQuery)
        );

        CompletableFuture.allOf(rewardExpFuture, credentialFuture, jobsFuture).join();

11、接口合并与批处理

数据库操作或或者是远程调用时,能批量操作就不要for循环调用。一个简单例子,我们平时一个列表明细数据插入数据库时,不要在for循环一条一条插入,建议一个批次几百条,进行批量插入。同理远程调用也类似想法,比如你查询营销标签是否命中,可以一个标签一个标签去查,也可以批量标签去查,那批量进行,效率就更高。

//反例
for(int i=0;i<n;i++){
  remoteSingleQuery(param)
}
//正例
remoteBatchQuery(param);

12、接口性能、Sql优化

我们做后端的,写好一个接口,离不开SQL优化。
SQL优化从这几个维度思考:
① explain 分析SQL查询计划(重点关注type、extra、filtered字段)
② 索引优化 (覆盖索引、最左前缀原则、隐式转换、order by以及group by的优化、join优化)
③ 大分页问题优化(延迟关联、记录上一页最大ID)
④ 数据量太大(分库分表、同步到es,用es查询)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 1.好的微服务是什么样的? 1.1.特点分析 首先看一下微服务架构的定义:微服务(MSA)是一种架构风格,旨在通过...
    写完了没阅读 2,938评论 0 5
  • 阿里巴巴 JAVA 开发手册 1 / 32 Java 开发手册 版本号 制定团队 更新日期 备 注 1.0.0 阿...
    糖宝_阅读 7,564评论 0 5
  • 来源与:阿里云栖 禁止用于商业用途 ps:如果需要电子书 评论你们邮箱 我会发给你们 下面感觉还是有点乱 目录 一...
    小向资源网阅读 7,585评论 0 12
  • 前言 本开发规范基于《阿里巴巴Java开发手册终极版》修改,并集成我们自己的项目开发规范,整合而成。 为表示对阿里...
    4ea0af17fd67阅读 5,640评论 0 5
  • 一、编程规约 (一)命名规约 【强制】 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。反...
    喝咖啡的蚂蚁阅读 1,506评论 0 2