In the era of microservices, we have to write more and more integrations for their interaction with each other and with third-party systems. Some people create separate libraries with integration and reuse them in several microservices. Some people clutter their code with a huge number of POJO classes; some people create one POJO class with many nested classes. In this article, I would like to share an approach that will help you hide most of the code that prevents you from reading and understanding the project.
For this example, we'll use GitHub API integration and familiar Spring annotations to describe our feign client.
Let's add feign-spring4 to support client generation based on spring annotations.
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-spring4</artifactId>
<version>${feign.version}</version>
</dependency>
Then, let's add feign-Jackson to support the annotations that will be in the POJO-generated classes.
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${feign.version}</version>
</dependency>
In the plugins section, add the configuration for the jsonschema2pojo-maven-plugin. Specify where you store the JSON schemas, what package name will be used in the generated classes, and disable adding additional fields to ignore those we don't use.
<build>
<plugins>
<plugin>
<groupId>org.jsonschema2pojo</groupId>
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<sourceDirectory>${basedir}/src/main/resources/schema</sourceDirectory>
<targetPackage>com.example.types</targetPackage>
<includeAdditionalProperties>false</includeAdditionalProperties>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Let's describe the JSON scheme that will be used to generate our POJO classes. To prevent the generator from missing classes from definitions, we need to add references to them in properties.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description" : "Definition of GitHubApi",
"type": "object",
"properties" : {
"contributor" : {
"type" : "object",
"$ref" : "#/definitions/Contributor"
},
"issue" : {
"type" : "object",
"$ref" : "#/definitions/Issue"
}
},
"definitions": {
"Contributor" : {
"type" : "object",
"properties": {
"login": {
"type": "string"
},
"contributions": {
"type": "integer"
}
}
},
"Issue" : {
"type" : "object",
"properties": {
"title": {
"type": "string"
},
"body": {
"type": "string"
},
"labels": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
},
"user": {
"type" : "object",
"$ref" : "#/definitions/User"
}
}
},
"User" : {
"type" : "object",
"properties": {
"login": {
"type": "string"
},
"avatar_url": {
"type": "string"
}
}
}
}
}
To simplify schematic creation, you can use an online schematic generator from JSON. If you want to use your custom classes as part of the response, you can add the following in the definition section and use "$ref": "#/definitions/MyClass":
"MyClass": {
"existingJavaType": "com.example.demo.MyClass"
}
Let's create a simple interface to describe the final URIs to retrieve the data.
interface GitHubApi {
@GetMapping("/repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
@GetMapping("/repos/{owner}/{repo}/issues")
List<Issue> issues(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
}
Create a client to access GitHub. Select JacksonDecoder to support annotations in POJO classes (such as @JsonProperty). Add SpringContract to use spring annotations (@GetMapping, @PathVariable).
GitHubApi github = Feign.builder()
.decoder(new JacksonDecoder())
.contract(new SpringContract())
.target(GitHubApi.class, "https://api.github.com");
We can then use this client to invoke methods such as contributors and issues. More in-depth customization can be done if needed. For example, we can add logging of each request/response, the number of repeated calls in case of errors, and much more.
Of course, there are many code generation implementations that can be used, such as OpenAPI. This approach gives flexibility in describing POJOs and easy client configuration.