GraphQL and microservices architectures are perfectly compatible. You can use GraphQL as a data layer for these microservices and combine data from all these services into one universal API. This way, you can break the core of your services into different microservices while still providing one endpoint that your clients can interact with. Before diving into GraphQL, let's explore what microservices are and why it can be helpful to add GraphQL.
Why Microservices?
Microservices are a perfect method to split your critical services into smaller services instead of one monolith APIs. Microservices are supposed to be independent services that have no direct connection with each other. Typically these services will all have their API, either REST or GraphQL, consumable from a client.
When using a data layer for your microservices, it serves as the single entry point for clients who consume your APIs. Through the data layer all the clients' requests are processed and forwarded to the correct microservice. Therefore the client doesn't have to call all these microservices individually. The approach of using a data layer as entry point for microservices is different from the BFF (Backend-For-Frontend) pattern, where other clients will interact with different endpoints.
Using a data layer for microservices is great because it lets you:
- Hide the microservice architecture from the client that interacts with it.
- By providing a single API it reduces the number of requests from your clients. Only the API of the data layer will be called.
- Simplifies the experience to consume your APIs.
You don't need to use GraphQL when you want to have an API data layer for your microservices, but it is a perfect match, as you'll learn in the next section.
GraphQL as Data Layer
There is a whole range of services and solutions available to create a data layer for your microservices. Most of these aren't made for GraphQL, while GraphQL is a great solution to build this API data layer. If you're not familiar with GraphQL yet, have a look here, where it's explained in detail. GraphQL can be used in both a microservices architecture and when you have a monolith.
Suppose you have three microservices in your architecture, services 1 to 3. Or a service for authentication, users and posts. In your client, you want to authenticate a user and show all the posts of this user. The diagram below shows what requests you need to make from a client in a microservices architecture without a data layer that services as an universal API.
The first service a client always needs to interact with is the authentication service. This service can get an OAuth token that is required to verify the requests sent to the other two services. Without an API data layer that bundles all the APIs of the microservices, you'll send and receive at least six network requests as microservices are not in direct contact with other microservices.
GraphQL can solve this problem for monolith APIs based on REST and for a microservice architecture when it's implemented as a data layer. With GraphQL, you can construct a query to retrieve both the user and posts of that user in one request. The API data layer will send the correct requests to the microservices for users and posts and stitch the data into one response.
The benefits of having a data layer, that you can find in the previous section, are all applicable to GraphQL. It hides the underlying microservice structure, can be used to create a single endpoint for all your microservices, and the experience for those consuming the APIs is simple. Besides this, GraphQL also comes with a rich ecosystem of tooling that you can use on your client(s) to interact with the API data layer through GraphQL. Combining all these requests can lead to increased response time, but this cost is often insignificant.
A drawback of the data layer pattern is the increased complexity it brings with it. The data layer needs to be built, maintained, and deployed when the underlying microservices are updated. Building this can be very time-consuming for smaller teams. But when you're using StepZen as this API data layer, you can save your team a lot of time. Let's find out how simple it is to implement a GraphQL data layer with StepZen.
Implementing a GraphQL Data Layer
With StepZen, you can implement such a data layer that serves as universal API for your microservices. Using something called directives, you can link any microservice that exposes a REST or GraphQL API. Your client can then query all your microservices in one API while also hiding your underlying microservice architecture.
If your microservices expose REST APIs, the @rest
directive will let you link them to a GraphQL operation. In comparison, you can use the @graphql
directive for GraphQL APIs. Most microservices are currently exposing REST APIs, so let's start there.
The first microservice you need to interact with is the authentication service, to which we need to send credentials to receive a token back. The StepZen configuration for this will look something like the following:
type Auth {
id: Int!
access_token: String!
}
type Query {
token: Auth
@rest(
endpoint: "https://my.api.com/api/token?grant_type=client_credentials&client_id={{.Get \"client_id\" }}&client_secret={{.Get \"client_secret\" }}"
configuration: "client_credentials"
)
}
This configuration adds a query to get the authentication details from the authentication server using a combination of a refresh token, client id, and client secret, which is a standard implementation for OAuth services. The users and posts services can use the returned access token for authentication.
You can use the @sequence
directive within a StepZen configuration file for the users' service. The @sequence
directive is a powerful one that lets you chain a set of operations, such as a query. From the user's service, you can get the token from this token
query and, subsequently, get the data for this user. Responses of the operations will are forwarded to the next operation in the chain, where you can use it as arguments as seen below:
type User {
id: Int!
name: String!
}
type Query {
user(id: Int!, access_token: String!): User
@rest(
endpoint: "https://my.api.com/api/users/$id/"
headers: [{ name: "Authorization", value: "Bearer $access_token" }]
)
getUser(id: Int!): User
@sequence(steps: [{ query: "token" }, { query: "user" }])
}
The getUser
operations will chain the token
and user
queries. The access token from the token
query response can be used as an argument in the user
query. For the posts service, the implementation is similar, as that service also needs the access token to verify the requests.
Even though this is a basic implementation for GraphQL as an API data layer for microservices, it validates how StepZen can play an essential role in creating this universal API. You can find a more in-depth step-by-step example of using the @sequence
directive in this post. Keep following our blog for an upcoming post with the full implementation of StepZen as a GraphQL API data layer.
Want to learn more about StepZen? Try it out here or ask any question on the Discord here.
Top comments (0)