Technical blogs are rife with stories recounting engineering teams' journeys from monolithic to microservice architectures. Martin Fowler popularized the strangler pattern as one of the main approaches to evolving a monolithic system towards microservices.

In this lab, you will extract a new project: the Fortune Service, out of the Greeting application. The end result should be two collaborating applications whereby the greeting application is a client of the fortune service.

Adhere to the following REST API contract for the fortune service: a single HTTP Get method to the base endpoint (/) of the fortune service should retrieve a fortune, as a json document, for example:

{
  "fortune": "Today will be an awesome day!"
}

This refactoring will necessitate a number of changes:

  • A new and separate project will have to be created for the fortune service application.

  • Fortune-specific logic will have to be moved out of the greeting application, including corresponding unit tests.

  • A FortuneController will be required to expose access to fortunes over HTTP.

The Greeting application will now become a client of the fortune service:

  • Instead of making a direct in-process call against an Autowired Spring bean, the GreetingController will need to be retrofitted to make a REST API call to the Fortune Service to obtain a fortune

  • The unit tests for the GreetingController will no longer work as is: the greeting project no longer has a FortuneService bean to inject into its test (in an upcoming lab, we’ll discuss how moving to microservices affects testing)

Here are additional miscellaneous considerations that are a result of breaking our application in two:

  • To "stand up" both apps on our laptop or VM, we should likely designate each service to run on a unique port. That’s easily achieved by using the spring boot server.port configuration property.

  • The greeting application will need to know the url of the fortune service in order call it. Consider externalizing that url value as a configuration property.

1. Developing the Fortune Service

Given our monolithic application as a starting point, one simple way to begin is as follows:

  1. Duplicate the greeting application into a sibling directory named fortune-service and remove the GreetingController, and GreetingProperties classes

  2. Review the build (build.gradle). Both microservices at this point can have the same build configuration. This service won’t require a templating library, so we can remove the freemarker dependency.

  3. Review fortune-service’s application.yml file. Set the spring.application.name, management.security.enabled configuration properties to appropriate values. Also add the server.port property, and set it to 8081

  4. The new project should have a FortuneApplication class, similar to the GreetingApplication class from the original project (consider renaming the GreetingApplication class you copied over into this new project)

  5. Run the FortuneServiceTests. Do they still pass? If so, terrific.

Write the FortuneController:

  1. Create the class

  2. Annotate it with the @RestController annotation. The controller should autowire an instance of the FortuneService.

  3. Add a Spring MVC endpoint (with a @GetMapping annotation) to call that service’s getFortune() method. Return the fortune wrapped in a simple Map object, using the key fortune, and return the map. Spring will take care of marshalling the Map into a json representation:

    @GetMapping("/")
    public Map<String, String> getFortune() {
      String fortune = fortuneService.getFortune();
      log.info("retrieving fortune '{}'", fortune);
    
      Map<String, String> map = new HashMap<>();
      map.put("fortune", fortune);
      return map;
    }

Start the fortune-service application (gradle bootRun, or from your IDE).

  • Does the application start up properly?

  • Visit the service’s root endpoint on port 8081. Does it return fortunes?

  • Visit the service’s env/ actuator endpoint. Does it come up properly? Does it show the local server port configuration value?

The fortune service

At this point you should have a working fortune-service application. Let’s turn our attention to the client: the Greeting application.

2. Retrofitting the Greeting Application

For a programmatic Rest API client, Spring offers the class RestTemplate. We will use this class to make our HTTP call to the fortune-service.

First steps:

  • Make sure you’re working with the greeting project

  • Delete the FortuneService and its test. You should be left with the GreetingController and its unit test, the GreetingProperties and GreetingApplication class.

  • At this point the GreetingController should no longer compile. we’ll deal with it in a moment

  • In GreetingApplication, create a @Bean-annotated method that will construct and contribute a RestTemplate object to the Spring application context:

    @Bean
    public RestTemplate restTemplate() {
      return new RestTemplate();
    }

As you retrofit the greeting application, consider the following ideas:

  • Instead of hard-coding the base url to the fortune-service application, consider defining a property for it in application.yml, named something like fortuneService.baseUrl

  • Consider keeping the existing structure of the GreetingController. The main problem is that we no longer have a FortuneService bean. Imagine having another bean instead, perhaps named FortuneServiceClient, that encapsulates making the HTTP call to fortune-service: a kind of proxy. In this way, the greeting controller code does not really change: we can just swap the fortune service for a new class, the FortuneServiceClient (or FortuneServiceProxy if you prefer). You can retrofit both the controller and the unit test in this same fashion: instead of mocking the fortune service, now we can mock the fortune service client

Write your FortuneServiceClient:

  • Annotate it with @Component so Spring can detect it as a bean

  • You’ll need to inject or autowire the RestTemplate object

  • Also, inject the fortune service base url configuration property using the @Value annotation.

  • Finally, expose a method that will use the restTemplate to call the fortune service at its base url. this should return a Map object, from which we can extract and return the fortune.

Here’s the resulting class:

package io.pivotal.training.greeting;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class FortuneServiceClient {
  private RestTemplate restTemplate;

  @Value("${fortuneService.baseUrl}")
  private String baseUrl;

  public FortuneServiceClient(RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
  }

  public String getFortune() {
    Map<String,String> result = restTemplate.getForObject(baseUrl, Map.class);
    return result.get("fortune");
  }
}

3. Testing the Refactored system

Re-run the unit tests for the greeting application. Make sure they still pass.

Manually verify that the greeting app is still functioning:

  1. Make sure the fortune service is running

  2. Start the greeting application

  3. Visit the greeting application root endpoint. do fortunes still show on the page?

If you were to log calls to the fortune controller endpoint, you should see a log message coming from the fortune controller each time you refreshed the greeting application in your browser. Similarly, if fortune-service is started in debug mode, with a breakpoint in the fortune controller, every call to the greeting app should trigger the breakpoint.

4. Congratulations

You’ve had to roll up our sleeves and make a number of changes to extract the fortune service from the greeting application.

Next, let’s talk about testing.