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.
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 back end 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 GreeterRest Controller and fill it in similarly to how we filled it in for the HolaR estController (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 @ConfigurePro perties 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: