本文讨论一下什么是API,以及如何进行设计出好的API。
API的概念
API:Application Programmable Interface,我们通常理解的接口。
它是一个方法,可以提供给使用方进行远程访问。它与系统内部的具体实现无关,只定义了和外部使用方的交互方式,他们需要提供什么,方法可以返回什么,因此API描述的可以通过这个接口做什么事情。
public List<Admin> getAdmins(String groupId);
上面是一个API的定义,API接收一个groupId并返回所有的管理员,这里getAdmin为API的名字,groupId为传入参数,List<Admin>为返回值。
API设计的Checklist
- Where:API应该在哪里定义。在微服务中,存在一个和Group相关的服务,那么上面通过GroupID获取所有管理员的API应该在Group服务中实现。
- What:API的功能是什么。API的名字要可以明确的表述出API能够实现的功能。
- How:API如何调用。API的参数设计,定义了在调用API时需要传入的参数。
- Result:API返回什么。API在执行结束后,返回给客户端的结果。
API设计的常见错误
- 命名问题:例如上面的API,返回的是属于某组的管理员,那它的名字就不应该是getAdmins,而应该是类似于getAdminsBelongToGroup,能够准确表达它的含义。
- 参数定义问题:假如请求方想判断一个用户列表是否全部为该组的管理员,那应该再设计一个API类似:
public boolean checkAdmins(String groupId, List<String> userId);
这里要强调的是,要根据API的功能进行参数的设计,不要传入额外不必要的参数。
- 参数类型的定义:定义准确的参数数据类型,如果调用者传入了错误的参数类型,直接返回调用失败。
- 尽可能多携带有用信息:假设getAdmins操作需要对传入的groupId进行额外的,比如鉴权或者之类的判断,而这些判断在调用getAdmins之前已经获得。那么是可以将这部分信息通过参数传给getAdmins,以减少重复的调用。
- 返回内容定义问题:有种设计方式是将所有信息全部提供给调用者,可以不关心调用者使用哪些信息,从安全和网络性能的角度考虑,这种方式是不推荐的,应该按照调用者的需求设计返回内容的格式。
POST和GET请求
仍然以获取某个组的所有管理员为例,如果设计成POST请求,那么API为:
* POST:https://www.meazza.tk/chat_messaging/getAdmins,
* Request:
{
"groupId": "123"
}
* Response:
{
"admins": [
{
"id": 11111,
"name": "meazza"
}
]
}
该POST请求也可以修改为将action放在Request中:
* POST:https://www.meazza.tk/chat_messaging,
* Request:
{
"groupId": "123",
"action": "getAdmins"
}
* Response:
{
"admins": [
{
"id": 11111,
"name": "meazza"
}
]
}
如果设计成GET请求,不需要发送payload,设计的API如下:
* GET:https://www.meazza.tk/chat_messaging/admins?groupId=123
* Response:
{
"admins": [
{
"id": 11111,
"name": "meazza"
}
]
}
避免副作用
假设有一个API,是将一组用户设置为该组的管理员,方法定义如下:
public void setAdmins(List<Admin> admins, String groupId);
现在的问题是,如果admins中包含了非该组的成员,该如何处理?一种方式是API的方法逻辑中进行判断,如果发生这种情况返回请求失败;另一种方式是将admins中不是该组成员的,添加到该组中,并设置成管理员。因此这个API的行为是不确定的,可能并不是一个好的API设计。
如果采用第二种处理方式,那么这个API的带有副作用的,也就是将一些用户添加到了该组中。因此这里有两个API的设计原则:
- 独立性:比如在API中完成了多种操作,并通过一些flag决定执行哪些操作的话,这个API是很奇怪的,此时应该拆成多个API。
- 原子性:比如setAdmins的实现,每次都要先清除group的管理员,如果某次请求失败,可能导致后续的请求无法执行,因此要确保每次API的操作都是相同的,不能存在中间状态。
解决返回数据量过大
如果一个API返回的数据量比较多,有两种方式可以进行优化:
- 分页(Pagination):比如一个返回200个用户的API,可以每次返回10个,并标记当前页数和总页数等信息。
- 分段(Fragment):分拆这个API的返回信息,并使用多个API实现,某个API返回其中的一部分信息,客户端可以按顺序进行依次请求。
数据一致性和可用性
最后讨论一些API在实际使用中的一些问题:
- 数据一致性:假如API是从数据库中查询数据,如果在API查询后,数据库中添加了新的数据,可能会导致出现数据不一致的情况,如果要解决不一致的问题,就会导致API的性能下降。因此要结合应用场景考虑,如果是不需要保证数据严格一致的场景,比如查询一个帖子的所有评论,是可以接受不一致的情况的。
- API的降级:如果API的返回内容过大导致有可能崩溃,此时应该保证API可以返回最重要的信息,而丢弃一部分信息,使得系统不会彻底崩溃。
小结
本节介绍了关于API设计的方方面面,在保证API设计的基本规范的前提下,在设计时还要考虑实际的应用场景,结合实际情况设计出最合适系统的API。
欢迎大家订阅专题,其中包含了系统设计基础系列的全部文章:System Design
网友评论