© Binildas Christudas 2019
Binildas ChristudasPractical Microservices Architectural Patternshttps://doi.org/10.1007/978-1-4842-4501-9_10

10. Microservice Performance

Binildas Christudas1 
(1)
Trivandrum, Kerala, India
 
When you move from the traditional three-tier or n-tier architecture to the microservices architecture, one main observable characteristic is the increased number of interconnections between microservice processes. A computation that once took place completely within a single process now might get split into many microcomputations spanning across processes. This increases the number of interprocess communications, the number of context switches, the number of I/O operations involved, and so on. So it makes right sense to have a look at these aspects in more detail. In this chapter, you will explore a few performance aspects to be considered in microservices and you will look into the following:
  • Synchronous vs. asynchronous HTTP

  • The Servlet 3.0 spec for asynchronous HTTP

  • Code to demonstrate asynchronous HTTP between browser and microservice

  • Code to demonstrate asynchronous HTTP between microservices

  • The Google Protocol Buffer

  • Code to demonstrate using the Google Protocol Buffer between microservices

Communication Across the Outer Architecture

The “Outer Architecture Perspective” section in Chapter 4 introduced the outer architecture of microservices. There you saw how the internal complexities of a typical monolith architecture pop out to the “surface” when you move to a microservices-based architecture. One obvious change is the split of a single or a few computational processes into many more similar, but smaller computationally intensive processes. This change is better explained with the help of Figure 10-1.
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig1_HTML.jpg
Figure 10-1

Monolith vs. microservices boundaries

In the traditional monolith, the multiple components that form the building blocks of a service realization get executed in a single container process. Method invocations or messaging between these components are “local” or “in process.” This will allow processes, whether a container-managed process or a simple Java application process, to make a lot of assumptions and optimizations and complete the execution. However, when you rearchitect the same service from the monolith into the microservices architecture, the implementation gets split and deployed into more than one process. This is shown in Figure 10-1.

Interprocess communications are heavyweight compared to communications within a process. They involve more context switches, network wait and I/O operations, data marshalling and unmarshalling, etc. This increases the complexity involved in the outer architecture space of a microservices architecture.

Further, every microservice involves socket listeners listening to incoming traffic. So, compared to a traditional monolith, microservices architecture involves many more socket listeners, associated threads to process incoming traffic, and so on. A look into the details of a few of these handpicked concerns and analyzing the options to address them will give you a fair enough idea of the whole landscape.

Asynchronous HTTP

You briefly looked at distributed messaging in Chapter 6, where you also appreciated the differences between the synchronous and asynchronous style of communications between microservices. You saw that messaging is a first-class technology by which microservices can communicate asynchronously, and you saw via examples how you can correlate many asynchronous message transmissions so as to weave them together as pieces of a single, end-to-end request/response cycle. However, all of the samples in Chapter 8 used the HTTP protocol for intercommunications between microservices. You will look at the nature of these communications more closely in this section.

The Bad and the Ugly Part of HTTP

As stated, all of the samples in Chapter 8 use the HTTP protocol for intercommunications between microservices. If you examine these interactions again, you can observe that most of them are synchronous HTTP calls. Close observation of these communications reveals that an incoming request from a consumer microservice to your provider microservice server will capture one servlet connection and perform a blocking call to the remote service before it can send a response to the consumer microservice. It works, but it does not scale effectively if you have many such concurrent clients. Synchronous HTTP is a best fit for a user using a client application to get instant feedback or a response; however, that is not the case when it comes to server-side processing.

APIs for Asynchronous HTTP Processing

Typical containers in application servers like in Apache Tomcat normally use a server thread per client request. Under increased load conditions, this necessitates containers to have a large amount of threads to serve all the client requests. This limits scalability, which can arise due to running out of memory or exhausting the pool of container threads. Java EE has added asynchronous processing support for servlets and filters from the Servlet 3.0 spec onwards. A servlet or a filter, on reaching a potentially blocking operation when processing a request, can assign the operation to an asynchronous execution context and return the thread associated with the request immediately to the container without waiting to generate a response. The blocking operation can later complete in the asynchronous execution context in some different thread, which can generate a response or dispatch the request to another servlet. The javax.servlet.AsyncContext class is the API that provides the functionality that you need to perform asynchronous processing inside service methods. The startAsync() method on the HttpServletRequest object of your service method puts the request into asynchronous mode and ensures that the response is not committed even after exiting the service method. You have to generate the response in the asynchronous context after the blocking operation completes or dispatch the request to another servlet.

The programming model is straightforward. Let’s try to understand the difference by first looking at the pseudo code for the traditional programming model of a Controller class. See Listing 10-1.
@RestController
public class ProductRestController{
    @RequestMapping(value = "/products", method = RequestMethod.GET ,
        produces = {MediaType.APPLICATION_JSON_VALUE})
    public List<Product> getAllProducts() {
        //return "List of All Products";
    }
}
Listing 10-1

Pseudo Code for a Sync Controller

When you use Spring, the controller method can return a java.util.concurrent.Callable to complete processing asynchronously. Spring MVC will then invoke the Callable in a separate thread with the help of a TaskExecutor. A TaskExecutor is Spring’s abstraction for asynchronous execution and scheduling of tasks. The rewritten pseudo code is shown in Listing 10-2.
@RestController
public class ProductRestController{
    @RequestMapping(value = "/products", method = RequestMethod.GET ,
        produces = {MediaType.APPLICATION_JSON_VALUE})
    public Callable<List<Product>> getAllProducts() {
        return new Callable<List<Product>>() {
            public Object call() throws Exception {
                // ...
                // return "List of All Products";
            }
        };
    }
}
Listing 10-2

Pseudo Code for an Async Controller Using Callable

Spring 3.2 introduced org.springframework.web.context.request.async.DeferredResult, which can again be returned by a Controller class. DeferredResult provides an alternative to using a Callable for asynchronous request processing. While a Callable is executed concurrently on behalf of the application, with a DeferredResult the application can produce the result from a thread of its choice. Here, the processing can happen in a thread not known to Spring MVC, similar to the case when you need to get a response from a messaging channel, and so on. So, when you use DeferredResult, even after you leave the controller handler method, the request processing is not done. Instead, Spring MVC (using Servlet 3.0 capabilities) will hold on with the response, keeping the idle HTTP connection. Even though the HTTP Worker Thread is no longer used, the HTTP connection is still open. At a later point in time, some other thread will resolve DeferredResult by assigning some value to it. Spring MVC will immediately pick up this event and send a response to the client, finishing the request processing.

The pseudo code for an async scenario using DeferredResult is shown in Listing 10-3.
@RestController
public class ProductRestController{
    @RequestMapping(value = "/products", method = RequestMethod.GET ,
        produces = {MediaType.APPLICATION_JSON_VALUE})
    public DeferredResult<List<Product>> getAllProducts() {
        DeferredResult<List<Product>> deferredResult =
            new DeferredResult<List<Product>> ();
        // Add deferredResult to an internal memory holder
        // like a Queue or a Map...
        return deferredResult;
    }
    // In some other thread...
    deferredResult.setResult(data);
    // Remove deferredResult from the internal memory holder
}
Listing 10-3

Pseudo Code for an Async Controller Using DeferredResult

Java 8 introduced java.util.concurrent. CompletableFuture , which can be used with Spring to create async endpoints that similarly free up request threads to perform other tasks. In this case, the DeferredResult.setResult may simply be replaced with CompletableFuture.complete. A CompletableFuture is a java.util.concurrent.Future . A Future represents the result of an asynchronous computation whereas a CompletableFuture is a Future that may be explicitly completed (setting its value and status) and may be used as a CompletionStage, supporting dependent functions and actions that trigger upon its completion. CompletableFuture makes handling complex asynchronous programming easier. It even lets you combine and cascade async calls, and also offers the static utility methods runAsync and supplyAsync to abstract away the manual creation of threads. See Listing 10-4.
@RestController
public class ProductRestController{
    @RequestMapping(value = "/products", method = RequestMethod.GET,
        produces = {MediaType.APPLICATION_JSON_VALUE})
    public CompletableFuture<List<Product>> getAllProducts() {
        Future<List<Product>> future =
            new CompletableFuture<List<Product>>();
        return CompletableFuture.supplyAsync(() -> "in the background");
    }
}
Listing 10-4

Pseudo Code for an Async Controller using CompletableFuture

Design a Scenario to Demo Async HTTP Between Microservices

You will use a trimmed down version of the same components used for previous examples. So your sample here will consist of three main components: an HTML-based client app and two microservices, as shown in Figure 10-2. I have removed all complexities of HATEOAS and any data repositories so that you can concentrate on async HTTP alone. Once you appreciate the design and the main components used to implement it, then you should be able to use a similar pattern for your other complex business scenarios.

In the design shown, the client app communicates with the Product Web microservice using the HTTP protocol; however, you want the client to do it using the async HTTP model. Similarly, the Product Web microservice communicates with the Product Server microservice, again using the HTTP protocol, and this intermicroservices communication is designed using the async HTTP model.
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig2_HTML.jpg
Figure 10-2

Design an async HTTP scenario in Spring Boot

Let’s walk through the design in more detail by referring to the code snippets so that the concepts will be more clear.

Code to Use Async HTTP in Spring Boot

All the code samples for this section are in folder ch10\ch10-01. Visit pom.xml to see the explicit mention of the spring-boot-starter-web dependency for your Product Server microservice. See Listing 10-5.
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Listing 10-5

Adding spring-boot-starter-web to Spring Boot (ch10\ch10-01\ProductServer\pom.xml)

spring-boot-starter-web will add Tomcat and Spring MVC to the Product Server microservice.

Next, look at the Boot main application class shown in Listing 10-6.
@SpringBootApplication
@EnableAsync
public class EcomProductMicroserviceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EcomProductMicroserviceApplication.class, args);
    }
}
Listing 10-6

Async Enabled Spring Boot Application (ch10\ch10-01\ProductServer\src\main\java\com\acme\ecom\product\EcomProductMicroserviceApplication.java)

The @EnableAsync annotation switches on Spring’s ability to run @Async methods in a background thread pool. This class also customizes the Executor backing the thread pool. By default, a SimpleAsyncTaskExecutor is used. The SimpleAsyncTaskExecutor does not reuse threads. Even though it supports limiting concurrent threads through the concurrencyLimit bean property, by default the number of concurrent threads is unlimited. In serious applications you should consider a thread-pooling TaskExecutor implementation instead.

Next, look at the Service component where you implement the async processing logic, shown in Listing 10-7.
@Service
public class ProductService{
    @Async
    public CompletableFuture<List<Product>> getAllProducts()
            throws InterruptedException{
        List<Product> products = getAllTheProducts();
        if (products.size() == 0) {
            LOGGER.debug("No Products Retreived from the repository");
        }
        LOGGER.debug(Thread.currentThread().toString());
        Thread.sleep(2000L); // Delay to mock a long running task
        return CompletableFuture.completedFuture(products);
    }
}
Listing 10-7

Service Component Implementing Async Processing (ch10\ch10-01\ProductServer\src\main\java\com\acme\ecom\product\service\ProductService.java)

The class is marked with the @Service annotation , making it a candidate for Spring’s component scanning to detect it and add it to the application context. Next, the getAllProducts method is marked with Spring’s @Async annotation, indicating it will run on a separate thread. The method’s return type is CompletableFuture<List<Product>> instead of <List<Product>>, a requirement for any asynchronous service. This code uses the completedFuture method to return a CompletableFuture instance, which is already completed with the collection of results queried from the repository (a processing logic supposed to be taking considerable amount of time to complete).

Now, look at the Rest Controller of the Product Server microservice, shown in Listing 10-8.
@RestController
public class ProductRestController {
    private final ProductService productService;
    @Autowired
    public ProductRestController(ProductService productService) {
        this.productService = productService;
    }
    @RequestMapping(value = "/products", method = RequestMethod.GET,
        produces = {MediaType.APPLICATION_JSON_VALUE})
    public CompletableFuture<List<Product>> getAllProducts()
            throws InterruptedException, ExecutionException{
        LOGGER.debug(Thread.currentThread().toString());
        return productService.getAllProducts()
            .thenApply(products -> {
                products.forEach(item->LOGGER.debug(item.toString()));
                LOGGER.debug(Thread.currentThread().toString());
                return products;
            });
    }
}
Listing 10-8

Product Server Rest Controller Facilitating Async (ch10\ch10-01\ProductServer\src\main\java\com\acme\ecom\product\controller\ProductRestController.java)

Note that the CompletableFuture response returned by productService.getAllProducts() allows you to manually control the moment when the future returns and also transform the output in the process. You then convert this simple CompletableFuture into the format you need with the help of thenApply() , which allows you to log some data about the current thread to make sure that the execution really happens asynchronously, that is, the thread that is finishing the work is not the thread that started the work. The Rest Controller too then returns the collection of the products once again as another CompletableFuture<List<Product>> so that Spring MVC will also act to execute the HTTP method in an async manner, making it a smartly implemented microservice! Keep in mind that this is an independent microservice; you will next look at a dependent microservice that depends on this independent microservice (as is shown in the design).

The dependent microservice is the Product Web microservice. The main code lies in the Controller class , shown in Listing 10-9.
@RestController
public class ProductRestController{
    private final AsyncRestTemplate asyncRestTemplate =
        new AsyncRestTemplate();
    private static String PRODUCT_SERVICE_URL =
        "http://localhost:8080/products/";
    @RequestMapping(value = "/productsweb", method = RequestMethod.GET,
        produces = {MediaType.APPLICATION_JSON_VALUE})
    public DeferredResult<List<Product>> getAllProducts() {
        LOGGER.debug(Thread.currentThread().toString());
        DeferredResult<List<Product>> deferredResult = new DeferredResult<>();
        ParameterizedTypeReference<List<Product>> responseTypeRef =
            new ParameterizedTypeReference<List<Product>>() {};
        ListenableFuture<ResponseEntity<List<Product>>> entity =
            asyncRestTemplate.exchange(PRODUCT_SERVICE_URL,
            HttpMethod.GET, (HttpEntity<Product>) null, responseTypeRef);
        entity.addCallback(new
            ListenableFutureCallback<ResponseEntity<List<Product>>>() {
                @Override
                public void onFailure(Throwable ex) {
                    LOGGER.debug(Thread.currentThread().toString());
                    LOGGER.error(ex.getMessage());
                }
                @Override
                public void onSuccess(ResponseEntity<List<Product>> result) {
                    List<Product> products = result.getBody();
                    products.forEach(item->LOGGER.debug(item.toString()));
                        LOGGER.debug(Thread.currentThread().toString());
                        deferredResult.setResult(products);
                }
        });
        LOGGER.debug(Thread.currentThread().toString());
        return deferredResult;
    }
}
Listing 10-9

Microservice Calling Async HTTP on Another Microservice (ch10\ch10-01\ProductWeb\src\main\java\com\acme\ecom\product\controller\ProductRestController.java)

The PRODUCT_SERVICE_URL here refers to the independent microservice. The Product Web microservice has to invoke the Product Server microservice. You have already made the Product Server microservice methods asynchronous. Let’s now make the Product Web microservice method asynchronous too so that all the containers hosting these microservices can better utilize container resources by implementing asynchronous characteristics to the runtime model. You are not done; you want to invoke the Product Server microservice too in an asynchronous mode so that both the microservice implementations as well as the intermicroservices communications are in smart, asynchronous mode!

org.springframework.web.client.AsyncRestTemplate is Spring’s central class for asynchronous client-side HTTP access. It exposes methods similar to those of RestTemplate; however, it returns ListenableFuture wrappers as opposed to concrete results. By default, AsyncRestTemplate relies on standard JDK facilities to establish HTTP connections. Here again you can switch to a different HTTP library such as Apache HttpComponents, Netty, or OkHttp by using a constructor accepting an AsyncClientHttpRequestFactory. The AsyncRestTemplate gives you a ListenableFuture , so the container thread will not wait for the response to be received back; instead it will continue with the next steps of processing since you are using AsyncRestTemplate. ListenableFuture has the capability to accept completion callbacks, so you add callbacks on failure and success scenarios. If success, you set the result to the body of the DeferredResult . Since the Spring MVC is holding on to the response through the idle HTTP connection, as soon as you set the result to the DeferredResult, the client will receive it.

product_service.js has the code that the Angular JS client will use to invoke the Product Web microservice; see Listing 10-10.
App.factory('ProductService', ['$http', '$q', function($http, $q){
    return {
        getApplication: function() {
            return $http.get('http://localhost:8081/productsweb/')
                .then(
                    function(response){
                        return response.data;
                    },
                    function(errResponse){
                        console.error('Error while fetching application');
                        return $q.reject(errResponse);
                    }
                );
        }
    };
}]);
Listing 10-10

Web Client Invoking Async HTTP (ch10\ch10-01\ProductWeb\src\main\resources\static\js\service\product_service.js)

Build and Test Asynchronous HTTP Between Microservices

The complete code required to demonstrate asynchronous HTTP between microservices is in folder ch10\ch10-01. You don’t require MongoDB for this sample. You can build, pack, and run the different microservices in the following order.

Bring up the Product Server microservice first:
cd ch10\ch10-01\ProductServer
D:\binil\gold\pack03\ch10\ch10-01\ProductServer>make
D:\binil\gold\pack03\ch10\ch10-01\ProductServer>mvn -Dmaven.test.skip=true clean package
D:\binil\gold\pack03\ch10\ch10-01\ProductServer>run
D:\binil\gold\pack03\ch10\ch10-01\ProductServer>java -jar -Dserver.port=8080 .\target\Ecom-Product-Microservice-0.0.1-SNAPSHOT.jar

Refer to Appendix D to get an overview of cURL and Postman. You can use any of these tools to test the above microservice by pointing to http://localhost:8080/products/.

Next, bring up the Product Web microservice:
cd ch10\ch10-01\ProductWeb
D:\binil\gold\pack03\ch10\ch10-01\ProductWeb>make
D:\binil\gold\pack03\ch10\ch10-01\ProductWeb>mvn -Dmaven.test.skip=true clean package
D:\binil\gold\pack03\ch10\ch10-01\ProductWeb>run
D:\binil\gold\pack03\ch10\ch10-01\ProductWeb>java -jar -Dserver.port=8081 .\target\Ecom-Product-Microservice-0.0.1-SNAPSHOT.jar

You can again use cURL or Postman to test the Product Web microservice pointing to http://localhost:8081/productsweb/.

Alternatively, you may open this HTML utility preferably in the Chrome browser:
ch10\ch10-01\ProductWeb\src\main\resources\product.html.

Upon loading itself, the browser client will fire a request to Product Web, listening on port 8081, which will delegate the request to the Product Server microservice listening on port 8080. All is good until now, as far as the request routes are concerned. In fact, the HTTP conduit between the client and the Product Web microservice as well as the one between the Product Web microservice and the Product Server microservice are kept open and idle, and the container threads in both the microservices will return to be available for context switching. So, your Postman client or the HTML utility client will keep waiting over the idle HTTP connection. On the server side, processing happens in different threads and as soon as the results are available, they are written back to the open HTTP connection.

The thread dynamics can be understood by carefully observing the logs of the microservices consoles. Listing 10-11 shows the console output for the called (ProductServer) microservice.
D:\binil\gold\pack03\ch10\ch10-01\ProductServer>run
D:\binil\gold\pack03\ch10\ch10-01\ProductServer>java -jar -Dserver.port=8080 .\target\Ecom-Product-Microservice-0.0.1-SNAPSHOT.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.4.RELEASE)
2019-02-23 10:11:33 INFO  StartupInfoLogger.logStarting:48 - Starting
2019-02-23 10:11:40 INFO  StartupInfoLogger.logStarted:57 - Started EcomProductMicroserviceApplication in 8.231 seconds (JVM running for 9.827)
2019-02-23 10:13:34 INFO  ProductRestController.getAllProducts:94 - Start
2019-02-23 10:13:34 DEBUG ProductRestController.getAllProducts:95 - Thread[http-nio-8080-exec-10,5,main]
2019-02-23 10:13:34 INFO  ProductService.getAllProducts:78 - Start
2019-02-23 10:13:34 DEBUG ProductService.getAllProducts:79 - Fetching all the products from the repository
2019-02-23 10:13:34 INFO  ProductService.getAllTheProducts:109 - Start
2019-02-23 10:13:34 INFO  ProductService.getAllTheProducts:136 - Ending...
2019-02-23 10:13:34 DEBUG ProductService.getAllProducts:86 - Thread[SimpleAsyncTaskExecutor-1,5,main]
2019-02-23 10:13:34 INFO  ProductService.getAllProducts:87 - Ending
2019-02-23 10:13:36 DEBUG ProductRestController.lambda$null$1:100 - Product [productId=1, ...]
2019-02-23 10:13:36 DEBUG ProductRestController.lambda$null$1:100 - Product [productId=2, ...]
2019-02-23 10:13:36 DEBUG ProductRestController.lambda$getAllProducts$2:101 - Thread[SimpleAsyncTaskExecutor-1,5,main]
Listing 10-11

Console Log of ProductServer (Called) Microservice

Listing 10-11 shows that ProductRestController gets executed in the context of Thread[http-nio-8080-exec-10,5,main] and so is the case with  ProductService. In ProductService, just after the ProductService.getAllTheProducts:136 - Ending... log has happened, the following two lines of code release the executing thread:
Thread.sleep(2000L); // Delay to mock a long running task
return CompletableFuture.completedFuture(products);
You need to differentiate between the above two lines of code, and I will explain them:
  1. 1.
    The first line just makes a delay so that the caller (line of code from ProductRestController of the ProductWeb microservice reproduced below) will feel a perceived delay from the called microservice:
    ListenableFuture<ResponseEntity<List<Product>>> entity =
        asyncRestTemplate.exchange(PRODUCT_SERVICE_URL, HttpMethod.GET,
        (HttpEntity<Product>) null, responseTypeRef);
     
  2. 2.

    The second line of code actually returns the executing thread, so Thread[http-nio-8080-exec-10,5,main] will be returned back to the embedded web container’s HTTP pool.

     

Later when CompletableFuture.completedFuture(products) resumes, it will get executed in some other thread context (Thread[SimpleAsyncTaskExecutor-1,5,main] in your case, as shown in Listing 10-11).

Similarly, Listing 10-12 shows the console output for the called (ProductServer) microservice.
D:\binil\gold\pack03\ch10\ch10-01\ProductWeb>run
D:\binil\gold\pack03\ch10\ch10-01\ProductWeb>java -jar -Dserver.port=8081 .\target\Ecom-Product-Microservice-0.0.1-SNAPSHOT.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.4.RELEASE)
2019-02-23 10:12:33 DEBUG StartupInfoLogger.logStarting:51 - Running with Spring Boot v1.5.4.RELEASE, Spring v4.3.9.RELEASE
2019-02-23 10:12:38 INFO  StartupInfoLogger.logStarted:57 - Started EcomProductMicroserviceApplication in 6.804 seconds (JVM running for 8.494)
2019-02-23 10:13:34 INFO  ProductRestController.getAllProducts:87 - Start
2019-02-23 10:13:34 DEBUG ProductRestController.getAllProducts:88 - Thread[http-nio-8081-exec-7,5,main]
2019-02-23 10:13:34 DEBUG ProductRestController.getAllProducts:113 - Thread[http-nio-8081-exec-7,5,main]
2019-02-23 10:13:37 DEBUG ProductRestController$2.lambda$onSuccess$0:107 - Product [productId=1, ...]
2019-02-23 10:13:37 DEBUG ProductRestController$2.lambda$onSuccess$0:107 - Product [productId=2, ...]
2019-02-23 10:13:37 DEBUG ProductRestController$2.onSuccess:108 - Thread[SimpleAsyncTaskExecutor-1,5,main]
Listing 10-12

Console Log of ProductWeb (Caller) Microservice

Listings 10-11 and 10-12 assert that the background processing happens in threads from SimpleAsyncTaskExecutor whereas the container threads of the format http-nio-*.* have already completed the request part of the transaction and were given back to the pool. This is a powerful way of bridging the synchronous, blocking paradigm of a RESTful interface with the asynchronous, non-blocking processing performed on the server side.

Google Protocol Buffer Between Spring Boot Microservices

The previous section talked about effectively utilizing the microservices’ server resources like HTTP connections and threads. Equally important are the other possible optimizations in intercommunications between microservices. Since microservices are spread across process boundaries, the amount of data sent across microservices matter. This boils down to the marshalling and unmarshalling of data structures across microservices. You will look into this with examples in this section.

Protocol Buffer

Protocol buffers are Google’s platform-neutral and language-neutral mechanism for marshalling and unmarshalling structured data. You need to define how you want your data to be structured to use Protocol Buffer, and then you can use generated source code in multiple platforms and languages to easily write and read your structured data to and from a variety of data streams. One benefit of using Protocol Buffer is that you can even update your data structure without breaking already deployed application code that is compiled against the “old” format. This is especially important in architecting applications capable of adapting or evolving to future requirement changes.

You will now look into the dynamics of using Protocol Buffer. You first need to specify how you want the information you’re serializing to be structured in .proto files . In doing so, you use the Protocol Buffer message types. Each Protocol Buffer message is a logical grouping of information, containing a series of name-value pairs. Listing 10-13 is a sample of a .proto file that defines a message containing information about a Product, which you used in all of your previous samples.
message Product {
    string productId = 1;
    string name = 2;
    string code = 3;
    string title = 4;
    string description = 5;
    string imgUrl = 6;
    double price = 7;
    string productCategoryName = 8;
}
Listing 10-13

Sample .proto File

Each message (message Product in your sample) type has one or more uniquely numbered fields, and each field has a name and a value type, where value types can be numbers (integer or floating-point), bools, strings, or raw bytes. Your Protocol Buffer message can also be other protocol buffer message types, as shown in the container message defined in Listing 10-14 to hold a collection of Product instances.
message Products {
    repeated Product product = 1;
}
Listing 10-14

.proto File for a Collection of Other .proto Types

You can specify fields as optional, required, and/or repeated, as shown in the Products message.

Once you define your messages, you can run the Protocol Buffer compiler for the application’s programming language of your choice on your .proto file to generate entity classes. These entity classes provide you with simple accessors for each field (like code() and set_code()) as well as methods to serialize/parse the whole structure to/from raw bytes. In your scenario, if your chosen language is Java, running the compiler on the above sample will generate a class called Product. You can use this entity class in your application to marshal and unmarshal Product protocol buffer messages. Your code will look like that in Listing 10-15.
Product product =
    Product.newBuilder().setProductId(id)
        .setName("Kamsung D3")
        .setCode("KAMSUNG-TRIOS")
        .setTitle("Kamsung Trios 12 inch , black , 12 px ....")
        .setDescription("Kamsung Trios 12 inch with Touch")
        .setImgUrl("kamsung.jpg")
        .setPrice(12000.00)
        .setProductCategoryName("Mobile").build();
// write
FileOutputStream output = new FileOutputStream("D:/Product.ser");
product.writeTo(output);
output.close();
// read
Product productFromFile = Product.parseFrom(new FileInputStream("D:/Product.ser"));
Listing 10-15

Marshalling and Unmarshalling .proto Types

You can now add new fields to your message formats without breaking backwards-compatibility; any old program binaries will ignore any new fields when parsing. So by using protocol buffers as the data format, you can extend your message protocol without having to worry about breaking existing code.

Google’s documentation on Protocol Buffer lists the advantages of using Protocol Buffer as follows:
  • Simpler

  • 3 to 10 times smaller

  • 20 to 100 times faster

  • Less ambiguous

  • Generate data access classes that are easier to use programmatically

The second and third bullet points are of great significance especially when you want to design numerous chit-chat kinds of interactions between microservices, which are again too many in number in any serious enterprise-grade application.

Let’s now look at a complete example to explain the usage of Protocol Buffer between microservices.

A Scenario to Demonstrate Protocol Buffer Between Microservices

You will modify the same example you used in your earlier demonstrations to leverage Protocol Buffer. You will use a trimmed down version of the same components used in Chapter 8. So your example here will consist of three main components: an HTML-based client app and two microservices, as shown in Figure 10-3. Here again I have removed all complexities of HATEOAS and data repositories so that you can concentrate on the usage of Protocol Buffer alone.
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig3_HTML.jpg
Figure 10-3

Use Protocol Buffer between microservices

In your example design, you use a .proto file where you spec out the entities you want to use. The next step is to run the Protocol Buffer compiler for the application’s programming language (Java, in your case) on your .proto file to generate similar classes. Now both the dependent and the independent microservices can be programmed against the generated entity classes, thus making intermicroservices communication using protocol buffer a straightforward step.

Code to Use Protocol Buffer in Spring Boot

All the code samples for this section are in folder ch08\ch10-02. You will look at the code for the independent microservice first, the Product Server microservice. Visit pom.xml to see the explicit mention of the protobuf-java and the protobuf-java-format dependency for your Product Server microservice. See Listing 10-16.
<project>
    <properties>
        <protobuf-java.version>3.1.0</protobuf-java.version>
        <protobuf-java-format.version>1.4</protobuf-java-format.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf-java.version}</version>
        </dependency>
        <dependency>
            <groupId>com.googlecode.protobuf-java-format</groupId>
            <artifactId>protobuf-java-format</artifactId>
            <version>${protobuf-java-format.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>com.github.os72</groupId>
                <artifactId>protoc-jar-maven-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <protocVersion>3.1.0</protocVersion>
                            <includeStdTypes>true</includeStdTypes>
                            <includeDirectories>
                                <include>
                                    src/main/resources
                                </include>
                            </includeDirectories>
                            <inputDirectories>
                                <include>
                                    src/main/protobuf
                                </include>
                            </inputDirectories>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
Listing 10-16

Adding a Protocol Buffer Compiler and Runtime to the Maven Build (ch10\ch10-02\ProductServer\pom.xml)

protoc-jar-maven-plugin performs protobuf code generation using the multi-platform executable protoc-jar. At build time, this Maven plugin detects the platform and executes the corresponding protoc binary so that it can compile .proto files using protoc-jar embedded protoc compiler, providing some portability across the major platforms (Linux, Mac/OSX, and Windows). In the pom.xml above, the compiler compiles .proto files in the main cycle and places the generated files into target/generated-sources, including google.protobuf standard types, and includes any additional imports required.

The protobuf-java Maven artifact provides the Java APIs required to serialize the generated message objects in Java. So it acts as the runtime for Protocol Buffer.

Note that the compiler version should be same as the Java API version. In your sample, you use the 3.1.0 version.

The protobuf-java-format Maven artifact provides serialization and deserialization of different formats based on Google’s protobuf Message. It enables overriding the default (byte array) output to text based formats such as XML, JSON, and HTML.

Now, look at the Protocol Buffer type declaration shown in Listing 10-17.
syntax = "proto3";
package binildas;
option java_package = "com.acme.ecom.product.model";
option java_outer_classname = "ECom";
message Products {
    repeated Product product = 1;
}
message Product {
    string productId = 1;
    string name = 2;
    string code = 3;
    string title = 4;
    string description = 5;
    string imgUrl = 6;
    double price = 7;
    string productCategoryName = 8;
}
Listing 10-17

Type Declaration in product.proto (ch10\ch10-02\ProductServer\src\main\resources\product.proto)

Since you use version 3 of both the protocol buffer compiler and the protocol buffer language runtime, the .proto file must start with the syntax = “proto3” declaration. If a compiler version 2 is used instead, this declaration would be omitted.

The .proto file should next have a package declaration, which helps prevent naming conflicts between different type declarations across different projects. In Java, this package name is also used as the Java package unless you have explicitly specified a java_package, as you have here.

Next are two Java-specific options: the java_package and java_outer_classname. The java_package option specifies in what Java package your generated classes should live. If you don’t specify this explicitly, it matches the package name given by the package declaration. The java_outer_classname option defines the container class name, which should contain all of the classes generated in the type definition file.

Next, you add a message for each data structure you want to serialize and then specify a name and a type for each field in the message. A message is an aggregate containing a set of typed fields. Many standard simple data types are available as field types, including bool, int32, float, double, and string. You can also add other message types as field types; in your example, the Products message contains Product messages. The = 1, = 2, = 3, etc. are markers on each element to identify the unique “tag” that the field uses in the binary encoding.

Fields can be annotated with one of the following modifiers:
  • required: Indicates that a value for the field must be provided; otherwise the message will be considered “uninitialized.”

  • optional: Indicates that the field may or may not be set. If an optional field value isn’t set, a default value is used, like zero for numeric types, the empty string for strings, false for Booleans, etc.

  • repeated: Indicates that the field may be repeated any number of times (including zero). The order of the repeated values will be preserved in the protocol buffer.

Next, look at the Product Server Controller in Listing 10-18.
package com.acme.ecom.product.controller;
import com.acme.ecom.product.model.ECom.Product;
import com.acme.ecom.product.model.ECom.Products;
@RestController
public class ProductRestController {
    @RequestMapping(value = "/products", method = RequestMethod.GET)
    public  Products getAllProducts() {
        List<Product> products = getAllTheProducts();
        if(products.isEmpty()){
            LOGGER.debug("No products retreived from repository");
        }
        products.forEach(item->LOGGER.debug(item.toString()));
        Products productsParent =
            Products.newBuilder().addAllProduct(products).build();
        return productsParent;
    }
}
Listing 10-18

Product Rest Controller to Emit Protocol Buffer (ch10\ch10-02\ProductServer\src\main\java\com\acme\ecom\product\controller\ProductRestController.java)

There is no major noticeable difference in the Rest Controller except the fact that you need to import Products and Product Java types defined within the outer container class, ECom.

You now need to instrument the microservice runtime with the required libraries to serialize and deserialize to the Protocol Buffer format. Spring’s ProtobufHttpMessageConverter comes handy to internally leverage Google’s libraries. This converter supports by default application/x-protobuf and text/plain with the official com.google.protobuf:protobuf-java library. It is to be noted that other formats can also be supported with the correct additional libraries on the classpath. You will instantiate this bean in a configuration class; see Listing 10-19.
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
@Configuration
public class ProductRestControllerConfiguration{
    @Bean
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }
}
Listing 10-19

Configuring Protocol Buffer Message Converter (ch10\ch10-02\ProductServer\src\main\java\com\acme\ecom\product\controller\ProductRestControllerConfiguration.java)

That’s all for the Product Server microservice. Now you will look at the code for the Product Web microservices. All of the code snippets you have seen and the explanations I have made for the Product Server microservice are still valid, so I will not repeat them here for brevity; instead, I will explain any additional requirements.

The Product Web microservice must delegate requests to the Product Server microservice. To make this delegation work, you need a bean of the RestTemplate type to be registered in a configuration class. This RestTemplate is injected with another bean of the ProtobufHttpMessageConverter type, which will help to automatically transform the received protocol buffer messages. This is shown in Listing 10-20.
@Configuration
public class ProductRestControllerConfiguration{
    @Bean
    RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
        return new RestTemplate(Arrays.asList(hmc));
    }
    @Bean
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }
}
Listing 10-20

Rest Template to Invoke Protocol Buffer (ch10\ch10-02\ProductWeb\src\main\java\com\acme\ecom\product\controller\ProductRestControllerConfiguration.java)

Next, the Rest Controller for the Product Web microservice will leverage the above RestTemplate to delegate calls to the Product Server microservice, as shown in Listing 10-21.
@RestController
public class ProductRestController{
    @Autowired
    RestTemplate restTemplate;
    private static String PRODUCT_SERVICE_URL =
        "http://localhost:8081/products/";
    @Autowired
    public ProductRestController(RestTemplate restTemplate){
        this.restTemplate = restTemplate;
    }
    @RequestMapping(value = "/productsweb", method = RequestMethod.GET,
        produces = {MediaType.APPLICATION_JSON_VALUE})
    public  Products getAllProducts() {
        Products products = restTemplate.getForObject(PRODUCT_SERVICE_URL,
            Products.class);
        List<Product> productsList = products.getProductList();
        Products productsParent =
        Products.newBuilder().addAllProduct(productsList).build();
        return productsParent;
    }
}
Listing 10-21

Rest Controller Delegating Calls to Microservice Spitting Protocol Buffer (ch10\ch10-02\ProductWeb\src\main\java\com\acme\ecom\product\controller\ProductRestController.java)

Build and Test the Protocol Buffer Between Microservices

Once your message structure is defined in a .proto file , you need a protoc compiler to convert this language-neutral content to Java code. Follow the instructions in the Protocol Buffer’s repository ( https://github.com/google/protobuf ) in order to get an appropriate compiler version. Alternatively, you can download a prebuilt binary compiler from the Maven central repository by searching for the com.google.protobuf:protoc artifact and then picking an appropriate version for your platform.

Next, you need to run the compiler, specifying the path to search for the .proto files , the destination directory where you want the generated code to go, and the absolute path to your .proto. In your case, it will look like:
D:\Applns\Google\ProtocolBuffer\protoc-3.4.0-win32\bin\protoc --proto_path .\src\main\resources --java_out .\src\main\java .\src\main\resources\product.proto

Because you want Java classes, you use the --java_out option; however, similar options are provided for other supported languages.

The above steps are handy, but they are not straightforward to weave into the Maven build. That’s why you used the protoc-jar-maven-plugin in the pom.xml in Listing 10-16; it performs protobuf code generation using the multiplatform executable protoc-jar. Hence no manual steps are needed to build and run the samples. Let’s get started.

The complete code required to demonstrate intermicroservices communication using Protocol Buffer is kept inside folder ch10\ch10-02. You don’t need MongoDB for this sample. You can build, pack, and run the different microservices in the following order.

Bring up the Product Server microservice first:
cd ch10\ch10-02\ProductServer
D:\binil\gold\pack03\ch10\ch10-02\ProductServer>make
D:\binil\gold\pack03\ch10\ch10-02\ProductServer>rem D:\Applns\Google\ProtocolBuffer\protoc-3.4.0-win32\bin\protoc --proto_path .\src\main\resources --java_out .\src\main\java .\src\main\resources\product.proto
D:\binil\gold\pack03\ch10\ch10-02\ProductServer>mvn -Dmaven.test.skip=true clean package
D:\binil\gold\pack03\ch10\ch10-02\ProductServer>run
D:\binil\gold\pack03\ch10\ch10-02\ProductServer>java -jar -Dserver.port=8080 .\target\Ecom-Product-Microservice-0.0.1-SNAPSHOT.jar
The above command will bring up the Product Server microservice in port 8080. You also want to inspect the format of the messages flowing through the wire, so you use Apache TCPMon as a proxy. See Appendix E to start TCPMon as a proxy.
cd D:\Applns\apache\TCPMon\tcpmon-1.0-bin\build
D:\Applns\apache\TCPMon\tcpmon-1.0-bin\build>tcpmon 8081 127.0.0.1 8080
This will bring up TCPMon so that all requests hitting port 8081 in the host where the TCPMon is running (localhost, in your case) will be proxied to port 8080 in host 127.0.0.1 in the above case. See Figure 10-4.
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig4_HTML.jpg
Figure 10-4

TCPMon Proxy set to monitor requests and responses

Next, bring up the Product Web microservice:
cd ch10\ch10-02\ProductWeb
D:\binil\gold\pack03\ch10\ch10-02\ProductWeb>make
D:\binil\gold\pack03\ch10\ch10-02\ProductWeb>mvn -Dmaven.test.skip=true clean package
D:\binil\gold\pack03\ch10\ch10-02\ProductWeb>run
D:\binil\gold\pack03\ch10\ch10-02\ProductWeb>java -Dserver.port=8082 -jar .\target\Ecom-Product-Microservice-0.0.1-SNAPSHOT.jar
You can now test the application by pointing to the URL to retrieve the HTML-based client app, preferably using the Chrome browser:
http://localhost:8082/

Upon loading itself, the browser client will fire a request to Product Web, listening on port 8082, which will delegate the request to the Apache TCPMon Proxy listening on port 8081. This request hitting port 8081 in the host where the TCPMon is running (localhost, in your case) will be proxied to port 8080 in localhost, again where the Product Server microservice is listening. If everything goes as planned, you should be able to see the products listed on the web page. If you inspect the TCPMon Proxy console, you should be able to validate that the communication between the two microservices uses Protocol Buffer as the wire-level protocol in handshaking the HTTP-based REST invocation.

The Impact of Using Protocol Buffer

Let’s inspect the response content length while using Protocol Buffer for intermicroservices communications. For this comparison, you will look at three scenarios.

Protocol Buffer Encoding

The sample demonstration in the previous section explained how to use Protocol Buffer for intermicroservices communications. You can observe the TCPMon Proxy console where you should be able to validate that the communication between the two microservices uses Protocol Buffer. It will also say that the response content length is 265 (which is representative). See Figure 10-5.
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig5_HTML.jpg
Figure 10-5

Inspect the Protocol Buffer encoding using TCPMon

XML Encoding

You can use Chrome and hit the TCPMon Proxy directly so that the client doesn’t explicitly ask for Protocol Buffer during content negotiation using the URL http://localhost:8081/products/. This will show the response in XML format; see Figure 10-6.
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig6_HTML.jpg
Figure 10-6

Inspect the XML encoding using TCPMon

You can examine the content size using any tool (I use Microsoft Word in Figure 10-7).
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig7_HTML.jpg
Figure 10-7

Content length while encoding using XML

JSON Encoding

One easy method to force JSON encoding between the microservices call is to slightly change the application code in the Product Server microservice. See Listing 10-22.
@RequestMapping(value = "/products", method = RequestMethod.GET,
    produces = {MediaType.APPLICATION_JSON_VALUE})
//@RequestMapping(value = "/products", method = RequestMethod.GET)
public  Products getAllProducts() {
    List<Product> products = getAllTheProducts();
    Products productsParent =     Products.newBuilder().addAllProduct(products).build();
    return productsParent;
}
Listing 10-22

Enforce JSON Encoding (ch10\ch10-02\ProductServer\src\main\java\com\acme\ecom\product\controller\ProductRestController.java)

You can now rebuild and restart the Product Server microservice and rerun the client application by going to http://localhost:8082/.

If you inspect the TCPMon Proxy console, you should be able to see that JSON encoding is opted in this case, as shown in Figure 10-8.
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig8_HTML.jpg
Figure 10-8

Inspect JSON encoding using TCPMon

You can again examine the content size using any tool (see Figure 10-9).
../images/477630_1_En_10_Chapter/477630_1_En_10_Fig9_HTML.jpg
Figure 10-9

Content length while encoding using JSON

You can now compare these three scenarios and get an idea of the variation in the size of the response while using Protocol Buffer for communication between microservices.

Summary

Microservices are like double-edged swords: they provide extra leverage but should be used with care. This is mainly due to the Inversion of Architecture discussed in Chapter 4. You learned two refactoring processes you can bring to your microservices architecture to influence the performance to a greater extent. There are more optimizations; however, I will limit them to the two discussed in this chapter. A major chunk of the rest of the book will talk about event-based microservices where the asynchronous nature of intermicroservices communications is leveraged by default using messaging instead of HTTP. You’ll start getting into those aspects in the next chapter.