Rejoiner GraphQL

The architecture of modern microservices often suffers from a fragmentation problem where clients must orchestrate multiple network calls to disparate services to fulfill a single user interface requirement. Rejoiner emerges as a sophisticated solution to this challenge, functioning as a tool that creates a uniform GraphQL schema from a collection of microservices. By leveraging the strengths of both GraphQL and gRPC, Rejoiner allows developers to define and compose a GraphQL schema as shared components with high flexibility. This capability is central to reducing the friction associated with data aggregation, enabling the transformation of complex backend infrastructures into a streamlined, queryable API.

At its core, Rejoiner bridges the gap between Protocol Buffers (Proto) and GraphQL. It possesses the ability to generate GraphQL types directly from Proto definitions, ensuring that the rigorous typing of protobufs is preserved and translated into the GraphQL ecosystem. This is not merely a mapping exercise; Rejoiner populates request Proto messages based on the parameters provided in a GraphQL query, effectively automating the translation layer between the client's high-level request and the backend's low-level RPC calls. Furthermore, it provides a Domain Specific Language (DSL) specifically designed to modify the generated schema, allowing architects to prune or extend the API surface without rewriting the underlying Proto files.

The system employs a mechanism to join diverse data sources by annotating methods that fetch data, creating a cohesive graph where nodes can be resolved across different service boundaries. A critical technical achievement of Rejoiner is the creation of Proto FieldMasks based on GraphQL selectors. This ensures that the backend only processes and returns the specific fields requested by the client, preventing the common "over-fetching" problem associated with traditional REST APIs and optimizing network bandwidth between the gateway and the microservices.

Schema Generation and Module Architecture

The foundational unit of schema construction in Rejoiner is the SchemaModule. This component is implemented as a Guice module, which serves as the engine for generating specific segments of the GraphQL schema. Upon instantiation, the SchemaModule scans for methods and fields decorated with Rejoiner annotations. The logic applied during this scan is deterministic: the module analyzes the parameters and the return type of the annotated methods to synthesize the corresponding GraphQL schema.

For instance, in a scenario involving a todo list application, a developer might implement a TodoQuerySchemaModule extending SchemaModule. By applying the @Query annotation to a method, such as listTodo, the developer signals to Rejoiner that this method should be exposed as a GraphQL query.

java final class TodoQuerySchemaModule extends SchemaModule { @Query("listTodo") ListenableFuture<ListTodoResponse> listTodo(ListTodoRequest request, TodoClient todoClient) { return todoClient.listTodo(request); } }

In the implementation above, the ListTodoRequest is a protobuf message. Rejoiner recognizes this type and automatically uses it as a parameter in the generated GraphQL query. This eliminates the need for manual mapping between GraphQL input types and Proto request messages, significantly increasing development velocity.

Integration with Google APIs and GaxSchemaModule

Rejoiner is particularly potent when integrated with Google APIs, many of which are accessible via gRPC. Since the proto definitions for these APIs are publicly available on GitHub, Rejoiner can be utilized to construct a GraphQL gateway that interfaces directly with Google's infrastructure. To optimize performance, this gateway server can be hosted on the Google Cloud Platform, ensuring that all round-trips occur within the high-speed Google network, thereby minimizing latency.

Standard queries are implemented using the @Query annotation, as seen in the following example where a document is retrieved from Firestore:

java @Query("getDocument") ListenableFuture<Document> getDocument( GetDocumentRequest request, FirestoreClient client) { return apiFutureToListenableFuture(client.getDocumentCallable().futureCall(request)); }

To further streamline the integration of Google's API Extensions (GAX) libraries, Rejoiner provides the GaxSchemaModule. This specialized module allows for the automatic generation of queries and mutations based on the RPCs defined within the services. This is achieved by utilizing the @Namespace annotation and overriding the configureSchema method.

java @Namespace("firestore") public final class FirestoreSchemaModule extends GaxSchemaModule { @Override protected void configureSchema() { addQueryList( serviceToFields(FirestoreClient.class, ImmutableList.of("getDocument", "listDocuments"))); addMutationList( serviceToFields( FirestoreClient.class, ImmutableList.of("createDocument", "updateDocument", "deleteDocument"))); } }

This programmatic approach to schema definition allows developers to selectively expose specific RPC methods (like createDocument or listDocuments) as GraphQL mutations and queries. Additionally, the architecture allows for the injection of extra fields into types already defined within these APIs, providing a way to augment the data model without altering the original Proto definitions.

Advanced Schema Modification and Kotlin Interoperability

While Rejoiner provides robust automatic generation, there are cases where the resulting schema must be precisely tuned. One such requirement is the removal of specific GraphQL fields. This is achieved using the @SchemaModification annotation. According to technical observations, Rejoiner treats these annotations differently depending on whether they are attached to a function or a variable. For the removal of a field to be successful, the annotation must be applied to a variable.

In a Kotlin environment, this presents a specific challenge because Kotlin automatically generates getter functions for properties. If a developer attempts to use @SchemaModification on a Kotlin property, the library may not process it as intended. To resolve this, the @JvmField annotation can be used to prevent the creation of the getter, ensuring the annotation is applied directly to the field in the resulting Java bytecode.

An example of attempting to remove a field from a RegisterUserRequest is as follows:

kotlin @SchemaModification val removeRegisterUserRemoteAddr = Type .find(RegisterUserRequest.getDescriptor()) .removeField("remoteAddr")!!

Without @JvmField, the Kotlin compiler might generate a synthetic method:

java /** @deprecated */ // $FF: synthetic method @SchemaModification public static void removeRegisterUserRemoteAddr$annotations() { }

This synthetic transformation can lead to the @SchemaModification failing to apply correctly. Once the property is correctly identified as a field, Rejoiner can successfully excise the remoteAddr field from the generated GraphQL schema, allowing for precise control over the public API surface.

Technical Capabilities and Feature Set

Rejoiner is an evolving framework with a wide array of capabilities designed to maximize the efficiency of gRPC-to-GraphQL translation.

  • Lossless scalar types: Rejoiner ensures that proto scalar types remain lossless when transitioning end-to-end via gRPC.
  • Relay support: The framework provides built-in support for Relay, a GraphQL client developed by Facebook, which is essential for complex pagination and caching strategies.
  • GraphQL Stream: Rejoiner implements streaming capabilities based on gRPC streaming, allowing for real-time data delivery that surpasses the capabilities of standard request-response cycles.
  • Unified Schema Creation: It enables the creation of a single, uniform GraphQL schema that abstracts multiple microservices, reducing the need for clients to manage multiple endpoints.
  • Proto-based Type Generation: GraphQL types are derived directly from Proto definitions, ensuring a single source of truth for data structures.
  • Request Population: The framework automatically populates Proto requests based on the parameters provided in the GraphQL query.
  • DSL for Schema Modification: A dedicated DSL is available for developers to refine and modify the generated schema.
  • Data Source Joining: Through the use of annotations on data-fetching methods, Rejoiner can join data from disparate sources into a single response.
  • FieldMask Generation: By analyzing GraphQL selectors, Rejoiner creates Proto FieldMasks, which informs the backend exactly which fields to return.

Comparative Analysis: GraphQL vs. REST and JSON

The adoption of Rejoiner and GraphQL over traditional RESTful architectures is driven by several technical and operational advantages.

Data Fetching Efficiency

REST APIs frequently suffer from two primary inefficiencies: over-fetching and under-fetching. Over-fetching occurs when a REST endpoint returns a full JSON object containing fields the client does not need, wasting bandwidth and processing power. Under-fetching occurs when a single REST endpoint does not provide enough data, forcing the client to make multiple subsequent requests to different endpoints to complete a UI component.

GraphQL solves this by allowing the client to define the exact data shape required. The response payload mirrors the request query precisely. For example, a request for a user's ID, name, and city will return exactly those fields, and nothing more.

Feature REST GraphQL (via Rejoiner)
Data Shape Determined by Server Determined by Client
Network Calls Multiple for complex data Single request for aggregated data
Type Safety Often loosely defined Strongly typed (Proto-based)
Bandwidth Potential for over-fetching Optimized via FieldMasks
Versioning Often requires /v1/, /v2/ Evolvable schema without breaking changes

Security and Validation

GraphQL offers enhanced security profiles compared to JSON RESTful APIs. One primary reason is the strongly typed schema. This allows for rigorous data validation at the gateway level, effectively preventing common vulnerabilities such as SQL injections that can occur when a wrong type is passed to a backend service.

Additionally, RESTful GET requests often rely on query strings within the URL. Because URLs are frequently logged by proxies and servers, sensitive data contained in these query strings can be exposed. GraphQL typically utilizes POST requests for its queries, ensuring that the request body—and the sensitive data within it—is not exposed in server logs.

Developer Experience (DX) and Velocity

The developer experience is significantly improved when using GraphQL, particularly in the context of UI development. Engineers can declare the data they need directly alongside their UI components. This reduces the friction of jumping between UI templates, client-side logic, and style sheets. When a new product prototype is needed, developers can leverage existing GraphQL APIs to fetch data from various sources without having to build new REST endpoints or wait for backend engineers to implement specific views.

Static validation tools, such as eslint-plugin-graphql, allow engineers to test GraphQL queries against the schema. This provides an immediate warning if a backend change breaks a client query, increasing confidence during refactoring and reducing the likelihood of production failures.

Implementation Summary and Architecture

Rejoiner functions as an abstraction layer that improves development velocity and decreases maintenance overhead. By acting as a data aggregator, it allows companies to expose their internal gRPC services as a public-facing GraphQL API. This is particularly beneficial for large-scale organizations like IBM, PayPal, and GitHub, who have adopted GraphQL to handle the complexity of their data ecosystems.

The use of a language-agnostic schema allows for the definition of types, such as an Address type, using built-in scalar types like String and Int.

graphql type Address { city: String! country: String! zip: Int }

In this structure, the ! indicates a non-nullable field, ensuring that the client always receives a value for the city and country, further strengthening the contract between the frontend and backend.

Conclusion

Rejoiner represents a paradigm shift in how microservices are exposed to consumers. By automating the bridge between gRPC and GraphQL, it eliminates the manual boilerplate associated with API gateways while preserving the type safety and performance of Protocol Buffers. The ability to generate schemas from Proto definitions, combined with the flexibility of the SchemaModule and GaxSchemaModule, allows for an architecture that is both scalable and maintainable.

The integration of FieldMasks ensures that the efficiency of gRPC is not lost when exposed as GraphQL, while the support for Relay and gRPC streaming extends the utility of the framework to high-performance, real-time applications. For organizations transitioning away from clunky REST APIs, Rejoiner provides a path toward an aggregator layer that increases developer happiness and reduces the time-to-market for new features. The shift toward strongly typed, client-driven data fetching is not merely a trend but a technical necessity for the modern, data-intensive web.

Sources

  1. Google Rejoiner GitHub
  2. Rejoiner Google APIs Documentation
  3. Kotlinlang Discussion on Java Annotations
  4. Webapplog GraphQL Analysis

Related Posts