RPC 框架的讨论一直是各个技术交流群中的热点话题,阿里的 dubbo,新浪微博的 motan,谷歌的 grpc,以及不久前蚂蚁金服开源的 sofa,都是比较出名的 RPC 框架。RPC 框架,或者一部分人习惯称之为服务治理框架,更多的讨论是存在于其技术架构,比如 RPC 的实现原理,RPC 各个分层的意义,具体 RPC 框架的源码分析…但却并没有太多话题和“如何设计 RPC 接口”这样的业务架构相关。
可能很多小公司程序员还是比较关心这个问题的,这篇文章主要分享下一些个人眼中 RPC 接口设计的最佳实践。
初识 RPC 接口设计
由于 RPC 中的术语每个程序员的理解可能不同,所以文章开始,先统一下 RPC 术语,方便后续阐述。
大家都知道共享接口是 RPC 最典型的一个特点,每个服务对外暴露自己的接口,该模块一般称之为 api;外部模块想要实现对该模块的远程调用,则需要依赖其 api;每个服务都需要有一个应用来负责实现自己的 api,一般体现为一个独立的进程,该模块一般称之为 app。
api 和 app 是构建微服务项目的最简单组成部分,如果使用 maven 的多 module 组织代码,则体现为如下的形式。
serviceA 服务
serviceA/pom.xml 定义父 pom 文件
serviceA-apiserviceA-apppommoe.cnkiritoserviceA1.0.0-SNAPSHOT
serviceA/serviceA-api/pom.xml 定义对外暴露的接口,最终会被打成 jar 包供外部服务依赖
serviceA moe.cnkirito1.0.0-SNAPSHOTjarserviceA-api
serviceA/serviceA-app/pom.xml 定义了服务的实现,一般是 springboot 应用,所以下面的配置文件中,我配置了 springboot 应用打包的插件,最终会被打成 jar 包,作为独立的进程运行。
serviceA moe.cnkirito1.0.0-SNAPSHOTjarserviceA-apporg.springframework.boot spring-boot-maven-plugin
麻雀虽小,五脏俱全,这样一个微服务模块就实现了。
旧 RPC 接口的痛点
统一好术语,这一节来描述下我曾经遭遇过的 RPC 接口设计的痛点,相信不少人有过相同的遭遇。
查询接口过多
各种 findBy 方法,加上各自的重载,几乎占据了一个接口 80% 的代码量。这也符合一般人的开发习惯,因为页面需要各式各样的数据格式,加上查询条件差异很大,便造成了:一个查询条件,一个方法的尴尬场景。这样会导致另外一个问题,需要使用某个查询方法时,直接新增了方法,但实际上可能这个方法已经出现过了,隐藏在了令人眼花缭乱的方法中。
难以扩展
接口的任何改动,比如新增一个入参,都会导致调用者被迫升级,这也通常是 RPC 设计被诟病的一点,不合理的 RPC 接口设计会放大这个缺点。
升级困难
在之前的 “初识 RPC 接口设计”一节中,版本管理的粒度是 project,而不是 module,这意味着:api 即使没有发生变化,app 版本演进,也会造成 api 的被迫升级,因为 project 是一个整体。问题又和上一条一样了,api 一旦发生变化,调用者也得被迫升级,牵一发而动全身。
难以测试
接口一多,职责随之变得繁杂,业务场景各异,测试用例难以维护。特别是对于那些有