Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Overview
So far, in our cloud application, weβve used the Gateway Pattern to support two main features.
First, we insulated our clients from each service, eliminating the need for cross-origin support. Next, we implemented locating instances of services using Eureka.
In this article, we are going to look at how to use the Gateway pattern to retrieve data from multiple services with a single request. To do this, weβre going to introduce Feign into our gateway to help write the API calls to our services.
To read up on how to use the OpenFeign client check out this article.
Spring Cloud now also provides the Spring Cloud Gateway project which implements this pattern.
2. Setup
Letβs open up the pom.xml of our gateway server and add the dependency for OpenFeign:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
For reference β we can find the latest versions on Maven Central (spring-cloud-starter-feign).
Now that we have the support for building a OpenFeign client, letβs enable it in the GatewayApplication.java:
@EnableFeignClients
public class GatewayApplication { ... }
Now letβs set up OpenFeign clients for the book and rating services.
3. OpenFeign Clients
3.1. Book Client
Letβs create a new interface called BooksClient.java:
@FeignClient("book-service")
public interface BooksClient {
@RequestMapping(value = "/books/{bookId}", method = RequestMethod.GET)
Book getBookById(@PathVariable("bookId") Long bookId);
}
With this interface, weβre instructing Spring to create a OpenFeign client that will access the β/books/{bookId}β endpoint. When called, the getBookById method will make an HTTP call to the endpoint, and make use of the bookId parameter.
To make this work we need to add a Book.java DTO:
@JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
private Long id;
private String author;
private String title;
private List<Rating> ratings;
// getters and setters
}
Letβs move on to the RatingsClient.
3.2. Ratings Client
Letβs create an interface called RatingsClient:
@FeignClient("rating-service")
public interface RatingsClient {
@RequestMapping(value = "/ratings", method = RequestMethod.GET)
List<Rating> getRatingsByBookId(
@RequestParam("bookId") Long bookId,
@RequestHeader("Cookie") String session);
}
Like with the BookClient, the method exposed here will make a rest call to our rating service and return the list of ratings for a book.
However, this endpoint is secured. To be able to access this endpoint properly we need to pass the userβs session to the request.
We do this using the @RequestHeader annotation. This will instruct OpenFeign to write the value of that variable to the requestβs header. In our case, we are writing to the Cookie header because Spring Session will be looking for our session in a cookie.
In our case, we are writing to the Cookie header because Spring Session will be looking for our session in a cookie.
Finally, letβs add a Rating.java DTO:
@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating {
private Long id;
private Long bookId;
private int stars;
}
Now, both clients are complete. Letβs put them to use!
4. Combined Request
One common use case for the Gateway pattern is to have endpoints that encapsulate commonly called services. This can increase performance by reducing the number of client requests.
To do this letβs create a controller and call it CombinedController.java:
@RestController
@RequestMapping("/combined")
public class CombinedController { ... }
Next, letβs wire in our newly created OpenFeign clients:
private BooksClient booksClient;
private RatingsClient ratingsClient;
@Autowired
public CombinedController(
BooksClient booksClient,
RatingsClient ratingsClient) {
this.booksClient = booksClient;
this.ratingsClient = ratingsClient;
}
And finally letβs create a GET request that combines these two endpoints and returns a single book with its ratings loaded:
@GetMapping
public Book getCombinedResponse(
@RequestParam Long bookId,
@CookieValue("SESSION") String session) {
Book book = booksClient.getBookById(bookId);
List<Rating> ratings = ratingsClient.getRatingsByBookId(bookId, "SESSION="+session);
book.setRatings(ratings);
return book;
}
Notice that we are setting the session value using the @CookieValue annotation that extracts it from the request.
There it is! We have a combined endpoint in our gateway that reduces network calls between the client and the system!
5. Testing
Letβs make sure our new endpoint is working.
Navigate to LiveTest.java and letβs add a test for our combined endpoint:
@Test
public void accessCombinedEndpoint() {
Response response = RestAssured.given()
.auth()
.form("user", "password", formConfig)
.get(ROOT_URI + "/combined?bookId=1");
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
assertNotNull(response.getBody());
Book result = response.as(Book.class);
assertEquals(new Long(1), result.getId());
assertNotNull(result.getRatings());
assertTrue(result.getRatings().size() > 0);
}
Start up Redis, and then run each service in our application: config, discovery, zipkin, gateway, book, and the rating service.
Once everything is up, run the new test to confirm it is working.
6. Conclusion
Weβve seen how to integrate OpenFeign into our gateway to build a specialized endpoint. We can leverage this information to build any API we need to support. Most importantly we see that we are not trapped by a one-size-fits-all API that only exposes individual resources.
Using the Gateway pattern we can set up our gateway service to each clientβs needs uniquely. This creates decoupling giving our services the freedom to evolve as they need, remaining lean and focused on one area of the application.
