Rejoiner 开源项目教程:从 gRPC 微服务构建统一 GraphQL Schema
引言:微服务架构下的数据查询挑战
在现代微服务架构中,每个服务通常使用不同的协议和数据格式,这给前端开发带来了巨大的挑战。你是否遇到过以下痛点:
- 需要向多个后端服务发起请求才能获取完整页面数据
- 不同服务返回的数据格式不一致,需要在前端进行复杂的数据转换
- 后端API变更导致前端需要大量重写代码
- 过度获取数据造成网络带宽浪费
Rejoiner 正是为解决这些问题而生!它是一个强大的开源工具,能够从 gRPC 微服务和其他 Protobuf 源生成统一的 GraphQL schema,让你像查询单个数据库一样查询整个微服务生态系统。
Rejoiner 核心概念解析
什么是 Rejoiner?
Rejoiner 是一个基于 Java 的库,主要功能包括:
- 🎯 统一 Schema 生成:从多个 gRPC 服务自动创建统一的 GraphQL schema
- 🔄 协议转换:将 GraphQL 查询转换为 gRPC 调用
- 🧩 灵活组合:支持 schema 的模块化定义和组合
- 🛠️ DSL 支持:提供丰富的 DSL 来修改生成的 schema
核心架构概览
快速开始:构建你的第一个 Rejoiner 应用
环境准备
首先添加 Maven 依赖:
<dependency>
<groupId>com.google.api.graphql</groupId>
<artifactId>rejoiner</artifactId>
<version>0.0.4</version>
</dependency>
或者使用 Gradle:
implementation 'com.google.api.graphql:rejoiner:0.0.4'
基础示例:Hello World
让我们从一个简单的示例开始,了解 Rejoiner 的基本工作原理。
1. 定义 Protobuf 服务
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
2. 创建 SchemaModule
import com.google.api.graphql.rejoiner.Query;
import com.google.api.graphql.rejoiner.SchemaModule;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
final class HelloWorldSchemaModule extends SchemaModule {
@Query("sayHello")
HelloReply sayHello(HelloRequest request, GreeterGrpc.GreeterBlockingStub client) {
return client.sayHello(request);
}
}
3. 配置 Guice 模块
import com.google.api.graphql.rejoiner.SchemaProviderModule;
import com.google.inject.AbstractModule;
public final class AppModule extends AbstractModule {
@Override
protected void configure() {
// 提供生成的 GraphQLSchema 实例
install(new SchemaProviderModule());
// 安装 schema 模块
install(new HelloWorldSchemaModule());
}
}
生成的 GraphQL Schema
执行上述配置后,Rejoiner 会自动生成以下 GraphQL schema:
type Query {
sayHello(name: String!): HelloReply
}
type HelloReply {
message: String
}
现在你可以使用标准的 GraphQL 查询:
query {
sayHello(name: "World") {
message
}
}
高级功能详解
1. 数据关联与连接
Rejoiner 最强大的功能之一是能够在不同类型之间建立关联。假设我们有两个服务:用户服务和待办事项服务。
final class TodoToUserSchemaModule extends SchemaModule {
@SchemaModification(addField = "creator", onType = Todo.class)
ListenableFuture<User> todoCreatorToUser(UserService userService, Todo todo) {
return userService.getUserByEmail(todo.getCreatorEmail());
}
}
这样就在 Todo 类型上添加了 creator 字段,实现了跨服务的关联查询。
2. Schema 修改与定制
Rejoiner 提供了灵活的 DSL 来修改生成的 schema:
final class TodoModificationsSchemaModule extends SchemaModule {
@SchemaModification
TypeModification removePrivateTodoData =
Type.find(Todo.getDescriptor()).removeField("privateTodoData");
}
3. 支持的类型系统
Rejoiner 支持多种返回类型:
| 返回类型 | 描述 | 示例 |
|---|---|---|
Message | 任何 Protobuf 消息 | HelloReply |
ImmutableList<? extends Message> | 消息列表 | ImmutableList<Todo> |
ListenableFuture<? extends Message> | 异步消息 | ListenableFuture<User> |
ListenableFuture<ImmutableList<? extends Message>> | 异步消息列表 | ListenableFuture<ImmutableList<Todo>> |
4. 字段掩码(FieldMask)支持
Rejoiner 能够基于 GraphQL 选择器自动生成 Proto FieldMask,优化数据传输:
实战案例:图书馆管理系统
让我们通过一个完整的示例来展示 Rejoiner 在实际项目中的应用。
系统架构
核心代码实现
1. 图书查询模块
final class BookQuerySchemaModule extends SchemaModule {
@Query("listBooks")
ListenableFuture<ListBooksResponse> listBooks(
ListBooksRequest request, BookService bookService) {
return bookService.listBooks(request);
}
@Query("getBook")
ListenableFuture<Book> getBook(GetBookRequest request, BookService bookService) {
return bookService.getBook(request);
}
}
2. 书架管理模块
final class ShelfMutationSchemaModule extends SchemaModule {
@Mutation("createShelf")
ListenableFuture<Shelf> createShelf(
CreateShelfRequest request, ShelfService shelfService,
@AuthenticatedUser String userId) {
request = request.toBuilder().setCreatorId(userId).build();
return shelfService.createShelf(request);
}
}
3. 数据关联模块
final class BookToShelfSchemaModule extends SchemaModule {
@SchemaModification(addField = "shelf", onType = Book.class)
ListenableFuture<Shelf> bookToShelf(ShelfService shelfService, Book book) {
return shelfService.getShelf(GetShelfRequest.newBuilder()
.setShelfId(book.getShelfId()).build());
}
}
生成的 GraphQL Schema
type Query {
listBooks(author: String, genre: String): ListBooksResponse
getBook(id: ID!): Book
}
type Mutation {
createShelf(name: String!, description: String): Shelf
}
type Book {
id: ID!
title: String!
author: String!
shelf: Shelf
}
type Shelf {
id: ID!
name: String!
description: String
books: [Book]
}
type ListBooksResponse {
books: [Book]
totalCount: Int
}
性能优化与最佳实践
1. 使用 DataLoader 进行批处理
Rejoiner 与 DataLoader 完美集成,可以自动批处理请求:
final class DataLoaderModule extends AbstractModule {
@Override
protected void configure() {
// 配置 DataLoader 注册表
bind(DataLoaderRegistryFactory.class).toInstance(
new DataLoaderRegistryFactory() {
@Override
public DataLoaderRegistry create(DataLoaderRegistry base) {
base.register("user",
new DataLoader<String, User>(userIds ->
userService.batchGetUsers(userIds)));
return base;
}
});
}
}
2. 字段级权限控制
final class AuthorizationSchemaModule extends SchemaModule {
@SchemaModification
TypeModification addAuthorization = Type.find(User.getDescriptor())
.addField(GraphQLFieldDefinition.newFieldDefinition()
.name("canEdit")
.type(Scalars.GraphQLBoolean)
.dataFetcher(environment -> {
User user = environment.getSource();
String currentUser = environment.getContext();
return currentUser.equals(user.getId());
})
.build());
}
3. 监控与日志
final class MonitoringSchemaModule extends SchemaModule {
@Query("listBooks")
ListenableFuture<ListBooksResponse> listBooks(
ListBooksRequest request, BookService bookService, MeterRegistry registry) {
Timer.Sample sample = Timer.start(registry);
return Futures.transform(
bookService.listBooks(request),
response -> {
sample.stop(registry.timer("graphql.query.listBooks"));
return response;
},
MoreExecutors.directExecutor());
}
}
常见问题与解决方案
Q1: 如何处理循环依赖?
解决方案: 使用 @SchemaModification 的延迟解析特性
final class CircularDependencySchemaModule extends SchemaModule {
@SchemaModification(addField = "relatedBooks", onType = Book.class)
ListenableFuture<List<Book>> getRelatedBooks(BookService service, Book book) {
// 这里的 Book 类型会在 schema 生成完成后才解析
return service.getRelatedBooks(book.getId());
}
}
Q2: 如何自定义标量类型?
解决方案: 扩展 ProtoScalars 类
public class CustomScalars extends ProtoScalars {
public static final GraphQLScalarType CUSTOM_DATE = GraphQLScalarType.newScalar()
.name("CustomDate")
.coercing(new Coercing() {
// 实现序列化和反序列化逻辑
})
.build();
}
Q3: 如何优化大量数据的查询性能?
解决方案: 结合 FieldMask 和分页
final class OptimizedQuerySchemaModule extends SchemaModule {
@Query("searchBooks")
ListenableFuture<SearchBooksResponse> searchBooks(
SearchBooksRequest request, BookService bookService) {
// 自动应用 FieldMask 优化
return bookService.searchBooks(request);
}
}
扩展与生态系统
1. Google Cloud 集成
Rejoiner 提供了与 Google Cloud 服务的预配置集成:
// 集成 Firestore
install(new FirestoreSchemaModule());
// 集成 Google Cloud Container
install(new ContainerSchemaModule());
2. Relay 支持
Rejoiner 支持 Relay 规范,包括全局 ID 和连接模式:
final class RelaySupportSchemaModule extends SchemaModule {
@Query("node")
ListenableFuture<RelayNode> node(@Arg("id") String id, NodeResolver resolver) {
return resolver.resolveNode(id);
}
}
3. 流式响应
支持基于 gRPC 流式的 GraphQL 响应:
final class StreamingSchemaModule extends SchemaModule {
@Query("streamUpdates")
Publisher<Update> streamUpdates(UpdateService updateService) {
return updateService.getUpdates();
}
}
总结与展望
Rejoiner 为微服务架构下的数据查询提供了优雅的解决方案。通过将多个 gRPC 服务统一为单个 GraphQL schema,它极大地简化了前端开发复杂度,同时保持了后端的灵活性和独立性。
核心优势总结:
| 特性 | 优势 | 适用场景 |
|---|---|---|
| 统一 Schema | 简化前端数据获取 | 多微服务环境 |
| 自动类型转换 | 减少胶水代码 | Protobuf 基础架构 |
| 灵活的组合性 | 模块化开发 | 大型项目 |
| 性能优化 | FieldMask 支持 | 大数据量场景 |
| 生态集成 | Google Cloud 支持 | 云原生应用 |
未来发展方向:
- 增强的流式处理能力
- 更丰富的监控和可观测性集成
- 对更多协议和数据源的支持
- 改进的开发工具和调试体验
无论你是正在构建新的微服务系统,还是希望优化现有的分布式架构,Rejoiner 都值得你深入探索。它不仅能提升开发效率,还能为你的系统带来更好的可维护性和扩展性。
开始你的 Rejoiner 之旅,体验统一数据查询的强大魅力!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



