gRPC 接口设计建议 - 处理创建和更新的正确样式
gRPC interface design advice - correct style for handling creates and updates
我们正在构建一个新平台,其中 gRPC 提供消息传递层,最终将公开我们 API 预期支持的全套功能。
我们正在尝试确定命名接口的最佳模式,以避免重复的消息类型,不得不繁重地处理边缘情况
我们正在做的一个简单示例涉及创建、更新和检索用户。这是我们今天的服务的示例:
service Users {
rpc GetUser(UserRequest) returns (core.user.User) {}
rpc ListUsers(google.protobuf.Empty) returns (ListUsersResponse) {}
rpc CreateUser(core.user.User) returns (core.user.User) {}
rpc UpdateUser(core.user.User) returns (core.user.User) {}
}
message UserRequest {
string id = 1;
}
message ListUsersResponse{
repeated core.user.User users = 1;
}
GetUser 非常简单 - 它接受一个简单的 UserRequest 消息,其中包含一个 ID,returns 一个用户(来自我们的核心包 - 应用程序中的许多服务将接受一个用户消息作为输入,所以我们将其放在共享位置)。
我的问题专门针对 Create/Update 用户调用,因为目前尚不清楚最佳解决方案是什么。这两个函数略有不同,主要在于在一种情况下我们已经有一个用户,因此有一个 ID,而在另一种情况下我们正在创建一个新用户。在 Create 案例中,我们可能只有 User 上可能存在的可用字段的一个子集——但理想情况下,我们只需要在一个地方维护这个列表,因为它可能会变得相当大。我们可以:
- 对于每个调用,定义自定义 request/response 消息,并在其中嵌入任何常用消息。这看起来像下面的代码。我担心的是,我们最终基本上每次调用都有一个消息类型,从可维护性的角度来看,这最终可能会非常繁重。
代码
message CreateUserRequest{
core.User user = 1;
}
message UpdateUserRequest{
int32 id = 1;
core.User user = 2;
}
- 我们可以 expect/send 常见的消息类型,并依靠评论或其他反馈机制来鼓励消费者传递正确的值(我的原始实现演示了这一点)。我对这种方法的担忧是,我们最终不得不添加大量验证以确保他们提供的字段是 "correct".
我正在努力在网上找到许多其他人如何处理这类问题的例子。我提供的示例相当简单,但您可以想象在整个项目中我们都会遇到类似的问题。我希望看到的是某人在实践中完成的相当复杂的 gRPC 接口的示例,或者只是来自广泛使用它的人的反馈,以了解他们认为围绕接口设计的哪些模式最有效。
谢谢!
我想你要找的是Google's networked API Design Guide. Look at the Naming Conventions。特别是该页面上的方法名称部分。您将看到与您尝试做的非常相似的示例,这恰好很常见。
有关更具体的示例,请查看如何 etcd
has written their API here. Similar to your CreateUserRequest
and UpdateUserRequest
etcd
has MemberAddRequest
and MemberUpdateRequest
。
您还可以查看 UBER 的在线设计文档,其中提供了单独响应实施的理由,并提供了详细的版本控制和命名约定
我们正在构建一个新平台,其中 gRPC 提供消息传递层,最终将公开我们 API 预期支持的全套功能。
我们正在尝试确定命名接口的最佳模式,以避免重复的消息类型,不得不繁重地处理边缘情况
我们正在做的一个简单示例涉及创建、更新和检索用户。这是我们今天的服务的示例:
service Users {
rpc GetUser(UserRequest) returns (core.user.User) {}
rpc ListUsers(google.protobuf.Empty) returns (ListUsersResponse) {}
rpc CreateUser(core.user.User) returns (core.user.User) {}
rpc UpdateUser(core.user.User) returns (core.user.User) {}
}
message UserRequest {
string id = 1;
}
message ListUsersResponse{
repeated core.user.User users = 1;
}
GetUser 非常简单 - 它接受一个简单的 UserRequest 消息,其中包含一个 ID,returns 一个用户(来自我们的核心包 - 应用程序中的许多服务将接受一个用户消息作为输入,所以我们将其放在共享位置)。
我的问题专门针对 Create/Update 用户调用,因为目前尚不清楚最佳解决方案是什么。这两个函数略有不同,主要在于在一种情况下我们已经有一个用户,因此有一个 ID,而在另一种情况下我们正在创建一个新用户。在 Create 案例中,我们可能只有 User 上可能存在的可用字段的一个子集——但理想情况下,我们只需要在一个地方维护这个列表,因为它可能会变得相当大。我们可以:
- 对于每个调用,定义自定义 request/response 消息,并在其中嵌入任何常用消息。这看起来像下面的代码。我担心的是,我们最终基本上每次调用都有一个消息类型,从可维护性的角度来看,这最终可能会非常繁重。
代码
message CreateUserRequest{
core.User user = 1;
}
message UpdateUserRequest{
int32 id = 1;
core.User user = 2;
}
- 我们可以 expect/send 常见的消息类型,并依靠评论或其他反馈机制来鼓励消费者传递正确的值(我的原始实现演示了这一点)。我对这种方法的担忧是,我们最终不得不添加大量验证以确保他们提供的字段是 "correct".
我正在努力在网上找到许多其他人如何处理这类问题的例子。我提供的示例相当简单,但您可以想象在整个项目中我们都会遇到类似的问题。我希望看到的是某人在实践中完成的相当复杂的 gRPC 接口的示例,或者只是来自广泛使用它的人的反馈,以了解他们认为围绕接口设计的哪些模式最有效。
谢谢!
我想你要找的是Google's networked API Design Guide. Look at the Naming Conventions。特别是该页面上的方法名称部分。您将看到与您尝试做的非常相似的示例,这恰好很常见。
有关更具体的示例,请查看如何 etcd
has written their API here. Similar to your CreateUserRequest
and UpdateUserRequest
etcd
has MemberAddRequest
and MemberUpdateRequest
。
您还可以查看 UBER 的在线设计文档,其中提供了单独响应实施的理由,并提供了详细的版本控制和命名约定