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 allows developers to create microservices through reduced boilerplate, configuration, and developer friction. This is a similar approach to the two other frameworks we’ll look at. Spring Boot does this by:

  • 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 technology to do the heavy lifting: JDBC databases, message queues, file systems, application-level caching, etc. A developer would have to stop what she’s 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 or issues that would arise 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:

  • JPA persistence

  • NoSQL databases like MongoDB, Cassandra, and Couchbase

  • Redis caching

  • Tomcat/Jetty/Undertow servlet engine

  • JTA transactions

Adding a submodule to your application brings in the 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 WAR). The idiom that most developers who use Spring Boot prefer is the self-contained JAR packaging for their application. This means Spring Boot packages all dependencies and application code into a self-contained JAR with a flat class loader. This makes it easier to understand application startup, dependency ordering, and log statements; but more importantly, it helps reduce the number of moving pieces required to take an app safely to production. This means 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, Dropwizard, and WildFly Swarm all follow this pattern of packaging everything into an executable uber JAR.

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

Production Ready

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

With Spring Boot, we 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 visiting the web version of Spring Initializr. 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 v1.3.3.RELEASE

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

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

After running this command, you should have a directory named hola-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 or 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; will be used in later iterations, so set to 1.0.

--java-version

Allows us to specify 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 if you navigate to the hola-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:

2016-03-25 10:57:08.920  [main] AnnotationMBeanExporter
: Registering beans for JMX exposure on startup
2016-03-25 10:57:08.982  [main] TomcatEmbeddedServletContainer
: Tomcat started on port(s): 8080 (http)
2016-03-25 10:57:08.987  [main] HolaSpringbootApplication
: Started HolaSpringbootApplication in 1.0 seconds
(JVM running for 4.7)

Congrats! You have quickly gotten a Spring Boot application up and running! You can even navigate to http://localhost:8080 in your browser and should see the following output:

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/hola that will return “Hola Spring Boot from X” where X is the IP address where the service is running. To do this, navigate to src/main/java/com/example. This location should have been created for you if you followed the preceding steps; remember, the groupId we passed to the spring init program did not apply groupId to the Java package hierarchy, and we’ve left it as it is which should be “com.example”. Then create a new Java class called HolaRestController, as shown in Example 2-1. We’ll add a method named hola() that returns a string along with the IP address of where the service is running. You’ll see in Chapter 5, in our load balancing and service discovery sections, how the host IPs can be used to demonstrate proper failover, loadbalancing, etc.

Example 2-1. src/main/java/com/example/HolaRestController.java
public class HolaRestController {

    public String hola() throws UnknownHostException {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                                  .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hola Spring Boot de " + 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

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

@RequestMapping

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

Note, import statements are omitted.

Example 2-2. src/main/java/com/example/HolaRestController.java
@RestController
@RequestMapping("/api")
public class HolaRestController {

    @RequestMapping(method = RequestMethod.GET, value = "/hola",
        produces = "text/plain")
    public String hola() throws UnknownHostException {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                                  .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hola Spring Boot de " + 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 = "/hola", produces = "text/plain"), we are telling Spring to expose an HTTP GET endpoint at /hola (which will really be /api/hola) and map requests with 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 we build our application and run spring-boot:run again, we should be able to reach our HTTP endpoint:

$ mvn clean package spring-boot:run

Now if we point our browser to http://localhost:8080/api/hola, we should see a response similar to:

successful hola

What if we want to add some environment-aware configuration to our application? For example, instead of saying “Hola,” maybe we want to say “Guten Tag” if we deploy our app in production for German users? We need a way to inject properties to 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 I want to bind all helloapp.* properties to my HolaRestController, I can add @ConfigurationProperties(prefix="helloapp"), and Spring Boot will automatically try to bind helloapp.foo and helloapp.bar to Java Bean properties in the HolaRestController class. Let’s define a new property in src/main/resources/application.properties called helloapp.saying. The application.properties file was automatically created for us when we created our project. Note we could change the file name to application.yml and Spring would still recognize it as a 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

In the HolaRestController in Example 2-3, let’s add the @ConfigurationProperties annotation and our new saying field. Note we also need setters and getters.

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

    private String saying;

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

    public String getSaying() {
        return saying;
    }

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

Let’s stop our application from running before (if we haven’t) and restart it:

$ mvn clean package spring-boot:run

Now if we navigate to http://localhost:8080/api/hola, we should see the German version of the saying:

successful german

We can now externalize properties that would 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 in which it runs! 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. Spring Boot comes with a prepackaged starter called 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 hola-springboot microservice and add the following Maven dependency within the <dependencies>...</dependencies> section:

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

Now restart your microservice by stopping it and running:

$ mvn clean package spring-boot:run

Just by adding the actuator dependency, our application now has a lot of information exposed that would be very handy for debugging or general microservice insight. Try hitting the following URLs and examine what gets returned:

Here’s an example of what the http://localhost:8080/env endpoint looks like:

successful german

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

How to Run This Outside of Maven?

Up to this point we’ve been thinking through 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 us 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 our hola-springboot microservice project and run the following commands:

$ mvn clean package
$ java -jar target/hola-springboot-1.0.jar

If your project was named demo instead of hola-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 Dropwizard and WildFly Swarm.

Calling Another Service

In a microservice environment, each service is responsible for providing the functionality or service to other collaborators. As we’ve 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-world microservice, we will need to create a service to which we can call using Spring’s REST client functionality. For this example and the rest of the examples, 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.

successful german

If you look in the source code for this book, we’ll see a Maven module called backend which 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 does not use any of the microservice frameworks (Spring Boot, Dropwizard, or WildFly Swarm). 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:

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:

$ mvn clean install jetty:run

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

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

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

We get something like this:

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

We will create a new HTTP endpoint, /api/greeting, in our Spring Boot hola-springboot example and use Spring to call this backend!

Create a new class in src/main/java/com/example called GreeterRestController and fill it in similarly to how we filled it in for the HolaRestController (see Example 2-4).

Example 2-4. src/main/java/com/example/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/hello?greeting={greeting}",
             backendServiceHost, backendServicePort);
        System.out.println("Sending to: " + backendServiceUrl);
        return backendServiceUrl;
    }
}

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

greeting.saying=Hola 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 of this 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.

Here’s what the source looks like when using the RestTemplate (again, the getters/setters omitted, 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 added the @ConfigurationProperties annotation (Example 2-5).

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);

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

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

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

Example 2-6. src/main/java/com/example/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, let’s 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 install jetty:run

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

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

Later on in the book we can see how running these microservices in their own Linux container removes the restriction of port swizzling at runtime. Now, let’s navigate our browser to http://localhost:9090/api/greeting to see if our microservice properly calls the backend and displays what we’re expecting:

successful german

Where to Look Next

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

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

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