- Generating stubs with Swagger Codegen and Gradle
- Using a custom template for Swagger Codegen with Gradle
- Generating Feign clients with Swagger Codegen and Gradle
- Accessing custom attributes in Swagger Codegen
- Extending Swagger Codegen with new mustache template files using a new language
In the last article, I showed you how to use a custom template for code generation. If you haven’t read the previous parts of the series, make sure you do that before continuing.
About Feign
First of all, I won’t give you a deep intro into Feign as the article is not about that, but rather I’ll give a high-level overview. Feign’s goal is to enable building declarative HTTP clients with lots of customization. Imagine that you want to make an HTTP call from ServiceA to ServiceB. If you are familiar with the Spring world, most probably you’d go with Spring’s RestTemplate . There’s nothing wrong with that, but Feign approaches this problem a bit differently. You just define an interface in ServiceA according to the contract of ServiceB and make some configuration like where the endpoint lies, what are the parameters, headers, etc.
Let’s have a quick example. This can also be found on OpenFeign’s GitHub page.
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo); } static class Contributor { String login; int contributions; } public static void main(String... args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); // Fetch and print a list of the contributors to this library. List<Contributor> contributors = github.contributors("OpenFeign", "feign"); for (Contributor contributor : contributors) { System.out.println(contributor.login + " (" + contributor.contributions + ")"); } }
As you can see, an interface is defined for the API, called GitHub and you can create a client according to that using Feign’s builder API. After you’re done with building, you can invoke the GitHub API via a simple method call.
Additionally, Feign is capable of multiple customization like using Ribbon, Hystrix, special logging or request management and many more.
Feign with Spring Cloud
Feign has it’s custom annotations for defining the APIs and you have to build your clients manually. Spring Cloud has a Feign integration which means that you can have Spring MVC annotations to define the API, like @RequestMapping , @PathVariable , @RequestParam , etc. Also, you don’t need to manually create the clients, you can just annotate the interface with @FeignClient and add the proper configuration there plus put the @EnableFeignClients annotation on one of the configuration classes.
This integration with Spring’s ecosystem comes handy as you can use the Feign clients as regular beans. Inject them into a bean and just simply invoke the method you want. Very very useful and clean compared to a RestTemplate for example.
Generating Feign clients
Now back to Swagger code generation. It’s already a great thing if you can provide a contract for your API for clients but even better if you could prepare an artifact with which the clients can interact with your API. With Spring Cloud, it’s possible to have Feign clients which doesn’t have hardcoded URLs, ports for the services they are working with instead they can use Eureka to resolve the services by name. This is what we’ll use for generating the Feign clients.
Swagger Codegen has 3 libraries for the language spring : spring-boot , spring-cloud , spring-mvc . For generating Feign clients, we’ll use spring-cloud as it’s already prepared for it, we’ll just customize it a bit for Eureka discovery.
Starting from the state where the previous article ended, we have the template folder in the project which contains the mustache templates for generation. In the libraries folder, there are 3 sub-directories, each for one of the 3 libraries. As we are going to use the spring-cloud library , let’s remove spring-boot and spring-mvc .
Okay, now let’s extend the buildscript with generating the Feign client. First of all, we’ll need a new placeholder project next to the other one, call it user-service-feign-client . The updated settings.gradle looks the following:
def name = 'user-service' def rootName = "${name}-contract" def serverName = "${name}-server" def clientName = "${name}-feign-client" rootProject.name = rootName file(serverName).mkdir() file(clientName).mkdir() include serverName include clientName
Including the Feign client generation in the build process is not that complicated if we’re reusing the existing server-side generator. The build.gradle is extended with the following
project("${rootProject.appName}-feign-client") { // user-service-feign-client // Dependencies for the generated sources dependencies { compile('org.springframework.boot:spring-boot-starter-web:1.5.13.RELEASE') compile('org.springframework.cloud:spring-cloud-starter-feign:1.4.0.RELEASE') compile('io.springfox:springfox-swagger2:2.7.0') } // Actual task for generating the Feign client task generateClient { doLast { def config = new CodegenConfigurator() config.setLang("spring") config.setLibrary('spring-cloud') config.setApiPackage(rootProject.apiPackage) // Package to be used for the API interfaces config.setModelPackage(rootProject.modelPackage) // Package to be used for the API models config.setInputSpec(rootProject.swaggerFile.toString()) // The swagger API file config.setOutputDir(project.buildDir.toString()) // The output directory, user-service-contract/build/user-service-feign-client/ config.setTemplateDir(rootProject.templateDir) // The directory where the templates are located config.setAdditionalProperties([ 'dateLibrary' : 'java8', 'title' : rootProject.appName, 'useTags' : 'true' ]) new DefaultGenerator().opts(config.toClientOptInput()).generate() } } compileJava.dependsOn('generateClient') }
Again, referencing the client project, adding the dependencies which will be used for the client code and setting up the Swagger generator. The important change here is that there is a new setting where we set the used library which is spring-cloud . Also, there is an additional property called title which will be used in the client template for Eureka discovery.
After executing ./gradlew clean build , there will be an error popping up which describes that there is a compilation failure due to missing classes.
error: package org.springframework.cloud.security.oauth2.client.feign does not exist import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor; ^ error: package org.springframework.security.oauth2.client does not exist import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; ^ ...
This happens because Swagger’s default template is using OAuth2 for the client generation along with other custom interceptors. Usually I don’t need this so I’ll show you a way how to get rid of this.
Change the client generation in the build.gradle to this:
project("${rootProject.appName}-feign-client") { // user-service-feign-client ... task generateClient { ... } // Task for deleting the unnecessary configuration task deleteNonClientRelatedClasses(dependsOn: 'generateClient') { doLast { delete "${project.buildDir}/src/main/java/io" } } compileJava.dependsOn('deleteNonClientRelatedClasses') }
There is a new task added, called deleteNonClientRelatedClasses which is responsible to delete the src/main/java/io folder where Swagger Codegen puts the configuration classes by default. Now we are left with a single error that the ClientConfiguration class is not found for the generated Feign clients, but this is expected as we deleted it.
error: cannot find symbol @FeignClient(name="${user-service.name:user-service}", url="${user-service.url:https://localhost}", configuration = ClientConfiguration.class) ^ symbol: class ClientConfiguration
The Feign client template is stored in the apiClient.mustache file within the spring-cloud library folder. Let’s open it and delete the unnecessary configuration for the client. The default template is the following:
package {{package}}; import org.springframework.cloud.netflix.feign.FeignClient; import {{configPackage}}.ClientConfiguration; {{=<% %>=}} @FeignClient(name="${<%title%>.name:<%title%>}", url="${<%title%>.url:<%basePath%>}", configuration = ClientConfiguration.class) <%={{ }}=%> public interface {{classname}}Client extends {{classname}} { }
Now we can remove the import for the ClientConfiguration class as well as configuration for the @FeignClient annotation. Also, as only Eureka discovery will be used for the Feign clients, I’ll simply remove the url configuration as well and end up with this:
package {{package}}; import org.springframework.cloud.netflix.feign.FeignClient; @FeignClient("{{title}}") public interface {{classname}}Client extends {{classname}} { }
It’s a really basic Feign client and the service will be resolved by it’s name, in this case the name of the service should be user-service in Eureka. This is how the generated code looks like:
package com.arnoldgalovics.blog.userservice.api; import org.springframework.cloud.netflix.feign.FeignClient; @FeignClient("user-service") public interface UserApiClient extends UserApi { }
After executing again ./gradlew clean build install , this client artifact can be included in any other service to invoke the user-service .
Testing time
Testing it is a bit tricky as we’ll need a Eureka server, we have to enhance the current user-service to register itself to Eureka and create actually another service which will call the user-service via Feign.
Let’s start with adding Eureka support to the user-service. The build.gradle for the service will now include spring-cloud-eureka as a dependency and the Application class will have @EnableDiscoveryClient annotation on it.
ext { springCloudVersion = 'Edgware.SR3' } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('org.springframework.cloud:spring-cloud-starter-eureka') compile('com.arnoldgalovics.blog:user-service-server:0.0.1-SNAPSHOT') } dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } }
@SpringBootApplication @EnableDiscoveryClient public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }
Okay, now check out the Eureka service. It’s basically a simple Spring Boot app with a dependency to eureka-starter and a bit of configuration. I’ve created a new project called discovery-service with Spring Initializr. The most important thing here is the @EnableEurekaServer annotation on the Application class and the following bootstrap.yml .
server: port: 8761 spring: application: name: discovery-service eureka: client: fetch-registry: false register-with-eureka: false
Next up, we’ll need another service where we can trigger the Feign invocation. Imagine a payment-service which will interact with the user-service. It will be a simple Spring Boot app with Eureka client and a very simple controller.
After creating the project, open the build.gradle and include the generated Feign client. Of course, don’t forget to add mavenLocal() as a repository.
dependencies { compile('com.arnoldgalovics.blog:user-service-feign-client:0.0.1-SNAPSHOT') ... }
The Application class looks the following:
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackageClasses = UserApiClient.class) public class PaymentServiceApplication { public static void main(String[] args) { SpringApplication.run(PaymentServiceApplication.class, args); } }
Notice the @EnableFeignClients annotation which will initiate the Feign client building process. Create a PaymentController with one method available which will invoke the client.
@RestController("/payment") class PaymentController { private UserApiClient userApiClient; public PaymentController(UserApiClient userApiClient) { this.userApiClient = userApiClient; } @GetMapping public ListPayingUserResponse getPayingUsers() { return new ListPayingUserResponse(userApiClient.getUsers().getBody().stream().map(UserResponse::getName).collect(Collectors.toList())); } }
public class ListPayingUserResponse { private List<String> names; public ListPayingUserResponse(List<String> names) { this.names = names; } public List<String> getNames() { return names; } public void setNames(List<String> names) { this.names = names; } }
One last step is missing, to fill up the response from user-service. Go back to the delegate and add some static data to the getUsers method.
@Component public class UserApiDelegateImpl implements UserApiDelegate { @Override public ResponseEntity<Void> createUser(UserCreateRequest user) { // TODO: implementation return null; } @Override public ResponseEntity<ListUserResponse> getUsers() { ListUserResponse response = new ListUserResponse(); response.add(new UserResponse().name("Arnold").id(1)); return ResponseEntity.ok(response); } }
Start the discovery-service first, then the payment-service and the user-service. You can do this from your favorite IDE or by executing ./gradlew clean bootRun on each of them.
Open up your Postman or any other REST Client and invoke GET http://localhost:8001/payment . If you did everything correctly, you’ll see the following response coming back:
{ "names": [ "Arnold" ] }
Cool, right? We just used the generated client from the contract to handle interaction between services.
Recap
In this article, we’ve checked how you can generate Feign clients from a Swagger contract. It’s extremely useful when you want to provide not only your API contract, but a ready to be used artifact with which you can invoke the actual API. Also, we’ve created a small test environment to test the generated Feign client and invoke the service which has the Swagger generated API.
The full project is available on GitHub.
Let me know what you think about this part of the series in the comments or on Twitter. Share it, like it, more articles are coming.