Book Image

Spring: Microservices with Spring Boot

By : In28Minutes Official
Book Image

Spring: Microservices with Spring Boot

By: In28Minutes Official

Overview of this book

Microservices helps in decomposing applications into small services and move away from a single monolithic artifact. It helps in building systems that are scalable, flexible, and high resilient. Spring Boot helps in building REST-oriented, production-grade microservices. This book is a quick learning guide on how to build, monitor, and deploy microservices with Spring Boot. You'll be first familiarized with Spring Boot before delving into building microservices. You will learn how to document your microservice with the help of Spring REST docs and Swagger documentation. You will then learn how to secure your microservice with Spring Security and OAuth2. You will deploy your app using a self-contained HTTP server and also learn to monitor a microservice with the help of Spring Boot actuator. This book is ideal for Java developers who knows the basics of Spring programming and want to build microservices with Spring Boot. This book is embedded with useful assessments that will help you revise the concepts you have learned in this book. This book is repurposed for this specific learning experience from material from Packt's Mastering Spring 5.0 by Ranga Rao Karanam.
Table of Contents (7 chapters)

First REST Service


Let's start with creating a simple REST service returning a welcome message. We will create a simple POJO WelcomeBean class with a member field called message and one argument constructor, as shown in the following code snippet:

    package com.mastering.spring.springboot.bean;

    public class WelcomeBean {
      private String message;

       public WelcomeBean(String message) {
         super();
         this.message = message;
       }

      public String getMessage() {
        return message;
      }
    }

Simple Method Returning String

Let's start with creating a simple REST Controller method returning a string:

    @RestController
    public class BasicController {
      @GetMapping("/welcome")
      public String welcome() {
        return "Hello World";
      }
    }

A few important things to note are as follows:

  • @RestController: The @RestController annotation provides a combination of @ResponseBody and @Controller annotations. This is typically used to create REST Controllers.

  • @GetMapping("welcome"): @GetMapping is a shortcut for @RequestMapping(method = RequestMethod.GET). This annotation is a readable alternative. The method with this annotation would handle a Get request to the welcome URI.

If we run Application.java as a Java application, it would start up the embedded Tomcat container. We can launch up the URL in the browser, as shown in the following screenshot:

Unit Testing

Let's quickly write a unit test to test the preceding controller method:

    @RunWith(SpringRunner.class)
    @WebMvcTest(BasicController.class)
    public class BasicControllerTest {

      @Autowired
      private MockMvc mvc;

      @Test
      public void welcome() throws Exception {
        mvc.perform(
        MockMvcRequestBuilders.get("/welcome")
       .accept(MediaType.APPLICATION_JSON))
       .andExpect(status().isOk())
       .andExpect(content().string(
       equalTo("Hello World")));
      }
    }

In the preceding unit test, we will launch up a Mock MVC instance with BasicController. A few quick things to note are as follows:

  • @RunWith(SpringRunner.class): SpringRunner is a shortcut to the SpringJUnit4ClassRunner annotation. This launches up a simple Spring context for unit testing.

  • @WebMvcTest(BasicController.class): This annotation can be used along with SpringRunner to write simple tests for Spring MVC controllers. This will load only the beans annotated with Spring-MVC-related annotations. In this example, we are launching a Web MVC Test context with the class under test being BasicController.

  • @Autowired private MockMvc mvc: Autowires the MockMvc bean that can be used to make requests.

  • mvc.perform(MockMvcRequestBuilders.get("/welcome").accept(MediaType.APPLICATION_JSON)): Performs a request to /welcome with the Accept header value application/json.

  • andExpect(status().isOk()): Expects that the status of the response is 200 (success).

  • andExpect(content().string(equalTo("Hello World"))): Expects that the content of the response is equal to "Hello World".

Integration Testing

When we do integration testing, we would want to launch the embedded server with all the controllers and beans that are configured. This code snippet shows how we can create a simple integration test:

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = Application.class, 
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class BasicControllerIT {

      private static final String LOCAL_HOST = 
      "http://localhost:";

      @LocalServerPort
      private int port;

      private TestRestTemplate template = new TestRestTemplate();

      @Test
      public void welcome() throws Exception {
        ResponseEntity<String> response = template
       .getForEntity(createURL("/welcome"), String.class);
        assertThat(response.getBody(), equalTo("Hello World"));
       }

      private String createURL(String uri) {
        return LOCAL_HOST + port + uri;
      }
    }

A few important things to note are as follows:

  • @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT): It provides additional functionality on top of the Spring TestContext. Provides support to configure the port for fully running the container and TestRestTemplate (to execute requests).

  • @LocalServerPort private int port: The SpringBootTest would ensure that the port on which the container is running is autowired into the port variable.

  • private String createURL(String uri): The method to append the local host URL and port to the URI to create a full URL.

  • private TestRestTemplate template = new TestRestTemplate(): The TestRestTemplate is typically used in integration tests. It provides additional functionality on top of RestTemplate, which is especially useful in the integration test context. It does not follow redirects so that we can assert response location.

  • template.getForEntity(createURL("/welcome"), String.class): It executes a get request for the given URI.

  • assertThat(response.getBody(), equalTo("Hello World")): It asserts that the response body content is "Hello World".

Simple REST Method Returning an Object

In the previous method, we returned a string. Let's create a method that returns a proper JSON response. Take a look at the following method:

    @GetMapping("/welcome-with-object")
    public WelcomeBean welcomeWithObject() {
      return new WelcomeBean("Hello World");
    }

This preceding method returns a simple WelcomeBean initialized with a message: "Hello World".

Executing a Request

Let's send a test request and see what response we get. The following screenshot shows the output:

The response for the http://localhost:8080/welcome-with-object URL is shown as follows:

    {"message":"Hello World"}

The question that needs to be answered is this: how does the WelcomeBean object that we returned get converted into JSON?

Again, it's the magic of Spring Boot auto-configuration. If Jackson is on the classpath of an application, instances of the default object to JSON (and vice versa) converters are auto-configured by Spring Boot.

Unit Testing

Let's quickly write a unit test checking for the JSON response. Let's add the test to BasicControllerTest:

    @Test
    public void welcomeWithObject() throws Exception {
      mvc.perform(
       MockMvcRequestBuilders.get("/welcome-with-object")
      .accept(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content().string(containsString("Hello World")));
    }

This test is very similar to the earlier unit test except that we are using containsString to check whether the content contains a substring "Hello World". We will learn how to write proper JSON tests a little later.

Integration Testing

Let's shift our focus to writing an integration test. Let's add a method to BasicControllerIT, as shown in the following code snippet:

    @Test
    public void welcomeWithObject() throws Exception {
      ResponseEntity<String> response = 
      template.getForEntity(createURL("/welcome-with-object"), 
      String.class);
      assertThat(response.getBody(), 
      containsString("Hello World"));
    }

This method is similar to the earlier integration test except that we are asserting for a sub-string using the Stringmethod.

Get Method with Path Variables

Let's shift our attention to path variables. Path variables are used to bind values from the URI to a variable on the controller method. In the following example, we want to parameterize the name so that we can customize the welcome message with a name:

    private static final String helloWorldTemplate = "Hello World, 
    %s!";

   @GetMapping("/welcome-with-parameter/name/{name}")
   public WelcomeBean welcomeWithParameter(@PathVariable String name) 
    {
       return new WelcomeBean(String.format(helloWorldTemplate, name));
    }

A few important things to note are as follows:

  • @GetMapping("/welcome-with-parameter/name/{name}"): {name} indicates that this value will be the variable. We can have multiple variable templates in a URI.

  • welcomeWithParameter(@PathVariable String name): @PathVariable ensures that the variable value from the URI is bound to the variable name.

  • String.format(helloWorldTemplate, name): A simple string format to replace %s in the template with the name.

Executing a Request

Let's send a test request and see what response we get. The following screenshot shows the response:

The response for the http://localhost:8080/welcome-with-parameter/name/Buddy URL is as follows:

    {"message":"Hello World, Buddy!"}

As expected, the name in the URI is used to form the message in the response.

Unit Testing

Let's quickly write a unit test for the preceding method. We would want to pass a name as part of the URI and check whether the response contains the name. The following code shows how we can do that:

    @Test
    public void welcomeWithParameter() throws Exception {
      mvc.perform(
      MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy")
     .accept(MediaType.APPLICATION_JSON))
     .andExpect(status().isOk())
     .andExpect(
     content().string(containsString("Hello World, Buddy")));
    }

A few important things to note are as follows:

  • MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy"): This matches against the variable template in the URI. We pass in the name Buddy.

  • .andExpect(content().string(containsString("Hello World, Buddy"))): We expect the response to contain the message with the name.

Integration Testing

The integration test for the preceding method is very simple. Take a look at the following test method:

    @Test
    public void welcomeWithParameter() throws Exception {
      ResponseEntity<String> response = 
      template.getForEntity(
      createURL("/welcome-with-parameter/name/Buddy"), String.class);
      assertThat(response.getBody(), 
      containsString("Hello World, Buddy"));
    }

A few important things to note are as follows:

  • createURL("/welcome-with-parameter/name/Buddy"): This matches against the variable template in the URI. We are passing in the name, Buddy.

  • assertThat(response.getBody(), containsString("Hello World, Buddy")): We expect the response to contain the message with the name.

In this section, we looked at the basics of creating a simple REST service with Spring Boot. We also ensured that we have good unit tests and integration tests. While these are really basic, they lay the foundation for more complex REST services we will build in the next section.

The unit tests and integration tests we implemented can have better asserts using a JSON comparison instead of a simple substring comparison. We will focus on it in the tests we write for the REST services we will create in the next sections.