-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: grpc guide #99
Merged
Merged
feat: grpc guide #99
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
e089656
feat: grpc guide
meskill 893037e
fix spelling
meskill edf6f11
fix prettier
meskill bfa7d30
Merge remote-tracking branch 'origin/develop' into grpc-guide
meskill f142324
update docs with link usage
meskill 9b0ed6b
fix prettier
meskill e0dd726
Merge remote-tracking branch 'origin/develop' into grpc-guide
meskill 940093a
chore: update docs a little bit
tusharmath 2729de9
chore: update operator doc
tusharmath 8c5c191
update grpc doc
tusharmath f3ce3c2
update grpc doc
tusharmath 3368d9a
chore: update styles
tusharmath 1035a0e
whitespace changes
tusharmath e3db33c
chore lint fixes
tusharmath a1e8e28
Merge branch 'develop' into grpc-guide
tusharmath File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
--- | ||
title: GraphQL on gRPC | ||
description: Building a GraphQL API on top of gRPC endpoints. | ||
--- | ||
|
||
In this guide, we will set up a simple gRPC service and use it inside Tailcall's config to fetch some of the data provided by the service. This way Tailcall can provide a single GraphQL interface wrapping any number of gRPC services. | ||
|
||
## What is gRPC? | ||
|
||
This guide assumes a basic familiarity with gRPC. It is a high-performance framework created by Google for remote procedure calls (RPCs). Its key features include: | ||
|
||
- **HTTP/2 Transport:** Ensures efficient and fast data transfer. | ||
- **Protocol Buffers (Protobuf):** Serves as a powerful interface description language. | ||
- **Efficiency:** Offers binary serialization, reduces latency, and supports data streaming. | ||
|
||
This combination of features makes gRPC ideal for microservices and distributed systems. If you need a more detailed understanding or are new to gRPC, we recommend visiting the [official gRPC website](https://grpc.io/) for comprehensive documentation and resources. | ||
|
||
Now, let's explore how gRPC can be integrated into our proxy gateway to enhance communication and data exchange in distributed systems. | ||
|
||
## gRPC upstream | ||
|
||
We need some gRPC service available to be able to execute requests from the Tailcall gateway. For pure example purposes, we will build some simple gRPC services. | ||
|
||
### Protobuf definition | ||
|
||
First, we need to create an example protobuf file that will define the structure of the data we want to transmit using gRPC. Here is the definition of `NewsService` that implements CRUD operations on news data that we'll put into the `news.proto` file. | ||
|
||
```protobuf | ||
syntax = "proto3"; | ||
|
||
import "google/protobuf/empty.proto"; | ||
|
||
package news; | ||
|
||
// Define message type for News with all its fields | ||
message News { | ||
int32 id = 1; | ||
string title = 2; | ||
string body = 3; | ||
string postImage = 4; | ||
} | ||
|
||
// Message with the id of a single news | ||
message NewsId { | ||
int32 id = 1; | ||
} | ||
|
||
// List of IDs of news to get multiple responses | ||
message MultipleNewsId { | ||
repeated NewsId ids = 1; | ||
} | ||
|
||
// List of all news | ||
message NewsList { | ||
repeated News news = 1; | ||
} | ||
|
||
// NewsService defines read and write operations for news items | ||
service NewsService { | ||
// GetAllNews retrieves all news items without any arguments | ||
rpc GetAllNews (google.protobuf.Empty) returns (NewsList) {} | ||
|
||
// GetNews fetches a single news item by its ID | ||
rpc GetNews (NewsId) returns (News) {} | ||
|
||
// GetMultipleNews retrieves multiple news items based on their IDs | ||
rpc GetMultipleNews (MultipleNewsId) returns (NewsList) {} | ||
} | ||
``` | ||
|
||
### Implement gRPC service | ||
|
||
Now having the protobuf file you can write a server that implements `NewsService` at any language you want that supports gRPC. Tailcall organization has a sample node.js service inside [this repo](https://github.com/tailcallhq/node-grpc) that you can pull to your local machine. To spin up the sample service run inside the repo and wait for logs about the service running. | ||
|
||
```sh | ||
npm i | ||
npm start | ||
``` | ||
|
||
## Tailcall config | ||
|
||
Now when we have a running gRPC service we're going to write Tailcall's config to make the integration. To do this we need to specify GraphQL types corresponding to gRPC types we have defined in the protobuf file. Let's create a new file `grpc.graphql` file with the following content: | ||
|
||
```graphql | ||
# The GraphQL representation for News message type | ||
type News { | ||
id: Int | ||
title: String | ||
body: String | ||
postImage: String | ||
} | ||
|
||
# Input type that is used to fetch news data by its id | ||
input NewsInput { | ||
id: Int | ||
} | ||
|
||
# Resolves multiple news entries | ||
type NewsData { | ||
news: [News]! | ||
} | ||
``` | ||
|
||
Now when we have corresponding types in schema we want to define GraphQL Query that specifies the operation we can execute onto news. We can extend our config with the next Query: | ||
|
||
```graphql | ||
type Query { | ||
# Get all news i.e. NewsService.GetAllNews | ||
news: NewsData! | ||
# Get single news by id i.e. NewsService.GetNews | ||
newsById(news: NewsInput!): News! | ||
} | ||
``` | ||
|
||
Also, let's specify options for Tailcall's ingress and egress at the beginning of the config using [`@server`](../operators/server.md) and [`@upstream`](../operators/upstream.md) operators. | ||
|
||
```graphql | ||
schema @server(port: 8000, graphiql: true) @upstream(baseURL: "http://localhost:50051", httpCache: true) { | ||
query: Query | ||
} | ||
``` | ||
|
||
To specify the protobuf file to read types from, use the `@link` operator with the type `Protobuf` on the schema. `id` is an important part of the definition that will be used by the `@grpc` operator later | ||
|
||
```graphql | ||
schema @link(id: "news", src: "./news.proto", type: Protobuf) | ||
``` | ||
|
||
Now you can connect GraphQL types to gRPC types. To do this you may want to explore more about [`@grpc` operator](../operators/grpc.md). Its usage is pretty straightforward and requires you to specify the path to a method that should be used to make a call. The method name will start with the package name, followed by the service name and the method name, all separated by the `.` symbol. | ||
|
||
If you need to provide any input to the gRPC method call you can specify it with the `body` option that allows you to specify a Mustache template and therefore it could use any input data like `args` and `value` to construct the body request. The body value is specified in the JSON format if you need to create the input manually and cannot use `args` input. | ||
|
||
```graphql | ||
type Query { | ||
news: NewsData! @grpc(method: "news.news.NewsService.GetAllNews") | ||
newsById(news: NewsInput!): News! @grpc(service: "news.news.NewsService.GetNews", body: "{{args.news}}") | ||
} | ||
``` | ||
|
||
Wrapping up the whole result config that may look like this: | ||
|
||
```graphql | ||
# file: app.graphql | ||
|
||
schema | ||
@server(port: 8000, graphiql: true) | ||
@upstream(baseURL: "http://localhost:50051", httpCache: true) | ||
@link(id: "news", src: "./news.proto", type: Protobuf) { | ||
query: Query | ||
} | ||
|
||
type Query { | ||
news: NewsData! @grpc(method: "news.news.NewsService.GetAllNews") | ||
newsById(news: NewsInput!): News! @grpc(method: "news.news.NewsService.GetNews", body: "{{args.news}}") | ||
} | ||
|
||
type News { | ||
id: Int | ||
title: String | ||
body: String | ||
postImage: String | ||
} | ||
|
||
input NewsInput { | ||
id: Int | ||
} | ||
|
||
type NewsData { | ||
news: [News]! | ||
} | ||
``` | ||
|
||
Start the server by pointing it to the config. | ||
|
||
``` | ||
tailcall start ./app.graphql | ||
``` | ||
|
||
And now you can go to the page `http://127.0.0.1:8000/graphql` and execute some GraphQL queries e.g.: | ||
|
||
```graphql | ||
{ | ||
news { | ||
news { | ||
id | ||
title | ||
body | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Or | ||
|
||
```graphql | ||
{ | ||
newsById(news: {id: 2}) { | ||
id | ||
title | ||
body | ||
} | ||
} | ||
``` | ||
|
||
## Batching | ||
|
||
Another important feature of the `@grpc` operator is that it allows you to implement request batching for remote data almost effortlessly as soon as you have gRPC methods that resolve multiple responses for multiple inputs in a single request. | ||
|
||
In our protobuf example file, we have a method called `GetMultipleNews` that we can use. To enable batching we need to enable [`@upstream.batch` option](../operators/upstream.md#batch) first and specify `groupBy` option for the `@grpc` operator. | ||
|
||
```graphql | ||
schema | ||
@server(port: 8000, graphiql: true) | ||
@upstream(baseURL: "http://localhost:50051", httpCache: true, batch: {delay: 10}) | ||
@link(id: "news", src: "./news.proto", type: Protobuf) { | ||
query: Query | ||
} | ||
|
||
type Query { | ||
newsById(news: NewsInput!): News! | ||
@grpc( | ||
method: "news.NewsService.GetNews" | ||
body: "{{args.news}}" | ||
# highlight-next-line | ||
groupBy: ["news", "id"] | ||
) | ||
} | ||
``` | ||
|
||
Restart the Tailcall server and make the query with multiple news separately, e.g.: | ||
|
||
```graphql | ||
{ | ||
n1: newsById(news: {id: 1}) { | ||
id | ||
title | ||
body | ||
} | ||
n2: newsById(news: {id: 2}) { | ||
id | ||
title | ||
body | ||
} | ||
} | ||
``` | ||
|
||
Those 2 requests will be executed inside a single request to the gRPC method `GetMultipleNews` | ||
|
||
## Conclusion | ||
|
||
Well done on integrating a gRPC service with the Tailcall gateway! This tutorial has demonstrated the straightforward and efficient process, showcasing Tailcall's compatibility with advanced communication protocols like gRPC. | ||
|
||
You can find this working example and test it by yourself by the next links: | ||
|
||
- [node-grpc](https://github.com/tailcallhq/node-grpc) - example implementation for gRPC service in node.js | ||
- [gRPC example config](https://github.com/tailcallhq/tailcall/blob/main/examples/grpc.graphql) - Tailcall's config to integrate with gRPC service above | ||
|
||
### Key Takeaways | ||
|
||
- **Simplicity of Integration:** The integration of gRPC with Tailcall seamlessly enhances the overall capability of your system to handle high-performance and efficient data composition. | ||
- **Scalability and Performance:** By leveraging the power of gRPC along with Tailcall, we've laid a foundation for building scalable and high-performing distributed systems. | ||
|
||
### Next Steps | ||
|
||
With the basics in place, we encourage you to explore further: | ||
|
||
- **Dive Deeper:** Tailcall gateway offers a lot of other features and configurations that you can utilize. Dive deeper into our documentation to explore more advanced settings and customization options. | ||
- **Explore Other Guides:** Our documentation includes a variety of guides and tutorials that can help you leverage the full potential of Tailcall in different scenarios. Whether it's adding security layers, load balancing, or detailed logging, there's a lot more to explore. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could change it to differentiate between the link id and the package name when setting the
@grpc
directive