{{announcement.body}}
{{announcement.title}}

Create a Single Microservices Endpoint With GraphQL, Kotlin, and Micronaut

DZone 's Guide to

Create a Single Microservices Endpoint With GraphQL, Kotlin, and Micronaut

In today’s article, you will see an example on how to implement a GraphQL API on the JVM, particularly using Kotlin language and Micronaut framework.

· Microservices Zone ·
Free Resource

GraphQL is a query language for APIs that was developed by Facebook. In today’s article, you will see an example on how to implement a GraphQL API on the JVM, particularly using Kotlin language and Micronaut framework. Most of the examples below are reusable on other Java/Kotlin frameworks. Then, we will consider how to combine multiple GraphQL services into a single data graph to provide a unified interface for querying all of your backing data sources. This is implemented using Apollo Server and Apollo Federation. Finally, the following architecture will be obtained:

architecture

Each component of the architecture answers several questions that may arise when implementing GraphQL API. The domain model includes data about planets in the Solar System and their satellites.

Prerequisites

You might also like: Microservices in Practice: From Architecture to Deployment

Planet Service

The main dependencies related to GraphQL are given below:

Kotlin

GraphQL dependencies (source code)

The first provides integration between GraphQL Java and Micronaut, i.e., defines common beans such as GraphQL controller and others. GraphQL controller is just a regular controller in terms of Spring and Micronaut; it processes GET and POST requests to /graphql path. The second dependency is a library that adds support of Apollo Federation to applications that are using GraphQL Java.

GraphQL schema is written in Schema Definition Language (SDL) and lives in the service’s resources:

Java

Schema of Planet service (source code)

Planet.id field has type ID which is one of the 5 default scalar types. GraphQL Java adds several scalar types and provides the ability to write custom scalars. The presence of the exclamation mark after type name means that a field cannot be null and vice versa (you may notice the similarities between Kotlin and GraphQL in their ability to define nullable types). @directive s will be discussed later. To learn more about GraphQL schemas and their syntax see, for example, an official guide. If you use IntelliJ IDEA, you can install JS GraphQL plugin to work with schemas.

There are two approaches to GraphQL API development:

  • schema-first

    First design the schema (and therefore the API), then implement it in code

  • code-first

    Schema is generated automatically based on code

Both have their pros and cons; you can find more on the topic in this blog post. For this project (and for the article) I decided to use the schema-first way. You can find a tool for either approach on this page.

There is an option in Micronaut application’s config which enables GraphQL IDE — GraphiQL — what allows making GraphQL requests from a browser:

YAML

Switching on GraphiQL (source code)

Main class doesn’t contain anything unusual:

Kotlin

Main class (source code)

GraphQL bean is defined in this way:

Kotlin

GraphQL configuration (source code)

FederatedSchemaBuilder class makes a GraphQL application adapted to the Apollo Federation specification. If you are not going to combine multiple GraphQL Java services into a single graph, then a configuration will be different (see this tutorial).

RuntimeWiring object is a specification of data fetchers, type resolvers and custom scalars that are needed to wire together a functional GraphQLSchema; it is defined as follows:

Kotlin

Creating a RuntimeWiring object (source code)

For the root type Query (other root types are Mutation and Subscription), for instance, planets field is defined in the schema, therefore it is needed to provide a DataFetcher for it:

Kotlin

PlanetsDataFetcher (source code)

Here the env input parameter contains all the context that is needed to fetch a value. The method just gets all the items from a repository and converts them into DTO. Conversion is performed in this way:

Kotlin

PlanetConverter (source code)

GenericConverter is just a common interface for Entity → DTO transformation. Let’s suppose details is a heavy field, then we should return it only if it was requested. So in the snippet above only simple properties are converted and for details object only id field is filled. Earlier, in the definition of the RuntimeWiring object, DataFetcher for details field of Planet type was specified; it is defined as follows (it needs to know a value of details.id field):

Kotlin

DetailsDataFetcher (source code)

Here you see that it is possible to return CompletableFuture instead of an actual object. More simple would be just to get Details entity from DetailsService, but this would be a naive implementation that leads to the N+1 problem: if we would make GraphQL request say:

Java
 




xxxxxxxxxx
1


 
1
{
2
  planets {
3
    name
4
    details {
5
      meanRadius
6
    }
7
  }
8
}


Example of possible resource-consuming GraphQL request

then for each planet’s details field separate SQL call would be made. To prevent this, java-dataloader library is used; BatchLoader and DataLoaderRegistry beans should be defined:

Kotlin

BatchLoader and DataLoaderRegistry (source code)

BatchLoader makes it possible to get a bunch of Details at once. Therefore, only two SQL calls will be performed instead of N+1 requests. You can make sure of it by making the GraphQL request above and seeing at the application’s log where actual SQL queries will be shown. BatchLoader is stateless, so it may be a singleton object. DataLoader simply points to the BatchLoader; it is stateful, therefore, it should be created per request as well as DataLoaderRegistry. Depending on your business requirements you may need to share data across web requests which is also possible. More on batching and caching is in the GraphQL Java documentation.

Details in GraphQL schema is defined as an interface, therefore, at the first part of the RuntimeWiring object’s definition TypeResolver object is created to specify to what concrete GraphQL type what DTO should be resolved:

Kotlin

TypeResolver (source code)

It is also needed to specify Java runtime values for values of Type enum defined in the schema (it seems like this is necessary only for using an enum in mutations):

Kotlin

Enum processing (source code)

After launching a service, you can navigate to http://localhost:8082/graphiql and see GraphiQL IDE, in which it is possible to make any requests defined in the schema; the IDE is divided into three parts: request (query/mutation/subscription), response, and documentation:

graphiql

There are other GraphQL IDEs, for example, GraphQL Playground and Altair (which is available as a desktop application, browser extension, and web page). The latter I will use further:

altair

On the documentation part, there are two additional queries besides defined in the schema: _service and _entities. They are created by the library that adapts the application to the Apollo Federation specification; this question will be discussed later.

If you navigate to the Planet type, you will see its definition:

Both the comment for type field and the @deprecated directive for isRotatingAroundSun field are specified in the schema.

There is one mutation defined in the schema:

Java
 




xxxxxxxxxx
1


 
1
type Mutation {
2
    createPlanet(name: String!, type: Type!, details: DetailsInput!): Planet!
3
}


Mutation (source code)

As a query, it also allows requesting fields of a returning type. Note that if you need to pass an object as an input parameter, input type should be used instead of queries' type:

Java
 




xxxxxxxxxx
1
10


 
1
input DetailsInput {
2
    meanRadius: Float!
3
    mass: MassInput!
4
    population: Float
5
}
6
 
          
7
input MassInput {
8
    number: Float!
9
    tenPower: Int!
10
}


Input type

As for a query, DataFetcher should be defined for a mutation:

Kotlin

DataFetcher for the mutation (source code)

Let’s suppose that someone wants to be notified of a planet adding event. For such a purpose subscription can be used:

Java
 




xxxxxxxxxx
1


 
1
type Subscription {
2
    latestPlanet: Planet!
3
}


Subscription (source code)

The subscription’s DataFetcher returns Publisher:

Kotlin

DataFetcher for subscription (source code)

To test the mutation and the subscription open two tabs of any GraphQL IDE or two different IDEs; in the first subscribe as follows (it may be required to set subscription URL ws://localhost:8082/graphql-ws):

Java
 




xxxxxxxxxx
1


 
1
subscription {
2
  latestPlanet {
3
    name
4
    type
5
  }
6
}


Request for subscription

In the second perform mutation like this:

Java
 




xxxxxxxxxx
1


 
1
mutation {
2
  createPlanet(
3
    name: "Pluto"
4
    type: DWARF_PLANET
5
    details: { meanRadius: 50.0, mass: { number: 0.0146, tenPower: 24 } }
6
  ) {
7
    id
8
  }
9
}


Request for mutation

The subscribed client will be notified of a planet creation:

mutation subscription

Subscriptions in Micronaut are enabled by using the following option:

YAML

Switching on GraphQL over WebSocket (source code)

Another example of subscriptions in Micronaut is a chat application. For more information on subscriptions, see GraphQL Java documentation.

Tests for queries and mutations can be written like this:

Kotlin

Query test (source code)

If a part of a query can be reused in another query, you can use fragments:

Kotlin

Query test using a fragment (source code)

To use variables, you can write tests in this way:

Kotlin

Query test using a fragment and variables (source code)

This approach looks a little strange because in Kotlin raw strings, or string templates, you can’t escape a symbol, so to represent $ you need to write ${'$'}.

Injected GraphQLClient in the snippets above is just a self-written class (it is framework-agnostic by using OkHttp library). There are other Java GraphQL clients, for example, Apollo GraphQL Client for Android and the JVM, but I haven’t used them yet.

Data of all 3 services are stored in H2 in-memory databases and are accessed using Hibernate ORM provided by the micronaut-data-hibernate-jpa library. The databases are initialized with data at the applications' startup.

Auth Service

GraphQL doesn’t provide means for authentication and authorization. For this project, I decided to use JWT. Auth service is only responsible for JWT token issue and validation and contains just one query and one mutation:

Java
 




xxxxxxxxxx
1
17


 
1
type Query {
2
    validateToken(token: String!): Boolean!
3
}
4
 
          
5
type Mutation {
6
    signIn(data: SignInData!): SignInResponse!
7
}
8
 
          
9
input SignInData {
10
    username: String!
11
    password: String!
12
}
13
 
          
14
type SignInResponse {
15
    username: String!
16
    token: String!
17
}


Schema of Auth service (source code)

To get a JWT you need to perform in a GraphQL IDE the following mutation (Auth service URL is http://localhost:8081/graphql):

Java
 




xxxxxxxxxx
1


 
1
mutation {
2
  signIn(data: {username: "john_doe", password: "password"}) {
3
    token
4
  }
5
}


Getting JWT

Including the Authorization header to further requests (it is possible in Altair and GraphQL Playground IDEs) allows access to protected resources; this will be shown in the next section. The header value should be specified as Bearer $JWT.

Working with JWT is done using the micronaut-security-jwt library.

Satellite Service

The service’s schema looks like that:

Java
 




xxxxxxxxxx
1
25


 
1
type Query {
2
    satellites: [Satellite!]!
3
    satellite(id: ID!): Satellite
4
    satelliteByName(name: String!): Satellite
5
}
6
 
          
7
type Satellite {
8
    id: ID!
9
    name: String!
10
    lifeExists: LifeExists!
11
    firstSpacecraftLandingDate: Date
12
}
13
 
          
14
type Planet @key(fields: "id") @extends {
15
    id: ID! @external
16
    satellites: [Satellite!]!
17
}
18
 
          
19
enum LifeExists {
20
    YES,
21
    OPEN_QUESTION,
22
    NO_DATA
23
}
24
 
          
25
scalar Date


Schema of Satellite service (source code)

Say in the Satellite type field lifeExists should be protected. Many frameworks offer security approach in which you just need to specify routes and different security policies for them, but such an approach can’t be used to protect some specific GraphQL query/mutation/subscription or types' fields, because all requests are sent to /graphql endpoint. Only you can do is to configure a couple of GraphQL-specific endpoints, for example, as follows (requests to any other endpoints will be disallowed):

YAML

Security configuration (source code)

It is not recommended putting authorization logic into DataFetcher to not make an application’s logic brittle:

Kotlin

LifeExistsDataFetcher (source code)

Protection of a field can be done using a framework’s means and custom logic:

Kotlin

SatelliteService (source code)

The following request can only be successful if you will specify the Authorization header with received JWT (see the previous section):

Java
 




xxxxxxxxxx
1


 
1
{
2
  satellite(id: "1") {
3
    name
4
    lifeExists
5
  }
6
}


Request for protected field

The service validates token automatically using the framework. The secret is stored in the configuration file (in the Base64 form):

YAML

JWT configuration (source code)

In real life, the secret can be stored in an environment variable to share it with several services. Also, instead of the sharing validation of the JWT can be used (validateToken method was shown in the previous section).

Such scalar types as Date, DateTime, and some others can be added to GraphQL Java service using graphql-java-extended-scalars library (com.graphql-java:graphql-java-extended-scalars:$graphqlJavaExtendedScalarsVersion in build script). Then the required types should be declared in the schema (scalar Date) and registered:

Kotlin

Registration of an additional scalar type (source code)

Then they can be used as others:

Java
 




xxxxxxxxxx
1


 
1
{
2
  satelliteByName(name: "Moon") {
3
    firstSpacecraftLandingDate
4
  }
5
}


Request

JSON

Response

There are different security threats to your GraphQL API (see this checklist to learn more). For example, if the domain model of the described project was a bit more complex, the following request would be possible:

Java
 




xxxxxxxxxx
1
15


 
1
{
2
  planet(id: "1") {
3
    star {
4
      planets {
5
        star {
6
          planets {
7
            star {
8
              ... # more deep nesting!
9
            }
10
          }
11
        }
12
      }
13
    }
14
  }
15
}


Example of expensive query

To make such a request invalid, MaxQueryDepthInstrumentation should be used. To restrict query complexity, MaxQueryComplexityInstrumentation can be specified; it optionally takes FieldComplexityCalculator in which it is possible to define fine-grained calculation criteria. The next code snippet shows an example on how to apply multiple instrumentations (FieldComplexityCalculator there calculates complexity like default one based on the assumption that every field’s cost is 1):

Kotlin

Setup an instrumentation (source code)

Note that if you specify MaxQueryDepthInstrumentation and/or MaxQueryComplexityInstrumentation, then documentation of a service may stop showing in your GraphQL IDE. This is because the IDE tries to perform IntrospectionQuery which has considerable size and complexity (discussion on this is on GitHub). FederatedTracingInstrumentation is used to make your server generate performance traces and return them along with responses to Apollo Gateway (which then can send them to Apollo Graph Manager; it seems like a subscription is needed to use this function). More on instrumentation see in GraphQL Java documentation.

There is an ability to customize requests; it differs in different frameworks. In Micronaut, for example, it is done in this way:

Kotlin

Example of GraphQLExecutionInputCustomizer (source code)

This customizer provides an ability to FederatedTracingInstrumentation to check whether a request has come from Apollo Server and whether to return performance traces.

To have an ability to handle all exceptions during data fetching in one place and to define custom exception handling logic you need to provide a bean as follows:

Kotlin

Custom exception handler (source code)

The main purpose of the service is to demonstrate how the distributed GraphQL entity (Planet) can be resolved in two services and then accessed through Apollo Server. Planet type was earlier defined in the Planet service in this way:

Java
 




xxxxxxxxxx
1


 
1
type Planet @key(fields: "id") {
2
    id: ID!
3
    name: String!
4
    # from an astronomical point of view
5
    type: Type!
6
    isRotatingAroundSun: Boolean! @deprecated(reason: "Now it is not in doubt. Do not use this field")
7
    details: Details!
8
}


Definition of Planet type in Planet service (source code)

Satellite service adds the satellites field (which contains only non-nullable elements and is non-nullable by itself as follows from its declaration) to the Planet entity:

Java
 




xxxxxxxxxx
1
11


 
1
type Satellite {
2
    id: ID!
3
    name: String!
4
    lifeExists: LifeExists!
5
    firstSpacecraftLandingDate: Date
6
}
7
 
          
8
type Planet @key(fields: "id") @extends {
9
    id: ID! @external
10
    satellites: [Satellite!]!
11
}


Extension of Planet type in Satellite service (source code)

In Apollo Federation terms Planet is an entity — a type that can be referenced by another service (by Satellite service in this case which defines a stub for Planet type). Declaring an entity is done by adding a @key directive to the type definition. This directive tells other services which fields to use to uniquely identify a particular instance of the type. The @extends annotation declares that Planet is an entity defined elsewhere (in Planet service in this case). More on Apollo Federation core concepts see in Apollo documentation.

There are two libraries for supporting Apollo Federation; both are built on top of GraphQL Java but didn’t fit the project:

  • GraphQL Kotlin

    This is a set of libraries written in Kotlin; it uses the code-first approach without a necessity to define a schema. The project contains graphql-kotlin-federation module, but it seems like you need to use this library in conjunction with other libraries of the project.

  • Apollo Federation on the JVM

    The project’s development is not very active and the API could be improved.

So I decided to refactor the second library to enhance the API and make it more convenient. The project is on GitHub.

To specify how a particular instance of the Planet entity should be fetched FederatedEntityResolver object is defined (basically, it points what should be filled in the Planet.satellites field); then the resolver is passed to FederatedSchemaBuilder:

Kotlin

Definition of GraphQL bean in Satellite service (source code)

The library generates two additional queries (_service and _entities) that will be used by Apollo Server. These queries are internal, i.e., they won’t be exposed by Apollo Server. A service with Apollo Federation support still can work independently. The library’s API may change in the future.

Apollo Server

Apollo Server and Apollo Federation allow achieving 2 main goals:

  • create a single endpoint for GraphQL APIs' clients

  • create a single data graph from distributed entities

That is even if you don’t use federated entities, it is more convenient for frontend developers to use a single endpoint than multiple endpoints.

There is another way for creating single GraphQL schema — schema stitching — but now on the Apollo site, it is marked as deprecated. However, there is a library that implements this approach: Nadel. It is written by creators of GraphQL Java and has nothing to do with Apollo Federation; I haven’t used it yet.

This module includes the following sources:

JSON

Meta information, dependencies, and other (source code)

JavaScript

Apollo Server definition (source code)

Maybe the source above can be simplified (especially in the part of passing authorization header); if so, feel free to contact me for change.

Authentication still works as was described earlier (you just need to specify Authorization header and its value). Also, it is possible to change security implementation, for example, move JWT validation logic from downstream services to the apollo-server module.

To launch this service you need to make sure you’ve launched 3 GraphQL Java services described previously, cd to the apollo-server directory, and run the following:

Shell


A successful launch should look like this:

Plain Text

Apollo Server startup log

Then you can use a unified interface to perform GraphQL requests to all of your services:

altair apollo server

Also, you can navigate to http://localhost:4000/playground in your browser and use a built-in Playground IDE.

Note that now even if you have set limitations on queries using MaxQueryComplexityInstrumentation and/or MaxQueryDepthInstrumentation with reasonable parameters as was described above, GraphQL IDE does show the combined documentation. This is because Apollo Server is getting each service’s schema by performing simple { _service { sdl } } query instead of sizeable IntrospectionQuery.

Currently, there are some limitations of such an architecture which I encountered while implementing this project:

An application written in any language or framework can be added as a downstream service of Apollo Server if it implements Federation specification; a list of libraries that offer such support is available on Apollo documentation.

Conclusion

In this article, I tried to summarize my experience with GraphQL on the JVM. Also, I showed how to combine APIs of GraphQL Java services to provide a unified GraphQL interface; in such an architecture an entity can be distributed among several microservices. It is achieved by using Apollo Server, Apollo Federation, and graphql-java-federation library. The source code of the considered project is on GitHub. Thanks for reading!

Further Reading

Smart Pipes and Smart Endpoints With Service Mesh

Microservices With GraphQL

Topics:
graphql ,java ,javascript ,kotlin ,micronaut ,microservices

Published at DZone with permission of Roman Kudryashov . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}