API设计的几条原则

API 本身的含义指应用程序接口,包括所依赖的库、平台、操作系统提供的能力都可以叫做 API 。我们在讨论微服务场景下的 API 设计都是指 WEB API,一般的实现有 RESTful、RPC等 。API 代表了一个微服务实例对外提供的能力,因此 API 的传输格式(XML、JSON)对我们在设计 API 时的影响并不大 。

API设计的几条原则

文章插图
 
API 设计是微服务设计中非常重要的环节,代表服务之间交互的方式,会影响服务之间的集成 。通常来说,一个好的 API 设计需要满足两个主要的目的:
  • 平台独立性 。 任何客户端都能消费 API,而不需要关注系统内部实现 。API 应该使用标准的协议和消息格式对外部提供服务 。传输协议和传输格式不应该侵入到业务逻辑中,也就是系统应该具备随时支持不同传输协议和消息格式的能力 。
  • 系统可靠性 。 在 API 已经被发布和非 API 版本改变的情况下,API 应该对契约负责,不应该导致数据格式发生破坏性的修改 。在 API 需要重大更新时,使用版本升级的方式修改,并对旧版本预留下线时间窗口 。
实践中发现,API 设计是一件很难的事情,同时也很难衡量设计是否优秀 。根据系统设计和消费者的角度,给出了一些简单的设计原则 。
使用成熟度合适的 RESTful APIRESTful 风格的 API 具有一些天然的优势,例如通过 HTTP 协议降低了客户端的耦合,具有极好的开放性 。因此越来越多的开发者使用 RESTful 这种风格设计 API,但是 RESTful 只能算是一个设计思想或理念,不是一个 API 规范,没有一些具体的约束条件 。
因此在设计 RESTful 风格的 API 时候,需要参考 RESTful 成熟度模型 。
API设计的几条原则

文章插图
RESTful 成熟度模型 。
根据自己的应用场景选择对应的成熟度模型,一般来说系统成熟度模型在 Level 2左右 。
避免简单封装API应该服务业务能力的封装,避免简单封装让API彻底变成了数据库操作接口 。例如标记订单状态为已支付,应该提供形如POST /orders/1/pay这样的API 。而非PATCH /orders/1,然后通过具体的字段更新订单 。
因为订单支付是有具体的业务逻辑,可能涉及到大量复杂的操作,使用简单的更新操作将业务逻辑泄漏到系统之外 。同时系统外也需要知道订单状态 这个内部使用的字段 。
更重要的是,破坏了业务逻辑的封装,同时也会影响其他非功能需求 。例如,权限控制、日志记录、通知等 。
关注点分离好的接口应该做到不多东西,不少东西 。怎么理解呢?在用户修改密码和修改个人资料的场景中,这两个操作看起来很类似,然后设计API的时候使用了一个通用的/users/1/udpateURI 。
然后定义了一个对象,这个对象可能直接使用了User这个类:
{"username": "用户名","password": "密码"}这个对象在修改用户名的时候,password是不必要的,但是在修改密码的操作中,一个password字段却不够用了,可能还需要
confirmPassword 。
于是这个接口变成:
{"username": "用户名","password":"密码","confirmPassword":"重复密码"}这种类的复用会给后续维护的开发者带来困惑,同时对消费者也非常不友好 。合理的设计应该是两个分离的 API:
// POST /users/{userId}/password{"password":"密码","confirmPassword":"重复密码"}// PATCH /users/{userId}{"username":"用户名","xxxx":"其他可更新的字段"}对应的实现,在 JAVA 中需要定义两个 DTO,分别处理不同的接口 。这也体现了面向对象思想中的关注点分离 。
完全穷尽,彼此独立API 之间尽量遵守完全穷尽,彼此独立 (MECE) 原则,不应该提供相互叠加的 API 。例如订单和订单项这两个资源,如果提供了形如 PUT /orders/1/order-items/1 这样的接口去修改订单项,接口 PUT /orders/1 就不应该具备处理某一个 order-item 的能力 。
这样的好处是不会存在重复的 API,造成维护和理解上的复杂性 。如何做到完全穷尽和彼此独立呢?
简单的方法是使用一个表格设计 API,标出每个 URI 具备的能力 。
API设计的几条原则

文章插图
API设计表格
资源 URL 设计来源于 DDD 领域建模就非常简单了,聚合根作为根 URL,实体作为二级 URI 设计 。聚合根之间应该彻底没有任何联系,实体和聚合根之间的责任应该明确 。


推荐阅读