系统设计基础9:API的设计

本文讨论一下什么是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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容