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:
-
Duplicate the greeting application into a sibling directory named
fortune-service
and remove the GreetingController, and GreetingProperties classes -
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. -
Review fortune-service’s
application.yml
file. Set thespring.application.name
,management.security.enabled
configuration properties to appropriate values. Also add theserver.port
property, and set it to 8081 -
The new project should have a
FortuneApplication
class, similar to the GreetingApplication class from the original project (consider renaming theGreetingApplication
class you copied over into this new project) -
Run the
FortuneServiceTests
. Do they still pass? If so, terrific.
Write the FortuneController:
-
Create the class
-
Annotate it with the
@RestController
annotation. The controller should autowire an instance of theFortuneService
. -
Add a Spring MVC endpoint (with a
@GetMapping
annotation) to call that service’sgetFortune()
method. Return the fortune wrapped in a simpleMap
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?
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 theGreetingController
and its unit test, theGreetingProperties
andGreetingApplication
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 likefortuneService.baseUrl
-
Consider keeping the existing structure of the
GreetingController
. The main problem is that we no longer have aFortuneService
bean. Imagine having another bean instead, perhaps namedFortuneServiceClient
, 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, theFortuneServiceClient
(orFortuneServiceProxy
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:
-
Make sure the fortune service is running
-
Start the greeting application
-
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.