
2024年在线市场平台的11大最佳支付解决方案
在 Go 工程化(二) 项目目录结构 中我们大概讲了一下 api 目录,但是并没有详细的说明,留在这这篇文章一起讲。
API 设计将分为四个部分:
b 站内部主要使用 grpc 作为内部通信的方式,因为他使用 protobuf 文件定义可以支持对语言代码生成,同时还避免了手写文档导致的文档错误过时等情况,具体的原因其实在第一课的笔记当中就有提到,如果感兴趣可以查看 微服务(二) 服务发现&多租户#gRPC
我们目前使用类似 http restful 的方式进行对外对内提供服务,但是我们之前的 API 管理其实是比较混乱的,分为以下几种情况:
所以就存在很多问题:
所以课程上毛老师提到的利用 protobuf 来定义接口的方式非常令人心动,因为 protobuf 当中包含了接口的函数签名,入参和返回值同时还支持注释,就是一份天然的文档,同时也不用担心出现代码更新了但是文档没有更新的情况,因为它既是文档也是代码,服务端也需要使用,所以代码更新之后文档也一定会更新。自然而然的就少了很多沟通的成本。
如上图所示于此同时我们还可以利用 protobuf 文件生成对应语言的客户端代码,就不用每个项目都去维护一套 sdk 了,同时我们使用接口生成代码,在 go 当中可以使用 gomock 非常方便的对代码进行 mock。
使用 protobuf 定义接口可以解决我们找到 api 文档之后,文档不准确,缺失的问题,但是我们应该如何找到我们的 api 呢?我们生成出的 api 文件调用方应该如何引用呢?难道我们给每个调用方都去开一个项目的权限么?那明显是不太行的,接下来我们就看看我们 api 该如何管理和组织。
毛老师他们仿照 googleapis/googleapis,istio/api 等知名项目在 b 站内部搞了一个 bapis 的仓库用于同一存放 api 定义文档,然后通过 ci/cd 生成对应的客户端代码放到各个语言的子仓库当中
工作流程如上图所示
我们的 api 项目是如何定义的呢?看下图
随着应用的不断开发,业务的不断发展我们的 api 肯定会不断的进行修改,在修改 api 的时候考虑 api 的兼容性就会很重要了,如果我们做了一些破坏性的变更就有可能会导致依赖我们的服务或者是客户端报错,这样就会带来事故。
一般而言新增都是相对安全的,但是我们要注意的是新增字段不能改变我们原本的逻辑,如果改变了 api 的逻辑,那就不一定安全了
产品名 | product |
---|---|
应用名 | app |
版本号 | v1 |
包名 | product.app.v1 |
目录结构 | api/product/app/v1/xx.proto |
「标准方法」 | 「HTTP 映射」 |
---|---|
List | GET |
Get | GET |
Update | PUT 或者 PATCH |
Create | POST |
Delete | DELETE |
除了标准的也有一些非标准的,例如同步数据可能会用 Sync
等,不过大部分的 api 应该都是标准的
// api/product/app/v1/blog.proto
syntax = "proto3";
package product.app.v1;
import "google/api/annotations.proto";
// blog service is a blog demo
service BlogService {
rpc GetArticles(GetArticlesReq) returns (GetArticlesResp) {
option (google.api.http) = {
get: "/v1/articles"
additional_bindings {
get: "/v1/author/{author_id}/articles"
}
};
}
}
注意,一般而言我们应该为每个接口都创建一个自定义的 message,为了后面扩展,如果我们用 Empty 的话后续就没有办法新增字段了
先说我们当前的问题,我们一直用的 http 然后我们返回是使用的下面这种格式,然后 http code 统一返回 200
{
"code": 1,
"msg": "xxx",
"data": {}
}
这种做法就存在一个比较大的问题,做监控的时候不太好做,很多现成的东西没有办法直接使用,因为我们都返回的成功。参照 google 的错误定义,将 http code 和 grpc 错误码进行映射,返回对应的错误信息
但是这样还是不行,因为这样很多业务错误信息无法区分,毛老师他们的 kratos v2 的做法是做了两层,使用下面的方式进行定义
message Status {
// 错误码,跟 grpc-status 一致,并且在HTTP中可映射成 http-status
int32 code = 1;
// 错误原因,定义为业务判定错误码
string reason = 2;
// 错误信息,为用户可读的信息,可作为用户提示内容
string message = 3;
// 错误详细信息,可以附加自定义的信息列表
repeated google.protobuf.Any details = 4;
}
和我们当前的方式差不太多,但是我们是在原来的基础上返回了 http code,剩下的字段还是和原来保持一致
这一点我们之前做的还行,错误传播这一部分很容易出的问题就是,当前服务直接把上游服务的错误给返回了,这样会导致一些问题:
正确的做法应该是把上游错误信息吞掉,返回当前服务自己定义的错误信息就可以了。
毛老师课上讲的 api 设计思路用起来还是挺爽的,我们已经在一个项目当中进行了试点,cicd 的流程也跑了起来,最爽的一点就是终于不用找接口文档了,然后还节省了一些代码量,我们之前的接口调用方式都是十分原始的,每个项目都自己去封装相关的 sdk 然后我们对单元测试还有要求,http 接口的 mock 是挺麻烦的事情,通过 protobuf 定义接口之后我写了一个结合内部网关的 sdk 代码生成器,直接生成相关接口代码,go interface 的 mock 实现也在 ci 流程中生产好了,调用方只需要调用不同的实现就行了。下一篇我们就通过写一个 从 proto 生成 gin 代码的生成器来看看这个代码生成器改如何实现。