扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • Go工程化规范设计

    • 前言
    • 开源规范
    • 文档规范
    • 版本规范
    • Git规范
    • 目录结构
    • 编码规范
    • 代码测试
    • 性能分析
    • API 设计
    • 项目管理
    • 研发流程
    • 参考资料
  • Go工程化标准实践

    • 前言
    • 项目结构
    • API 设计
    • 配置管理
    • 模块管理
    • 测试
    • 参考资料

扫码订阅《 》或入驻星球,即可阅读文章!

API 设计


Kyle

为了统一检索和规范 API,可在内部建立统一的仓库,整合所有对内对外 API(可参考 googleapis/googleapis (opens new window)、envoyproxy/data-plane-api (opens new window)、istio/api (opens new window))。

  • API 仓库,方便跨部门协作。
  • 版本管理,基于 git 控制。
  • 规范化检查(API lint)。
  • API design review(变更 diff)。
  • 权限管理,目录 OWNERS

# gRPC

gRPC (opens new window) 是一种高性能的开源统一 RPC 框架:

  • 基于 Proto 的请求响应,支持多种语言。
  • 轻量级、高性能:序列化支持 Protocol Buffer 和 JSON。
  • 可插拔:支持多种插件扩展。
  • IDL:基于文件定义服务,通过 proto3 生成指定语言的数据结构、服务端接口以及客户端 Stub(所有语言都是一致的,可代表文档)。
  • 移动端基于标准 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 多路复用、服务端推送等特性,使得 gRPC 在移动端设备上更加省电和网络流量(传输层透明,便于升级到 HTTP/3、QUIC)。
syntax = "proto3";

package rpc_package;

service HelloWorldService {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
    string name = 1;
}
message HelloReply {
    string message = 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
protoc --go_out=.--go_opt=paths=source_relative \ 
    --go-grpc_out=.--go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto
1
2
3

设计原则:

  • 服务而非对象,消息而非引用:促进微服务系统间粗粒度消息交互设计理念。
  • 负载无关:不同服务使用不同的消息类型和编码,例如 protocol buffers、JSON、XML、Thrift。
  • 流:Streaming API。
  • 阻塞 / 非阻塞:支持异步和同步处理在客户端和服务端间交互消息序列。
  • 元数据交换:常见的横切关注点,如认证或跟踪,依赖数据交换。
  • 标准化状态码:客户端以有限方式响应 API 调用返回的错误(优先使用标准的 HTTP 状态码)。

设计时不要过早关注性能问题,先实现标准化。

# 目录结构

参考:

|-- bapis
    |-- api
        |-- echo
            |-- v1
                |-- echo.proto
                |-- OWNERS 权限拥有者
    |-- rpc 
        |-- status.proto 内部状态码
    |-- metadata 框架元信息
        |-- locale
        |-- network
        |-- device
    |-- annotations 注解定义 options
    |-- third_party 第三方引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 兼容性

维护 API 需要注意总是保持向后兼容(非破坏性)的修改:

  • 为服务添加 API(从协议的角度来看始终是安全的)。
  • 为请求消息添加字段(客户端在新版和旧版中对字段的处理保持一致,添加请求字段就是兼容的)。
  • 为响应消息添加字段(在不改变其他字段的前提下,非资源响应消息可以扩展而不必破坏客户端的兼容性。即使会引入冗余,先前在响应中填充的任何字段应继续使用相同的语义填充)。

应避免破坏性的修改(一般需要修改 major 版本号):

  • 删除或重命名服务,字段,方法或枚举值(如果客户端代码可引用的内容,删除或重命名它都是不兼容的变化)。
  • 修改字段的类型(即使新类型是传输格式兼容的,也可能导致客户端生成代码发生变化,对于静态语言而言会容易引入编译错误)。
  • 修改现有请求的可见行为(客户端通常依赖于 API 行为和语义,即使没有被明确支持或记录。在大多数情况下,修改 API 数据的行为或语义将被消费者视为是破坏性的。如果行为没有加密隐藏,应该假设用户已经发现并依赖于它)。
  • 给资源消息添加读写字段。

# 命名规范

包名为应用的标识(appid),用于生成 gRPC 请求路径或 proto 之间引用 Message。

文件中声明的包名称应该与产品和服务名称一致,带有版本的 API 的软件包名称必须以此版本结尾。

参考():

示例
产品名称 Google Calendar API
服务名称 calendar.googleapis.com
软件包名称 google.calendar.v3
接口名称 google.calendar.v3.CalendarService
来源目录 //google/calendar/v3
API 名称 calendar

请求 URL:/package_name.version.service_name/method

# 原始字段

gRPC 默认使用 Protobuf v3 格式,去除了 required 和 optional 关键字(默认全部是 optional)。没有赋值的字段默认是基础类型字段的默认值,比如 0 或者 “”。

// proto2
message Account {
    // 必须
    required string name = 1;
    // 可选,默认值改为 -1.0,有 haxXxx 方法。
    optional double profit_rate = 2 [default=-1.0];
}

// proto3
message Account {
    // 都是可选,默认值为 0 和 "",无 hasXxx 方法。
    string name = 1
    double profit_rate = 2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

将无法区分默认值或未赋值。因此在 Protobuf v3 中建议使用:wrappers.proto (opens new window)。Wrapper 类型的字段即包装一个 message,使用时变为指针。

message DoubleValue {
    double value = 1;
}
1
2
3

Protobuf 作为强 schema 约束的描述文件,也方便扩展,因此也可以用于配置文件定义。

# 异常处理

首先由于会为服务监控带来麻烦,明确禁止在 HTTP Status Code 中统一设置为 200、在 Body 中再定义 code 字段标记具体错误类型的做法。

使用标准错误配合具体错误:比如服务端使用一个标准 google.rpc.Code.NOT_FOUND 错误代码告知客户端无法找到特定资源(大类:404,小类:具体资源)。

  • 状态空间变小降低了文档的复杂性。
  • 在客户端库中提供了更好的惯用映射,降低了逻辑复杂性。
  • 不限制是否包含可操作信息(/google/rpc/error_details)。

错误传播:如果 API 服务依赖于其他服务,不应盲目地将服务错误传播到客户端。在翻译错误时建议:

  • 隐藏详细信息和机密信息。
  • 调整负责该错误的一方。比如一个服务端从其它服务接收到 INVALID ARGUMENT 错误,应该将 INTERNAL 传播给自己的调用者。

全局错误码 是松散、契约易被破坏的,应在每个服务传播错误时做一次翻译,保证每个服务 + 错误枚举是唯一的,定义在 proto 中(可作为文档)。

# 设计规则

有时接口复用会带来歧义,比如一些字段给 A 方法用、另一些给 B 方法用;如果为不同方法定义 struct 又会造成冗余。

service LibraryService {
    rpc UpdateBook(UpdateBookRequest) returns (Book);
}
message UpdateBookRequest { Book book = 1;}
message Book {
    string name = 1;
    string author = 2;
    string title = 3;
    bool read = 4;
}
1
2
3
4
5
6
7
8
9
10

gRPC 推荐的做法是利用 FieldMask 的部分更新:客户端可执行需要更新的字段信息,空 FieldMask 默认应用到所有字段。

service LibraryService {
    rpc UpdateBook(UpdateBookRequest) returns (Book);
}

message UpdateBookRequest {
    Book book = 1;
    google.protobuf.FieldMask mask = 2;
}
1
2
3
4
5
6
7
8
  • gRPC
  • 目录结构
  • 兼容性
  • 命名规范
  • 原始字段
  • 异常处理
  • 设计规则