如此丝滑的API设计,用起来真香

news/2024/11/17 4:55:31/文章来源:https://www.cnblogs.com/kdaddy/p/18200166

分享是最有效的学习方式。

博客:https://blog.ktdaddy.com/


故事

工位上,小猫一边撸着代码,一边吐槽着前人设计的接口。

如下:

“我艹,货架模型明明和商品SKU模型是一对多的关系,接口入参的时候偏偏要以最小粒度的SKU将重复入参进行平铺”。

“一个接口居然做了多件事情,传入参数复杂异常,不是一块业务类型的东西,非得全部揉在一起”。

“如此长的业务流程,接口能快起来么,难怪天天收到接口慢的告警”。

业务告警

“这都啥啊,这名字怎么能这么取呢,这也太随意了吧....”

......

小猫一边写着V2版本的新接口,一边骂着现状接口。

聊聊APi设计

在日常开发过程中,相信大家在维护老代码的时候也多多少少会像小猫一样吐槽现有接口设计。很多项目经过历史沉淀以及业务验证,接口设计问题就慢慢放大暴露出来了。具体原因是这样的:

第一种情况可能是业务发展的必然趋势:不同技术人员对业务的看法和理解不同,一个接口可能经过多人的维护开发迭代,很多时候,新增功能也只是在原有的接口上直接拓展,当业务需求比较紧急的时候,大部分的研发一般都会选择快速去实现,而不会太过去考虑现有接口拓展的合规性。

第二种情况可能是本身开发人员自身能力问题,对业务的把控以及评估不合理导致的最终接口设计缺陷问题。

在系统软件开发过程中,一个好的UI设计可以让用户更好地使用一款产品。那么深入一层,一个好的API设计则可以让开发者高效地使用一个系统的能力,尤其是现在很多大型微服务项目中,API设计更加重要,因为此时的API调用方不仅仅是前端,甚至直接是其他服务。

那么接下来,老猫会和大家从下面的几个方面探讨一下,日常开发中我们应该如何去设计API。

概览

API设计需要明确边界

在实际职场中,部门与部门之间、管理员与管理员之间容易出现扯皮、推诿现象。当然在系统和系统之间API的交互中其实往往也存在这样的情况。打个比方客户端的交互细节让后端代码通过接口来兜,你觉得合理不?

所以这就要求我们遵循下面两个点,咱们分别中两个维度来看,一个是“面向于服务和服务之间的API”,另一个是“面向客户端和服务之间的API”。

1、我们在设计API的过程中应该聚焦软件系统需要提供的服务或者能力。API是系统和外部交互的接口,至于外部如何使用,通过什么途径使用并不是重点。

2、对于面向UI的API设计中,我们更应该避免去过多关注UI的交互细节。交互属于客户端范畴,不同的终端设备,其交互必然也是不一样的。

API设计思路尽量面向结果设计而不是面向过程设计

相信大家应该都知道面向对象编程和面向过程编程吧。

老猫虽说的这里的面向结果设计其实和面向对象的概念有点类似。这种情况下的API应该是根据对象的行为来封装具体的业务逻辑,调用方直接发起请求需要什么就能给出一个最终的结果性质的东西,而不是中间过程中某个状态性质的东西。上层业务无需多次调用底层接口进行组装才能获取最终结果。

如下图:

面向执行过程API设计

面向最终结果API设计

举个例子。

银行提现逻辑中,

如果面向执行过程设计的API应该是这样的,先查询出余额,然后再进行扣减。于是有了下面这样的伪代码。

public interface BankService {AccountInfo getAccountByUserName(String userName);void updateAccount(AccountInfoReq accountInfoReq);
}

如果是面向结果设计,那么应该就是这样的伪代码。

public interface BankService {AccountInfo withdraw(String userName,Long amount);
}

API设计需要尽量保证职责单一

在设计API的时候,应该尽力要求一个API只做一件事情,职责单一的API可以让API的外观更加稳定,没有歧义。并且上层调用层也是一目了然,简单易用。

对于一个API如果符合下面条件的时候,咱们就可以考虑对其进行拆分了。

1、一个API内部完成了多件事情。例如:一个API既可以发布新商品信息,又能更新商品的价格、标题、规格信息、库存等等。如果这些行为在一个接口进行调用,接口复杂度可想而知。
另外的接口的性能也是需要考虑的一部分,再者如果后续涉及权限粒度拆分,其实这种设计就不便于权限管控了。

2、一个API用于处理不同类型对象的业务。例如:一个API编辑不同的商品类型,由于不同类型的商品对应的模型通常是不同的(例如出行类的商品以及卡券类的商品差别就很大),
如果放在一个API中,API的输入和输出参数会非常复杂,使用和维护成本就很高。

其实关于API单一职责相关的话题,老猫在之前的文章中也有提及过,有兴趣的小伙伴可以戳【忍不了,客户让我在一个接口里兼容多种业务功能】

API不应该基于实现去设计

在API设计过程中,我们应该避免实现细节。一个API有多种实现,在API层面不应该暴露实现细节,从而误导用户。

例如生成token是最为常见的,生成token的方式也会有很多种。可以通过各种算法生成token,
有的是根据用户信息的hash算法生成,或者也可以用base64生成,甚至雪花算法直接生成。如果对外暴露更多实现细节,其实内部实现的可拓展性就会相当差。
我们来看一下下面的代码。

//反例:暴露实现细节
public interface tokenService {TokenInfo generateHashTokenByUserName(String userName);
}
//正例:足够抽象、便于拓展
public interface tokenService {TokenInfo generateToken(Object key);
}

API的命名相当重要

一个好的API名字无疑是相当重要的,使用者一看API的命名就能知道如何使用,可以大大降低调用方的使用成本。所以我们在设计API的时候需要注意下面几个方面。

1、API的名字可以自解释,一个好的API的名称可以清晰准确概括出API本身提供的能力。

2、保持对称性。例如read/write,get/set。

3、基本的API的拼写务必准确。API一旦发布之后,只能增加新的API去订正,旧API完全没有请求量之后才能废弃,错误的API的拼写可能会带给调用方理解上的歧义。

API设计需要避免标志性质的参数

所谓标志性的参数,就是一个接口为了兼容不同的逻辑分支,增加参数让调用方去抉择。这块其实和上述提及的API设计保证职责单一有点重复,但是老猫觉得很重要,所以还是
单独领出来细说一下。举个例子,上述提及的发布商品,在发布商品中既有更新的原有商品信息的功能在,又有新增商品的功能在。于是就有了这样错误的设计,如下:

public class PublishProductReq {private String title;private String headPicUrl;private List<Sku> skuList;//是否为更新动作,isModify就是所说的标志性质的参数private Boolean isModify;.....
}

那么对应的原始的发布接口为:

//反例:内部入参通过isModify抉择区分不同的逻辑
public interface PublishService {PublishResult publishProduct(PublishProductReq req);
}

比较好的逻辑应将其区分开来,移除原来的isModify标志位:

public interface PublishService {PublishResult addProduct(PublishProductReq req);PublishResult editProduct(PublishProductReq req);
}

API设计出入参需要保证风格一致

这里所说的出入参的风格一致主要指的是字段的定义需要保持一个,例如对外的订单编号,一会叫做outerNo,一会叫做outerOrderNo。相关的用户在调用的时候八成是会骂娘的。

老猫最近其实在对接供应商的相关API,调用对方创建发货订单之后返回的订单编号是orderNo,后来用户侧完成订单需要通知供应商,入参是outerNo。老猫此时是懵逼的,都不知道这个
outerNo又是个什么,后来找到对面的研发沟通了一轮才知道原来outerNo就是之前返回的orderNo。

于是“我艹,坑笔啊”收尾.....

API设计的时候考虑性能

最后再聊聊API性能,维护了很多的项目,发现很多小伙伴在设计接口的时候并不会考虑接口性能。或者说当时那么设计确实不会存在接口的性能问题,可是随着业务的增长,数据量的增长,
接口性能问题就暴露出来了。就像上面小猫吐槽的,接口又又又慢了,又在报接口慢警告了。

举个例子,查询API,当数据量少的情况下,一个List作为最终返回搞定没有问题的。但是随着时间的推移,数据量越来越大,List能够cover吗?显然是不行的,此时就要考虑是否需要通过分页去做。
所以原来的List的接口就必须要改造成分页接口。

当然关于API性能的优化提升,老猫整理了如下提升方式。

1、缓存:CRUD的读写性能毕竟是有限的。所以对某些数据进行频繁的读取,这时候,可以考虑将这些数据缓存起来,下次读取时,直接从缓存中读取,减少对数据库的访问,提升API性能。

2、索引优化:很多时候接口慢是由于数据库性能瓶颈,如果不用上述提及的缓存,那么我们就需要看一下接口究竟是慢在哪个环节,可能是某个查询,可能是更新,所以我们就要分析
执行的SQL情况去添加一些索引。当然这里涉及如何进行MYSQL索引优化的知识点了,老猫在此不展开。

3、分页读取:如上述老猫举的例子中,针对的是那种随着数据量增长暴露出来的,那么我们就要对这些数据进行分页读取处理。

4、异步操作:在一个请求中开启多任务模式。

异步操作模式

举个例子:订单支付中,支付是核心链路,支付后邮件通知是非核心链路,因此,可以把这些非核心链路的操作,改成异步实现,
这样就可以提升API的性能。常用的异步方式有:线程池,消息队列,事件总线等。当然自从Java8之后还有比较好用的CompletableFuture。

5、Json序列化:JSON可以将复杂的数据结构或对象转换为简单的字符串,以便在网络传输、存储或与其他程序交互时进行数据交换。
优化JSON序列化过程可以提高API性能。使用高效的序列化库,减少不必要的数据字段,以及采用更紧凑的数据格式,都可以减少响应体的大小,从而加快数据传输速度和解析时间。

6、其他提升性能方案:例如运维侧提升带宽以及网速等等

上述罗列了相关API性能提升的一些措施,如果大家还有其他不错的方法,也欢迎留言。

总结

谈及软件中的设计,无论是架构设计还是程序设计还是说API设计,
原则其实都差不多,要能够松耦合、易扩展、注意性能。遵循上述这些API的设计规则,
相信大家都能设计出比较丝滑的API。当然如果还有其他的API设计中的注意点也欢迎在评论区留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/708662.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《user-agent(UA)识别 Api 接口助力智能应用开发》

在现代智能应用的开发中,往往需要对用户的设备和浏览器进行识别,以便适配不同的操作系统和浏览器。而user-agent是一种非常重要的信息,它包含了用户设备、操作系统和浏览器的相关信息。在本文中,我们将介绍一个强大的user-agent识别 API 接口,它可以帮助开发者轻松实现用户…

科学时如何更快进行DNS解析及微信双开

如何更快进行DNS解析科学了,发现访问很慢,有时还无法访问,明显是被某种神秘的东方力量给阻断了。 DNS解析就起作用了。可以快速寻址,目前国内比较知名的且比较快的就是阿里云的:223.5.5.5。但是呢,这还需要看你自己的网络是哪家的,去访问国际的时候路由节点是否在国内来…

eclipse安装tomcat

一、确保Tomcat服务器处于关闭状态在配置之前确保tomcat服务器处于关闭状态,若tomcat处于启动状态则将其关闭,Service Status的值为Stopped表明Tomcat已经关闭 二、在Eclipse中配置Tomcat打开Eclipse---->点击Window---->点击Preferences点击Server---->点击Runtime…

Redis安装之集群-集群(cluster)模式

一、背景 Redis 哨兵模式在一定程度上解决的系统的高可用问题,但单 master 节点的写入也成为了系统处理高并发请求时的瓶颈。 二、方案原理采用多个 master 节点集群模式实现 Redis 水平扩容,提供并发请求处理能力; cluster 自带 sentinel 故障转移机制,无需再使用哨兵功能…

主流原型设计工具介绍

当谈到原型设计工具时,Axure 和墨刀是两个备受推崇的选择。它们各自拥有独特的特点和优势,适用于不同的设计需求和团队工作流程。今天我会重点介绍这两种工具的特点以及使用方法,并且简单介绍其他的一些原型设计工具例如:Sketch,Figma Axure Axure 是一款功能强大的原型设计…

【HFSS】看多个频点的三维方向图

1.扫频设置 扫频种类为Discrete,记得要保存场,Save Fields2.查看结果solution选择Sweep1,就是刚才新建的扫频设置即可在选项卡Families里面可以选择要查看的频点

MQTT详解以及实际操作

目录1 MQTT1.1 MQTT介绍1.1.1 简介1.1.2 特点和应用1.1.3 为什么要用 MQTT协议1.2 MQTT控制报文的结构1.2.1 固定报文头(Fixed Header)1.2.2 可变报文头(Variable Header)1.2.3 有效负荷和消息类型1.2.4 消息质量(QoS)1.4 搭建MQTT服务1.5 SpringBoot搭建提供端1.5.1 pom…

CERIO-DT系列路由器Save.cgi接口存在命令执行漏洞

漏洞描述: 由于未经过过滤和适当限制的情况下,传入的参数直接用于构建并执行系统命令,攻击者通过将恶意命令注入到"Save.cgi"接口的请求参数中可以执行任意命令。 Fofa: title="DT-100G-N" || title="DT-300N" || title="DT-100G" …

H2 数据库介绍(2)--使用

本文主要介绍 H2 的基本使用,文中所使用到的软件版本:Java 1.8.0_341、H2 2.2.224、PostgreSQL 驱动 42.5.5。 1、嵌入式(本地)模式 直接使用 JDBC 连接数据库即可,如果数据库不存在会自动创建。 1.1、持久数据库@Test public void localFile() throws SQLException {String…

利用Burpsuite爆破带有验证码web登录接口

工具下载地址 https://github.com/f0ng/captcha-killer-modified 该工具下的验证码识别python脚本要求python环境小于3.10.0 安装验证码识别python脚本引用的库 pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com ddddocr aiohttp 加载…

整理C语言预处理过程语法的实用方法与技巧

预处理 目录预处理一、宏定义数值宏常量字符串宏常量用define宏定义注释符号?程序的编译过程预处理中宏替换和去注释谁先谁后?如何写一个不会出现问题的宏函数do-while-zero结构do-while-zero的评价宏定义中的空格宏只能在main函数上面定义吗?宏的作用范围#undef宏替换是在函…

如何把多个文件(夹)平均复制到多个文件夹中

首先,需要用到的这个工具:度娘网盘 提取码:qwu2 蓝奏云 提取码:2r1z 假定的情况是,共有20个兔兔的图片,想要平均的复制4个文件夹里,那么每个文件夹里面就有5个图片(如果是5个,那每个自然是4个,具体除数是多少,根据实际情况即可)打开工具,切换到 文件批量复制 版块…