Microservices with Spring Boot

Fred van West , Senior Software Developer , February 16, 2018

What are Microservices?

Introduction

The term "microservices" appears in many technology journals these days. This blog addresses common questions including: "What are microservices, and more importantly, how are they a better solution than what's already out there? Also, if microservices are so great, how easy is it to develop microservices?" Microservices are stand-alone applications that break down functionality into fine-grained components that run and restart independently.

The Problem

The problem with large, monolithic applications is that they are difficult to maintain, test, and extend. Component architectures such as COM, SOA, etc. promised reusability but led to proliferations of libraries that became hard to maintain as well (e.g., DLL hell or JAR hell, etc.). Microservices with Spring Cloud promise faster change, greater availability, fine-grain scaling, and better adaptation to DevOps thinking. They typically use REST and HTTP to decouple components, leading to easier scalability. With Spring Cloud and Spring Boot, you get an opinionated framework that reduces the work required when developing services while offering flexibility when you need to customize your system.

The Solution

Core Characteristics of Microservices Include:

  • Components exposed as services: Services are registered in a registry.
  • Tied to a specific domain: Services can focus on their own work rather than being tied to the workings of other services.
  • Loosely coupled: There's a natural separation of concerns, since the services can focus on their individual work. This narrowed focus reduces concerns such as how the services talk to each other or what's going on inside the other services.
  • Built to tolerate failure: Because components are not intertwined, the system is built to allow services to be automatically retried when a down service returns to health. Systems are assumed to fail, so resiliency is baked right in.
  • Delivered continuously via automation: Services can be updated independently and deployed without disturbing other components.
  • Built and ran by independent teams: Teams can "divide and conquer" by splitting the work into smaller components that don't disturb each other.

Components of Spring Cloud Thanks to Netflix®:

  • Service registry and discovery in Spring Cloud Eureka
  • Circuit breaking technology in Spring Cloud Hystrix
  • Client-side load balancing in Spring Cloud Ribbon
  • Service proxying and API gateway via Spring Cloud Zuul
  • Messaging (Kafka, Rabbit MQ, etc.) through Spring Cloud Stream
  • Data processing pipelines with Spring Cloud Data Flow
  • Convenient name proxies with Feign

Real World Use Cases for Microservices

Our recent work with a client allowed us to have a hands-on experience with microservices in a real-world education environment. We were able to deploy developers across multiple parts of the system and have them work independently to bring the system forward. In addition, we took advantage of a mixed online and local environment, to isolate components used only for development from deployed components. Eventually, we did end up with a local deployment for improved performance (initially getting things working online). Also, we used Vagrant to deploy a complete development environment for the developers working on the front end. This lessened concerns regarding wiring up microservices or having to stub out services, enabling front end developers to come up to speed quickly.

Microservices In Action: A Vending Machine Service

Application Background

Now, let's look at microservices in action for a real-world application. In this case, we are starting with a service to display the inventory in a vending machine. We will illustrate a problem with the application and see how Spring Cloud's Eureka can better separate the client from the server. A client (e.g., a mobile app or mobile-responsive web app) could talk directly to the vending machine. While this can be demonstrated using an HTTP lookup, the following example will simply use localhost.

This code was created using Spring Tool Suite (STS) based on Eclipse and Maven; however, the code was initiated using Spring Initializr at http://start.spring.io.

Application in Action

For our model, we have a vending machine (i.e., a dispenser) containing sleeves of items. Each item is either a can or bottle with a brand associated with it. In our example, the inventory is hard-coded, but live.

Produce an inventory in JSON of the products in the dispenser:
http://localhost:8086/inventory

picture 1

Withdraw a beverage item and update the inventory:
http://localhost:8085/beverage/{line}

picture 2

{line} is a value from 0-4 corresponding to the brand dispensing line in our stock inventory.

To display the current contents in a client, we have a second service that simply calls the above directly to display the current inventory:
http://localhost:8086/dispenser

picture 3

(This is before withdrawing a beverage as in the above URL)

Repeatedly hitting the beverage line will deplete the inventory:

picture 4

One of the many benefits of microservices architecture is to decouple the client from service; we can use the Eureka service registry as a means to discover services rather than hard-coding websites or references into code. Now, if the server remains at a fixed address or domain, we would have no difficulty; however, if the client were, say, embedded within a mobile web application, we would need to update the client every time the server name changed. Moreover, we would need to rely on a hardware load balancer to scale up on load. Instead, we can use the Eureka service to make the server discoverable by the client and we then demonstrate how to decouple the services.

Installing the Application

Steps:

  1. Create a development directory for the Vending Machine software
    1. $ cd <working-directory>
    2. For me, this is
      $ cd /Users/fredericvanwest/Downloads/VendingMachine/checkout
      $ mkdir VendingMachine
      $ cd VendingMachine
  2. Clone the repositories from git
    1. From the UNIX command line, clone the repository
      1. $ git clone https://github.com/fredvanwest/vending-machine-service.git
      2. $ git clone https://github.com/fredvanwest/vending-machine-client.git
    2. This will retrieve the sample repositories and stage them for development
  3. Import the vending-machine-service into Spring Tool Suite
    1. Launch Spring Tool Suite

      picture 5

    2. From Package Explorer, right-click and select "Import…"

      picture 6

    3. Select "Maven > Existing Maven Projects"

      picture 7

    4. Select "Next"
    5. In "root directory" navigate to the working directory:

      picture 8

    6. Click "Open"
    7. At this point, select the /vending-machine-service/pom.xml file:

      picture 9

    8. Click "Finish"
    9. The project should build immediately
    10. Under the "Boot Dashboard", "Local > vending-machine-service", right-click and select "(Re)start":

      picture 10

      picture 11

    11. This will start the service running on port 8086:

      picture 12

      This will start the service running on port 8086

    12. If port 8086 is unavailable, you'll need to adjust in the application.properties file under "src > main > resources":

      picture 13

    13. You can test if the service is running by opening a web browser and hitting the URL
      1. http://localhost:8086/inventory (See above as well)
      2. You should see that the following returned:

        {"sleeves":[{"items":[{"type":"can","brand":"Coke"},{"type":"can","brand":"Coke"},{"type":"can","brand":"Coke"}]},{"items":[{"type":"can","brand":"Diet Coke"},{"type":"can","brand":"Diet Coke"}]},{"items":[{"type":"can","brand":"Sprite"}]},{"items":[{"type":"can","brand":"Dr. Pepper"},{"type":"can","brand":"Dr. Pepper"}]},{"items":[{"type":"bottle","brand":"Dasani"},{"type":"bottle","brand":"Dasani"},{"type":"bottle","brand":"Dasani"},{"type":"bottle","brand":"Dasani"}]}]}​

  4. Import the vending-machine-client into Spring Tool Suite:

    picture 14

    1. From Package Explorer, right-click and select "Import…"
    2. Select "Maven > Existing Maven Projects":

      picture 15

    3. Select "Next"
    4. In "root directory" navigate to the working directory:

      picture 16

      In "root directory" navigate to the working directory

    5. Click "Open"
    6. At this point, select the /vending-machine-client/pom.xml file:

      picture 17

    7. Click "Finish"
    8. The project should build immediately
    9. Under the "Boot Dashboard", "Local > vending-machine-client", right-click and select "(Re)start":

      picture 18

      picture 19

    10. This will start the client running on port 8085
    11. If port 8085 is unavailable, you'll need to adjust in the application.properties file under "src > main > resources":

      picture 20>

    12. You can test that the service is running by opening a web browser and hitting the URL:
      1. http://localhost:8085/dispenser (See above illustrations)
      2. You should see:

        COKE: 3 DIET CODE: 2 SPRITE: 1 DR PEPPER: 2 DASANI:4

      3. Note: The inventory is reset every time that the vending-machine-service is cycled, so the results above can be returned by restarting that service
      4. Also, you can test that the inventory goes down by issuing:
        http://localhost:8086/beverage/0
      5. This will return a JSON representation of an item:
        {"type":"can","brand":"Coke"}
      6. A subsequent call to http://localhost:8085/dispenser will now yield: 

        COKE: 2 DIET CODE: 2 SPRITE: 1 DR PEPPER: 2 DASANI:4

      7. This demonstrates that the COKE inventory dropped by one
      8. Make sure to stop the services in STS before proceeding

  5. Create the Eureka Server
    1. From Package Explorer, right-click and select "New > Spring Starter Project":

      picture 21

    2. From the dialog, change
      1. Name: vending-machine-server
      2. Group: com.vanwest
      3. Artifact: vending-machine-server
      4. Description: Vending Machine Server
      5. Package: com.vanwest
      6. Uncheck default location and select the same root directory as were used for vending-machine-server and vending-machine-client
      7. Make sure to paste "vending-machine-server" on the end of the Location:

        picture 22

      8. Click "Next"
      9. Select "Cloud Discovery > Eureka Server":

        picture 23

      10. Select "Ops > Actuator":

        picture 24

      11. Click "Finish"
    3. Add Eureka server to code
      1. Open "src > main> java > com.vanwest > VendingMachineServerApplication.java"
      2. Above @SpringBootApplication, add @EnableEurekaServer and fix imports

        picture 25

      3. Open "src > main > resources > application.properties", and enter:

        server.port=8761
        eureka.client.register-with-eureka=false
        eureka.client.fetch-registry=false
        eureka.datacenter=phoenix
        eureka.environment=development

        picture 26

    4. Under the "Boot Dashboard", "Local > vending-machine-server", right-click and select "(Re)start":

      picture 27

      picture 28

      Right-click and select "(Re)start"

      1. You can see that Eureka is running by hitting the website:
        http://localhost:8761
      2. You should see that the "test" and "phoenix" environments are displayed
      3. Under "Instances currently registered with Eureka" you should see "No instances available":

        picture 29


  6. Adjust the vending-machine-service to be a Eureka client
    1. From "Package Explorer" right-click on "vending-machine-service" and select "Maven > Add Dependency":

      picture 30

      picture 31

    2. In "Enterd groupi, artifactId or sha1 prefix or pattern (*):" enter: "spring-cloud-starter-eureka"
    3. Select "org.springframework.cloud  spring-cloud-starter-eureka":

      picture 32

    4. Click "OK"
    5. Open "src > main > java > com.vanwest.Vending.Machine.Service > VendingMachineServiceApplication.java"
    6. Above @SpringBootApplication, add @EnableEurekaClient and fix imports:

      picture 33

    7. Save the file
    8. Open "src > main> resources > application.properties" and enter:

      eureka.client.register-with-eureka=true
      eureka.client.fetch-registry=true
      eureka.instance.hostname=localhost

      picture 34

    9. ​Save the file
    10. Right-click on "src > main > resources" and select "New > File" and enter: "bootstrap.properties":

      picture 35

      picture 36

    11. Enter: spring.application.name=vending-machine-service
    12. Save the file
    13. Under the "Boot Dashboard" "Local > vending-machine-service" right-click and select "(Re)start":

      picture 37

      Under the "Boot Dashboard" "Local > vending-machine-service"

      picture 38

    14. Assuming you left vending-machine-server running, open:
      http://localhost:8761
    15. Under "Instances currently registered with Eureka" you should see "VENDING-MACHINE-SERVICE":

      picture 39

    16. You can reassure yourself the service is running with the tests above by opening:
      http://localhost:8086/inventory

  7. Adjust the vending-machine-client to be a Eureka client and use a named servicen in place of the hard-coded address
    1. From "Package Explorer" right-click on "vending-machine-client" and select "Maven > Add Dependency":

      picture 40

      picture 41

    2. In "Enter groupid, artifactId or sha1 prefix or pattern (*):" enter: "spring-cloud-starter-eureka"
    3. Select "org.springframework.cloud spring-cloud-starter-eureka":

      picture 42

    4. Click "OK"
    5. Open "src > main > java > com.vanwest.vendingmachineclient > VendingMachineClientApplication.java"
    6. Above @SpringBootApplication, add @EnableEurekaClient and fix imports:

      picture 43

    7. Open "src > main > java > com.vanwest.vendingmachineclient > VendingController.java":

      picture 44

    8. Under "VendingController" enter:

      ​@LoadBalanced
      @Bean
      public RestTemplate restTemplate(RestTemplateBuilder builder) {
               
      return builder.build();
      }

              @Autowired
              private RestTemplate restTemplate;

    9. In method GetDispenser, replace:

      RestTemplate rest = new RestTemplate();
      Dispenser dispenser = rest.getForObject("http://localhost:8086/inventory",
      Dispenser.class);

      with:

      //RestTemplate rest = new RestTemplate();
      //Dispenser dispenser = rest.getForObject("http://localhost:8086/inventory", 
      //Dispenser.class);
      Dispenser dispenser = restTemplate.getForObject("http://vending-machine-service/inventory", Dispenser.class);

      picture 45

    10. Save the file
    11. Open "src > main> resources > application.properties" and enter:

      eureka.client.register-with-eureka=true
      eureka.client.fetch-registry=true
      eureka.instance.hostname=localhost

      picture 46

    12. Save the file
    13. Right-click on "src > main > resources" select "New > File" and enter: "bootstrap.properties":

      picture 47

      picture 48

    14. Enter: spring.application.name=vending-machine-client
    15. Save the file
    16. Under the "Boot Dashboard", "local > vending-machine-client", right-click and select "(Re)start":

      picture 49

      picture 50

      1. Now open:
        http://localhost:8761

      2. Under "Instances currently registered with Eureka" you should see "VENDING-MACHINE-CLIENT":

        picture 51

      3. You can reassure yourself the service is running with the tests above by hitting:
        http://http://localhost:8086/beverage/0

        picture 52

        and seeing that the inventory is now reduced in:
        http://localhost:8085/dispenser

        picture 53

    So here we can see how a direct named URL was replaced with registry lookup in Eureka. The Spring Cloud framework makes it much easier to create microservices and manage them. Additional features, such as circuit breaker technology, can allow a microservice to halt on fault or load and resume when conditions improve without human intervention. Microservice technology is instrumental in deploying fault-tolerant systems, which is especially true in the education domain.