Chapter 2. Spring Boot for Microservices

Spring Boot is an opinionated Java framework for building microservices based on the Spring dependency injection framework. Spring Boot facilitates creation of microservices through reduced boilerplate, configuration, and developer friction. This is a similar approach to the two other frameworks we’ll look at.

Advantages of Spring Boot

Spring Boot offers the following advantages in comparison to the Spring framework:

  • Favoring automatic, conventional configuration by default

  • Curating sets of popular starter dependencies for easier consumption

  • Simplifying application packaging

  • Baking in application insight (e.g., metrics and environment info)

Simplified Configuration

Spring historically was a nightmare to configure. Although the framework improved upon other high-ceremony component models (EJB 1.x, 2.x, etc.), it did come along with its own set of heavyweight usage patterns. Namely, Spring required a lot of XML configuration and a deep understanding of the individual beans needed to construct JdbcTemplates, JmsTemplates, BeanFactory lifecycle hooks, servlet listeners, and many other components. In fact, writing a simple “Hello World” with Spring MVC required understanding of DispatcherServlet and a whole host of Model–View–Controller classes. Spring Boot aims to eliminate all of this boilerplate configuration with some implied conventions and simplified annotations—although, you can still finely tune the underlying beans if you need to.

Starter Dependencies

Spring was used in large enterprise applications that typically leveraged lots of different technologies to do the heavy lifting: JDBC databases, message queues, file systems, application-level caching, etc. Developers often had to stop what they were doing, switch cognitive contexts, figure out what dependencies belonged to which piece of functionality (“Oh, I need the JPA dependencies!”), and spend lots of time sorting out versioning mismatches and other issues that arose when trying to use these various pieces together. Spring Boot offers a large collection of curated sets of libraries for adding these pieces of functionality. These starter modules allow you to add things like:

  • Java Persistence API (JPA)

  • NoSQL databases like MongoDB, Cassandra, and Couchbase

  • Redis caching

  • Tomcat/Jetty/Undertow servlet engines

  • Java Transaction API (JTA)

Adding a submodule to an application brings in a curated set of transitive dependencies and versions that are known to work together, saving developers from having to sort out dependencies themselves.

Application Packaging

Spring Boot really is a set of bootstrap libraries with some convention for configurations, but there’s no reason why you couldn’t run a Spring Boot application inside your existing application servers as a Web Application Archive (WAR). The idiom that most developers who use Spring Boot prefer for their applications is the self-contained Java Archive (JAR) packaging, where all dependencies and application code are bundled together with a flat class loader in a single JAR. This makes it easier to understand application startup, dependency ordering, and log statements. More importantly, it also helps reduce the number of moving pieces required to take an app safely to production. You don’t take an app and chuck it into an app server; the app, once it’s built, is ready to run as is—standalone—including embedding its own servlet container if it uses servlets. That’s right, a simple java -jar <name.jar> is enough to start your application now! Spring Boot, MicroProfile/Thorntail, and many other frameworks like Vert.x and Dropwizard all follow this pattern of packaging everything into an executable uber-AR.

But what about the management things we typically expect out of an application server?

Production-Ready Features

Spring Boot ships with a module called spring boot actuator that enables things like metrics and statistics about your application. For example, you can collect logs, view metrics, perform thread dumps, show environment variables, understand garbage collection, and show which beans are configured in the BeanFactory. You can expose this information via HTTP or Java Management Extensions (JMX), or you can even log in directly to the process via SSH.

With Spring Boot, you can leverage the power of the Spring framework and reduce boilerplate configuration and code to more quickly build powerful, production-ready microservices. Let’s see how.

Getting Started

We’re going to use the Spring Boot command-line interface (CLI) to bootstrap our first Spring Boot application (the CLI uses Spring Initializr under the covers). You are free to explore the different ways to do this if you’re not comfortable with the CLI. Alternatives include using Spring Initializr plug-ins for your favorite IDE, or using the web version. The Spring Boot CLI can be installed a few different ways, including through package managers and by downloading it straight from the website. Check for instructions on installing the CLI most appropriate for your development environment.

Once you’ve installed the CLI tools, you should be able to check the version of Spring you have:

$ spring --version

Spring CLI v2.1.1.RELEASE

If you can see a version for your installation of the CLI, congrats! Now navigate to the directory where you want to host your examples from the report and run the following command:

$ spring init --build maven --groupId com.redhat.examples 
  --version 1.0 --java-version 1.8 --dependencies web 
  --name hello-springboot  hello-springboot

After running this command, you should have a directory named hello-springboot with a complete Spring Boot application. If you run the command and end up with a demo.zip, then just unzip it and continue. Let’s take a quick look at what those command-line options are:

--build

The build management tool we want to use. maven and gradle are the two valid options at this time.

--groupId

The groupId to use in our Maven coordinates for our pom.xml. Unfortunately this does not properly extend to the Java package names that get created; these need to be modified by hand.

--version

The version of our application. This will be used in later iterations, so set to 1.0.

--java-version

The build compiler version for the JDK.

--dependencies

This is an interesting parameter; we can specify fully baked sets of dependencies for doing common types of development. For example, web will set up Spring MVC and embed an internal servlet engine (Tomcat by default, Jetty and Undertow as options). Other convenient dependency bundles/starters include jpa, security, and cassandra).

Now, from the hello-springboot directory, try running the following command:

$ mvn spring-boot:run

If everything boots up without any errors, you should see some logging similar to this:

2018-12-13 13:18:19  --- [main] TomcatWebServer
  : Tomcat started on port(s): 8080 (http) with context path ''
2018-12-13 13:18:19 --- [main] HelloSpringbootApplication
    : Started HelloSpringbootApplication in 2.3 seconds
    (JVM running for 10.265)

Congrats! You have just gotten your first Spring Boot application up and running. If you navigate to http://localhost:8080 in your browser, you should see the output shown in Figure 2-1.

Figure 2-1 Whitelabel error page
Figure 2-1. Whitelabel error page

This default error page is expected since our application doesn’t do anything yet. Let’s move on to the next section to add a REST endpoint to put together a Hello World use case.

Hello World

Now that we have a Spring Boot application that can run, let’s add some simple functionality. We want to expose an HTTP/REST endpoint at /api/hello that will return “Hello Spring Boot from X" where X is the IP address where the service is running. To do this, navigate to src/main/java/com/examples/hellospringboot. This location should have been created for you if you followed the preceding steps. Then create a new Java class called HelloRestController, as shown in Example 2-1. We’ll add a method named hello() that returns a string along with the IP address of where the service is running. You’ll see in Chapter 6, when we discuss load balancing and service discovery, how the host IPs can be used to demonstrate proper failover, load balancing, etc.

Example 2-1. src/main/java/com/examples/hellospringboot/HelloRestController.java
public class HelloRestController {

    public String hello() {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                                  .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hello Spring Boot from " + hostname;
    }

}

Add the HTTP Endpoints

At this point, this piece of code is just a POJO (plain old Java object), and you could (and should) write a unit test that verifies its behavior. To expose this as a REST endpoint, we’re going to make use of the following annotations in Example 2-2:

@RestController

Tells Spring this is an HTTP controller capable of exposing HTTP endpoints (GET, PUT, POST, etc.)

@RequestMapping

Maps specific parts of the HTTP URI path to classes, methods, and parameters in the Java code

Note that import statements are omitted.

Example 2-2. src/main/java/com/examples/hellospringboot/HelloRestController.java
@RestController
@RequestMapping("/api")
public class HelloRestController {

    @RequestMapping(method = RequestMethod.GET, value = "/hello",
            produces = "text/plain")
    public String hello(){
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                    .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hello Spring Boot from " + hostname;
    }

}

In this code, all we’ve done is add the aforementioned annotations. For example, @RequestMapping("/api") at the class level says “map any method-level HTTP endpoints under this root URI path.” When we add @RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "text/plain"), we are telling Spring to expose an HTTP GET endpoint at /hello (which will really be /api/hello) and map requests with a media type of Accept: text/plain to this method. Spring Boot defaults to using an embedded Tomcat servlet container, but this can be switched to other options like Undertow or Jetty.

If you build the application and run spring-boot:run again, you should be able to reach your HTTP endpoint:

$ mvn clean spring-boot:run

Now if you point your browser to http://localhost:8080/api/hello, you should see a response similar to Figure 2-2.

Figure 2-2 Successful hello
Figure 2-2. Successful hello

What if we want to add some environment-aware configuration to our application? For example, instead of saying “Hello,” maybe you want to say “Guten Tag” if we deploy our app in production for German users. We need a way to inject properties into our app.

Externalize Configuration

Spring Boot makes it easy to use external property sources like properties files, command-line arguments, the OS environment, or Java system properties. We can even bind entire “classes” of properties to objects in our Spring context. For example, if we want to bind all helloapp.* properties to the HelloRestController, we can add @ConfigurationProperties(prefix="helloapp"), and Spring Boot will automatically try to bind helloapp.foo and helloapp.bar to Java Bean properties in the HelloRestController class. We can define new properties in src/main/resources/application.properties. The application.properties file was automatically created for us when we created our project. (Note that we could change the filename to application.yml and Spring would still recognize the YAML file as the source of properties.)

Let’s add a new property to our src/main/resources/application.properties file:

helloapp.saying=Guten Tag aus

Next we add the @ConfigurationProperties annotation and our new saying field to the HelloRestController class, as shown in Example 2-3. Note we also need setters.

Example 2-3. src/main/java/com/examples/hellospringboot/HelloRestController.java
@RestController
@RequestMapping("/api")
@ConfigurationProperties(prefix="helloapp")
public class HelloRestController {

    private String saying;

    @RequestMapping(method = RequestMethod.GET, value = "/hello",
            produces = "text/plain")
    public String hello(){
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                    .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return saying + " " + hostname;
    }

    public void setSaying(String saying) {
        this.saying = saying;
    }
}

Stop the application from running (if you haven’t already) and restart it:

$ mvn clean spring-boot:run

Now if you navigate to http://localhost:8080/api/hello, you should see the German version of the saying as shown in Figure 2-3.

Figure 2-3 Successful German
Figure 2-3. Successful German greeting

We can now externalize properties that will change depending on the environment in which we are running. Things like service URIs, database URIs and passwords, and message queue configurations would all be great candidates for external configuration. Don’t overdo it, though; not everything needs to change depending on the environment! Ideally an application would be configured exactly the same in all environments, including timeouts, thread pools, retry thresholds, etc.

Expose Application Metrics and Information

If we want to put this microservice into production, how will we monitor it? How can we get any insight about how things are running? Often our microservices are black boxes unless we explicitly think through how we want to expose metrics to the outside world. Fortunately, Spring Boot comes with a prepackaged starter (spring-boot-starter-actuator) that makes doing this a breeze.

Let’s see what it takes to enable the actuator. Open up the pom.xml file for your hello-springboot microservice and add the following Maven dependency in the <dependencies>...</dependencies> section:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Because we’ve added the actuator dependency, our application now can expose a lot of information that will be very handy for debugging or general microservice insight.

Not all endpoints provided by the actuator dependency are exposed by default, however. We need to manually specify which endpoints will be exposed.

Add the following property to src/main/resources/application.properties to expose some technology-agnostic endpoints:

#Enable management endpoints
management.endpoints.web.exposure.include=
        beans,env,health,metrics,httptrace,mappings

Now restart your microservice by stopping it and running:

$ mvn clean spring-boot:run

Try hitting the following URLs and examine what gets returned:

  • http://localhost:8080/actuator/beans

  • http://localhost:8080/actuator/env

  • http://localhost:8080/actuator/health

  • http://localhost:8080/actuator/metrics

  • http://localhost:8080/actuator/httptrace

  • http://localhost:8080/actuator/mappings

Figure 2-4 shows an example of what the http://localhost:8080/env endpoint looks like.

Figure 2-4 Actuator response
Figure 2-4. Actuator response

Exposing runtime insights like this frees up the developer to just focus on writing code for the microservice that delivers business value. Delegating the heavy lifting and boilerplate to frameworks is definitely a good idea.

Running Outside of Maven

Up to this point we’ve been thinking about development and building our Hello World microservice from the perspective of a developer’s laptop using Maven. But what if you want to distribute your microservice to others or run it in a live environment (development, QA, production)?

Luckily, with Spring Boot it only takes a few steps to get ready for shipment and production. Spring Boot prefers atomic, executable JARs with all dependencies packed into a flat classpath. This means the JAR that we create as part of a call to mvn clean package is executable and contains all we need to run our microservice in a Java environment. To test this out, go to the root of the hello-springboot microservice project and run the following commands:

$ mvn clean package
$ ava -jar target/hello-springboot-1.0.jar

If your project was named demo instead of hello-springboot, then substitute the properly named JAR file (demo-1.0.jar).

That’s it!

We’ll notice this sort of idiom when we explore MicroProfile in the next chapter, too.

Calling Another Service

In a microservices environment, each service is responsible for providing its functionality or service to other collaborators. As we discussed in the first chapter, building distributed systems is hard, and we cannot abstract away the network or the potential for failures. We will cover how to build resilient interactions with our dependencies in Chapter 5. In this section, however, we will just focus on getting a service to talk to a dependent service.

If we wish to extend the hello-springboot microservice, we will need to create a service that we can call using Spring’s REST client functionality. For this example and the rest of the examples in the report, we’ll use a backend service and modify our service to reach out to the backend to generate the greetings we want to be able to use, as indicated by Figure 2-5.

Figure 2-5 Calling another service
Figure 2-5. Calling another service

If you look at the source code for this report, you’ll see a Maven module called backend that contains a very simple HTTP servlet that can be invoked with a GET request and query parameters. The code for this backend is very simple, and it does not use any of the microservice frameworks (Spring Boot, MicroProfile etc.). We have created a ResponseDTO object that encapsulates time, ip, and greeting fields. We also leverage the awesome Jackson library for JSON data binding, as seen here:

@WebServlet(urlPatterns = {"/api/backend"})
public class BackendHttpServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req,
        HttpServletResponse resp)
                throws ServletException, IOException {
        resp.setContentType("application/json");

        ObjectMapper mapper = new ObjectMapper();
        String greeting = req.getParameter("greeting");

        ResponseDTO response = new ResponseDTO();
        response.setGreeting(greeting +
             " from cluster Backend");
        response.setTime(System.currentTimeMillis());
        response.setIp(getIp());

        PrintWriter out = resp.getWriter();
        mapper.writerWithDefaultPrettyPrinter()
            .writeValue(out, response);
    }

    private String getIp() {
        String hostname = null;
        try {
            hostname = InetAddress
                            .getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return hostname;
    }
}

To start up the backend service on port 8080, navigate to the backend directory and run the following command:

$ mvn wildfly:run

The backend project uses the Maven WildFly plug-in, which allows us to quickly boot up our app using mvn wildfly:run.

This service is exposed at /api/backend and takes a query parameter called greeting. For example, when we call this service with the path /api/backend?greeting=Hello, like this (you can also visit this URL with your browser):

$ curl -X GET http://localhost:8080/api/backend?greeting=Hello

Then the backend service will respond with a JSON object something like this:

{
  "greeting" : "Hello from cluster Backend",
  "time" : 1459189860895,
  "ip" : "172.20.10.3"
}

Next, we’ll create a new HTTP endpoint, /api/greeting, in our hello-springboot microservice and use Spring to call this backend.

First, we create a class in src/main/java/com/examples/hellospringboot called GreeterRestController and fill it in similarly to how we filled in the HelloRestController class (see Example 2-4).

Example 2-4. src/main/java/com/example/hellospringboot/GreeterRestController.java
@RestController
@RequestMapping("/api")
@ConfigurationProperties(prefix="greeting")
public class GreeterRestController {

    private String saying;

    private String backendServiceHost;

    private int backendServicePort;

    @RequestMapping(value = "/greeting",
    method = RequestMethod.GET, produces = "text/plain")
    public String greeting(){
        String backendServiceUrl = String.format(
            "http://%s:%d/api/backend?greeting={greeting}",
            backendServiceHost, backendServicePort);
        System.out.println("Sending to: " + backendServiceUrl);
        return backendServiceUrl;
    }
}

We’ve left out the setters for the properties in this class, but make sure you have them in your source code! Note that we are using the @ConfigurationProperties annotation again to configure the REST controller here, although this time we are using the greeting prefix. We also create a GET endpoint, like we did with the hello service; and all it returns at the moment is a string with the values of the backend service host and port concatenated (these values are injected in via the @ConfigurationProperties annotation). Let’s add the backendServiceHost and backendServicePort to our application.properties file:

greeting.saying=Hello Spring Boot
greeting.backendServiceHost=localhost
greeting.backendServicePort=8080

Next, we’re going to use Spring’s RestTemplate to do the invocation of the remote service. Following a long-lived Spring convention with its template patterns, the RestTemplate wraps common HTTP/REST idioms inside a convenient wrapper abstraction which then handles all the connections and marshalling/unmarshalling the results of an invocation. RestTemplate uses the native JDK for HTTP/network access, but you can swap that out for Apache HttpComponents, OkHttp, Netty, or others.

Example 2-5 shows what the source looks like when using the RestTemplate (again, the getters/setters are omitted here, but required). We are communicating with the backend service by constructing a URL based on the host and port that have been injected, and we add a GET query parameter called greeting. The value we send to the backend service for the greeting parameter is from the saying field of the GreeterRestController object, which gets injected as part of the configuration when we add the @ConfigurationProperties annotation.

Example 2-5. src/main/java/com/example/GreeterRestController.java
@RestController
@RequestMapping("/api")
@ConfigurationProperties(prefix="greeting")
public class GreeterRestController {

    private RestTemplate template = new RestTemplate();

    private String saying;

    private String backendServiceHost;

    private int backendServicePort;

    @RequestMapping(value = "/greeting",
    method = RequestMethod.GET, produces = "text/plain")
    public String greeting(){

        String backendServiceUrl = String.format(
            "http://%s:%d/api/backend?greeting={greeting}",
            backendServiceHost, backendServicePort);

        System.out.println("Sending to: " + backendServiceUrl);

        BackendDTO response = template.getForObject(
        backendServiceUrl, BackendDTO.class, saying);

        return response.getGreeting() + " at host: " +
        response.getIp();
    }
}

Next, we add the BackendDTO class, which is used to encapsulate responses from the backend (Example 2-6).

Example 2-6. src/main/java/com/examples/hellospringboot/BackendDTO.java
public class BackendDTO {

    private String greeting;
    private long time;
    private String ip;

    public String getGreeting() {
        return greeting;
    }

    public void setGreeting(String greeting) {
        this.greeting = greeting;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }
}

Now let’s build the microservice and verify that we can call this new greeting endpoint and that it properly calls the backend. First, start the backend if it’s not already running. Navigate to the backend directory of the source code that comes with this application and run it:

$ mvn clean wildfly:run

Next, we’ll build and run the Spring Boot microservice. Let’s also configure this service to run on a different port than the default port (8080) so that it doesn’t collide with the backend service, which is already running on port 8080:

$ mvn clean spring-boot:run -Dserver.port=9090

Later in the report we’ll see how running these microservices in their own Linux container removes the restriction of port swizzling at runtime.

Now, point your browser to http://localhost:9090/api/greeting to see if the microservice properly calls the backend and displays what we’re expecting, as shown in Figure 2-6.

Figure 2-6 Successful backend
Figure 2-6. Successful backend

Where to Look Next

In this chapter, you learned what Spring Boot is and how it’s different from traditional WAR/EAR deployments. You also saw some simple use cases, including exposing an HTTP/REST endpoint, externalizing configuration, exposing metrics, and calling another service. This is just scratching the surface; if you’re interested in learning more about Spring Boot, please take a look at the following references:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.28.107