Rest Service Client Java Example
In this post, we are going to talk about OpenFeign which is a declarative REST client that we can use in our Spring Boot applications. Feign helps us a lot when writing web service clients, allowing us to use several helpful annotations to create integrations.
Originally Netflix developed Feign, but as they stopped supporting the library, it is now a community-driven project and is called OpenFeign. In this post, we will simply call it Feign.
Setup
As IDE for this post, we will use IntelliJ IDEA.
We will also use Gradle, and Lombok in the Spring Boot example application.
For containerization, we will use Docker and Docker Compose.
Code
To show how Feign works we will create two services. One of those services will call another using the Feign interface. We will try to keep those services as simple as possible, to focus on Feign features.
Client
Dependencies
Our client will be a fairly simple service with some API calls. Let us see how our build.gradle
looks like, to have a picture of service dependencies:
plugins { id 'org.springframework.boot' version '2.3.0.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'java' id 'io.freefair.lombok' version '5.0.0' } group = 'guru.springframework' version = '0.0.1-SNAPSHOT' sourceCompatibility = '14' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } } test { useJUnitPlatform() }
Almost all of this code can be generated using the IntelliJ IDEA Spring Initializr project creator, as most of these are defaults after that process. We have the spring-boot-starter-web
, as we have picked web dependency to easily create API and we have also added Lombok plugin by hand (id 'io.freefair.lombok' version '5.0.0' }
).
Code
The class we will use first will be a simple DTO class:
import lombok.Value; @Value public class SimpleClientData { private int id; private String name; private int amount; }
To simplify, we have used Lombok @Value
, which generates for us an all arguments constructor we will use in the controller. As for the fields, we will have id
, name
, and amount
, nothing really out of ordinary here.
Time for our API:
@RestController public class SimpleClientController { @GetMapping("/data/{dataId}") public SimpleClientData getData(@PathVariable int dataId) { return new SimpleClientData(dataId, "name-" + dataId, dataId * 2); } }
We have a simple controller with one endpoint inside, for fetching data for a particular id. In our example, we will just create some kind of fake object using the provided id, and return the SimpleClientData
object to the caller as JSON.
That is all for our client, it is enough for us to show Feign usage. Time for more interesting stuff in the second service.
Service
Dependencies
If we are going to use Feign, we should import appropriate dependency. Let us do that in our build.gradle
file:
plugins { id 'org.springframework.boot' version '2.3.0.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'java' id 'io.freefair.lombok' version '5.0.0' } group = 'guru.springframework' version = '0.0.1-SNAPSHOT' sourceCompatibility = '14' repositories { mavenCentral() } ext { set('springCloudVersion', 'Hoxton.SR5') } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } } dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } } test { useJUnitPlatform() }
You can generate the above file using the IntelliJ IDEA Spring Initializr project creator or a web version here: Spring Initializr. In addition to the web dependency, we also selected OpenFeign one (spring-cloud-starter-openfeign
). There is also a declaration for spring cloud dependencies bom, with proper release train version. How does the Spring Cloud work is out of scope for this post, though feel free to check the official project page: Spring Cloud project page.
Code
When we have Feing dependency on place, we can enable our application to use it. To do that, we have to add @EnableFeignClients
annotation to our main application class:
@SpringBootApplication @EnableFeignClients public class FeignApplication { public static void main(String[] args) { SpringApplication.run(FeignApplication.class, args); } }
This allows scanning for interfaces that are declared as feign clients.
To declare an interface as a feign client, @FeignClient
annotation is used. We can see how this looks like on the example of our client interface:
@FeignClient(name = "simple-client", url = "http://localhost:8081") public interface SimpleClient { @GetMapping("/data/{dataId}") SimpleClientData getData(@PathVariable int dataId); }
What we have here is the declaration of the Feign client interface. As arguments, we have passed service name
and client url
to that service, with an appropriate port. In name
and url
attributes, placeholders are supported, so you can also use, for example, ${simple-service.client.name}
. The method inside the interface looks exactly the same as in our client, except it has no body.
For the created Feign client we also get automatically created logger. By default, it has the full class name of the interface. Feing logging only responds to the DEBUG
level though (you can find more information about logging in the official documentation: Feign logging).
We have two more classes in our service. One of them is the same DTO as we have used in our client. The second one is a controller, that is also quite similar to that in client service:
@RestController @RequiredArgsConstructor public class AppController { private final SimpleClient client; @GetMapping("/data/{id}") public SimpleClientData getData(@PathVariable int id) { return client.getData(id); } }
Lombok's @RequiredArgsConstructor
annotation is used to generate a constructor for autowiring our client interface. Next, we use that interface to make a call to client service and fetch the data using id passed with the call.
We also have to set the port for the client to be different than our caller service. We can do that in application.yml
file:
server: port: 8081
Result
Let us try how those two services work. After we started our client service and a caller service, we can try to call our caller service endpoint in our browser, to check if we really receive desired output:
As we can see it works perfectly. We received JSON data exactly as we have created it in the client service.
Manual Creation of Feign Client
It is also possible to manually create Feign client if there is a need for some particular configuration. Let us create such a client along with a next controller class:
@RestController @Import(FeignClientsConfiguration.class) public class ManualFeignController { private SimpleClient clientManual; public ManualFeignController(Contract contract) { this.clientManual = Feign.builder() .contract(contract) .decoder((response, type) -> new SimpleClientData(7, "manual", 10)) .target(SimpleClient.class, "http://localhost:8081"); } @GetMapping("/data/manual/{id}") public SimpleClientData getManualData(@PathVariable int id) { return clientManual.getData(id); } }
FeignClientsConfiguration
class in @Import
annotation is just a default configuration provided by Spring Cloud Netflix.
In the constructor of the controller, we are creating Feing client using FeignBuilder API. It is also possible to configure Feing clients using application properties. Feign Contract
object defines what annotations and values are valid on interfaces. This autowired bean provides us with the possibility to use SpringMVC annotations instead of native Feign ones.
We have changed decoder, to instead of passing a decoded value that we get from the client, gives us a new, constant instance of SimpleClientData
.
In target
, we have just used the interface we have, pointing to our localhost:8081
url.
As a result of running this new endpoint in a browser, we get:
As you can see now we receive that constant object, instead of the value returned from our client service.
There are many options you can configure in that manner, like some request interceptors for authentication, decoders/encoders, or even if the decoder should process 404 status responses. All of that you can find in the official documentation.
Feign
Of course, there is a lot more to Feign than just that simple example above. What we have used is one of the simplest ways we can use Feign. Let us talk about other features we can get with Feign.
Load Balancing
Feign has built-in support for load balancing.
Remember when we used the name as an argument to the @FeignClient
annotation? Feign uses this argument as the client's name during load balancing. This name is used to create either a Ribbon load-balancer or Spring Cloud LoadBalancer.
Note that Ribbon is used as a default load-balancer, although it is deprecated now, and you should use Spring Cloud LoadBalancer instead.
Load-balancer will try to find a physical server address for the service name used. Of course, for this to work we need to have some kind of service discovery (for example, check the series of Consul articles where we are setting up Feign with service discovery: Consul Miniseries: Spring Boot Application and Consul Integration Part 1).
Circuit Breaker
When using Feing, it is possible to use the built-in support for circuit breaker. A circuit breaker allows us to stop recurring failures from happening. This can happen due to various reasons, for example, client service may be down or there could be some network problems.
To use that feature, Hystrix should be available on the classpath, and feign.hystrix.enabled=true
property has to be set. To read more about Hystrix circuit breaker check official documentation: Hystrix documentation.
With Feign and Hystrix it is also possible to register fallbacks, a default code path if any error occurs during remote calls.
Request/Response Compression
It is possible to use GZIP compression on request/response with Feign. All you have to do is to enable corresponding property:
feign.compression.request.enabled=true
feign.compression.response.enabled=true
There is a possibility to filter those by mime types or minimum size if necessary.
For additional features, you can check official Feign documentation here: Official OpenFeign documentation.
Summary
In this post, we have created two services that we connected using OpenFeign declarative REST client. We have also learned how to make a basic remote call to external service using this library. Additionally, we have also briefly mentioned several important features of Feign.
OpenFeign is a great tool that simplifies writing web service clients. With its support for load-balancing and circuit breaking, it's also a good way to enable more resiliency in our systems.
The source code for this post can be found here on GitHub.
Source: https://springframework.guru/feign-rest-client-for-spring-application/
Belum ada Komentar untuk "Rest Service Client Java Example"
Posting Komentar