In this paper, the point

  • Microservices can decouple your code
  • Microservices enable different teams to focus on a narrower range of work responsibilities, use separate technologies, and deploy more securely and frequently
  • SpringBoot supports a variety of REST API implementations
  • Service discovery and service invocation are platform independent
  • Swagger generates robust API documentation and invocation interfaces

If you’re not ready to use microservices, you’re definitely behind the early adopter stage of the learning curve, and it’s time to start microservices. In this article, we’ll demonstrate the various components necessary to create a RESTFUL microservice, using Consul service registry and Spring Boot for scaffolding, dependency injection, and dependency management, building with Maven, Create Java RESTFUL style apis using Spring REST and Jersey/JaxRS.

Over the past two decades, enterprises have become very agile with SDLC processes, but applications are still quite large and coupled together, containing a large number of JARS that support various versions of a wide variety of apis. However, there is a trend towards more streamlined DevOps-style processes and “serverless” functionality. Microservice refactoring decouples code and resources, makes the build process smaller, makes releases safer, and makes apis more stable.

In this article, we will build a simple stock market portfolio management application. In this application, customers can price their stock portfolios (ticker symbol and quantity) through service calls. The portfolio microservice retrieves the user’s portfolio, sends it to the pricing microservice to apply the latest pricing, returns the fully priced and categorized portfolio, and presents all this information to the customer through a REST call.

Before we can start creating the microservice, we need to install Consul to prepare our environment.

Download Consul Service Registry

We will use the Hashicorp Consul to implement the service discovery, so please go to www.consul.io/downloads.h… Download Consul, available for Windows, Linux, Mac and more. This link will provide an executable that you need to add to your PATH environment variable.

Start the Consul

Launch Consul in dev mode from a script popup:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__consul agent -dev__Wed Jan 10 2018  10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

To verify that it’s actually running, open your browser and go to Consul UI http://localhost:8500. If all is well, Consul should report that it is operating in good health. Click on Consul (on the left) for more information.

If there is a problem with this place, make sure you have consul added to the execution path and ports 8500 and 8600 are available.

Create the SpringBoot application

We will use Spring Initializr integrated with mainstream ides to create scaffolding for our SpringBoot application. The screenshot below uses IntelliJ IDEA.

Select File/New Project to open the New Project Template pop-up, and then select Spring Initializr.

In fact, you can install scaffolding without an IDE. Completing an online Web form through the SpringBoot Initializr site start.spring. IO will produce a downloadable ZIP file containing your empty project.

Click the “Next” button to fill in all project metadata. Use the following configuration:

Click the “Next” button to select dependencies, then type Jersey and Consul Discovery in the dependencies search bar. Add those dependencies:

Click the “Next” button to specify your project name and location. Using the default name “Portfolio” configured in the Web form, specify where you want the project to be stored, and then click “Finish” to generate and open the project:



(Click image to enlarge)

You can use the generated application.properties file, but SpringBoot also accepts the YAML file format, which looks more intuitive, so rename this file to Application.yml.

We named this microservice “portfolle-service”. We can specify a port or use port 0 to make an application use an available port. In our example, we use port 57116. If you deploy the service as a Docker Container, you can map it to any port you choose. Let’s name the application and specify the port by adding the following configuration to the applicatin.yml file:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__spring:
 application:
   name: portfolio-service
server:
 port: 57116
__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

To make our service discoverable, we need to annotate SpringBoot’s Application class. Open PortfolioApplication and add @enableDiscoveryClient above the class declaration.

Accept imports. The class would look like this:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__package com.restms.demo.portfolio;  import org.springframework.cloud.client.discovery.EnableDiscoveryClient; . . . @SpringBootApplication @EnableDiscoveryClient public class PortfolioApplication { public static void main(String[]  args) { SpringApplication.run(PortfolioApplication.class, args); } } __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

(To demonstrate how to compose microservices from various independent platforms, we will use Jersey for this service and Spring REST for the next service).

To install the Jersey REST Style Web Service, we need to specify a ResourceConfig Configuration class. Add the JerseyConfig class (in this case, we’ll put it under the same package as our Application class). It should look like this, with the appropriate package and imports:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__@Configuration
@ApplicationPath("portfolios")
public class JerseyConfig extends ResourceConfig {
   public JerseyConfig()
   {
       register(PortfolioImpl.class);
   }
}__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Note that it inherits ResourceConfig to indicate that it is a Jersey configuration class. The @ApplicationPath(” assortative “) attribute specifies the context of the call, meaning the call path should start with “assortative”. (If you don’t specify it, the context defaults to “/”.)

PortfolioImpl class will serve two requests, where portfolios/ Customer /{customer-id} returns all portfolios, Portfolios /customer/{customer-id}/portfolio/{portfolio-id} return a portfolio. A portfolio consists of a ticker symbol and a corresponding share of holdings.

(In this case, there are 3 customers with ids 0, 1, and 2, and each customer has 3 portfolios with ids 0, 1, and 2).

Your IDE will let you create PortfolioImpl, just do it. In this case, add it to the same package. Enter the following code and accept all imports:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__@Component @Path("/portfolios") public class PortfolioImpl implements InitializingBean { private Object[][][][] clientPortfolios; @GET @Path("customer/{customer-id}") @Produces(MediaType.APPLICATION_JSON) // a portfolio consists of an array of arrays, each containing an array of // stock ticker and associated shares public Object[][][] getPortfolios(@PathParam("customer-id") int customerId) { return clientPortfolios[customerId]; } @GET @Path("customer/{customer-id}/portfolio/{portfolio-id}") @Produces(MediaType.APPLICATION_JSON) public Object[][] getPortfolio(@PathParam("customer-id") int customerId, @PathParam("portfolio-id") int portfolioId) { return getPortfolios(customerId)[portfolioId]; } @Override public void afterPropertiesSet() throws Exception { Object[][][][] clientPortfolios = { { // 3 customers, 3 portfolios each {new Object[]{"JPM", 10201}, new Object[]{"GE", 20400}, new Object[]{"UTX", 38892}}, {new Object[]{"KO", 12449}, new Object[]{"JPM", 23454}, new Object[]{"MRK", 45344}}, {new Object[]{"WMT", 39583}, new Object[]{"DIS", 95867}, new Object[]{"TRV", 384756}}, }, { {new Object[]{"GE", 38475}, new Object[]{"MCD", 12395}, new Object[]{"IBM", 91234}}, {new Object[]{"VZ", 22342}, new Object[]{"AXP", 385432}, new Object[]{"UTX", 23432}}, {new Object[]{"IBM", 18343}, new Object[]{"DIS", 45673}, new Object[]{"AAPL", 23456}}, }, { {new Object[]{"AXP", 34543}, new Object[]{"TRV", 55322}, new Object[]{"NKE", 45642}}, {new Object[]{"CVX", 44332}, new Object[]{"JPM", 12453}, new Object[]{"JNJ", 45433}}, {new Object[]{"MRK", 32346}, new Object[]{"UTX", 46532}, new Object[]{"TRV", 45663}}}}; this.clientPortfolios = clientPortfolios; } } __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

The @Component annotation indicates that this is a Spring Component class, exposing it as an endpoint. As we can see from the method annotations, the @Path annotation states that this class is accessible through the portfolios Path, The two supported API calls are available through portfolios/ Customer /{customer-id} and portfolios/ Customer /{customer-id}/portfolio/{portfolle-id}. These methods indicate that they serve HTTP GET requests through the @get annotation, which declares that it returns an array and is annotated as returning Json, so it will return a Json array. Notice how the @PathParam annotation is used in the method declaration to extract the mapping parameters from the request.

(In this case, we return hard-coded values. Of course, in practice, the implemented service would query a database or some other service or data source.

Now build the project and run it. If you are using IntelliJ, it will create a default runnable program that you simply click on the green “Run” arrow. You can still use it

mvn spring-boot:run

Alternatively, you can run Maven install once, then run the application using java-jar and specify the jar file generated in the target directory:

Java jar target \ portfolio – 0.0.1 – the SNAPSHOT. The jar

We should now be able to view this service in the Consul, so back to the browser, open http://localhost:8500/ui/#/dc1/services (if you have already opened up the address, it is ok to refresh).

We see our portfolio service there, but showing as failing. That’s because Consol is waiting for a “healthy” heartbeat request to be sent from our service.

To generate heartbeat requests, we add a SpringBoot “Actuator” service dependency in the APPLICATION’s POM file.

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>__Wed Jan  10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

In the POM file, note that the Jersey version has a version conflict in Ony-starter and Jersey-starter. To resolve this conflict, move the Jersey starter to the first dependency.

Your POM file should now contain the following dependencies:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId>  </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Restart Consul and portfolio service will show normal:

There are now two passing nodes under portfolle-service, one of which is the Portfolio service we implemented, and the other is the heartbeat service.

Check the assigned ports. You can see it in the application output console:

INFO 19792 — [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 57116 (http)

You can also view this port directly in Consul UI. Click on portfolio-service and then select the ‘service’ portfolio-service’ link to display the port for the service, which in this case is 57116.

Call http://localhost:57116/portfolios/customer/1/portfolio/2, then you will see the json array [[” IBM “, 18343], [” DIS “, 45673], [” popular “, 23456]].

Our first micro service is officially open!

Pricing service

Next, we’ll create the pricing service, this time using Spring RestController instead of Jersey.

The pricing service takes the client ID and Portfolio ID as parameters, then queries the Portfolio service using a RestTemplate to get the ticker symbol and shares, and returns the current price. (It’s all fake data, so don’t use it to make trading decisions!)

Create a new project with the following information:

Select Web, Consul Discovery, and Skeletonoid dependencies this time:

(Click image to enlarge)

Name the project “Pricing” and generate the project in the directory you have selected.

This time we will use application.properties instead of application.yml.

Set the name and port in application.properties as follows:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__spring.application.name=pricing
server.port=57216
__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Annotate price Application with @enableDiscoveryClient. The class should look like this, plus package and imports.

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__@SpringBootApplication
@EnableDiscoveryClient
public class PricingApplication {
  public static void main(String[] args) {
     SpringApplication.run(PricingApplication.class, args);
  }
}
__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Next, we’ll create the PricingEndpoint class. This class is a bit verbose because it demonstrates some important functionality, including finding portfolio Services and using RestTemplate to create a query:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__@RestController @RequestMapping("/pricing") public class PricingEndpoint implements InitializingBean { @Autowired DiscoveryClient client; Map<String, Double> pricingMap = new HashMap<>(); RestTemplate restTemplate = new RestTemplate(); @GetMapping("/customer/{customer-id}/portfolio/{portfolio-id}") public List<String> getPricedPortfolio( @PathVariable("customer-id") Integer customerId, @PathVariable("portfolio-id") Integer portfolioId) { List<ServiceInstance> instances = client.getInstances("portfolio-service"); ServiceInstance instance = instances.stream() .findFirst() .orElseThrow(() -> new RuntimeException("not found")); String url = String.format("%s/portfolios/customer/%d/portfolio/%d", instance.getUri(), customerId, portfolioId); // query for the portfolios, returned as an array of List // of size 2, containing a ticker and a position (# of shares) Object[] portfolio = restTemplate.getForObject(url, Object[].class); // Look up the share prices, and return a list of Strings, formatted as // ticker, shares, price, total List<String> collect = Arrays.stream(portfolio).map(position -> { String ticker = ((List<String>) position).get(0); int shares = ((List<Integer>) position).get(1); double price = getPrice(ticker); double total = shares * price; return String.format("%s %d %f %f", ticker, shares, price, total); }).collect(Collectors.toList()); return collect; } private double getPrice(String ticker) { return pricingMap.get(ticker); } @override public void afterPropertiesSet() throws Exception {pricingmap.put ("MMM",201.81); PricingMap. Put (AXP, 85.11); PricingMap. Put (" popular ", 161.04); PricingMap. Put (" BA ", 236.32); PricingMap. Put (" CAT ", 118.02); PricingMap. Put (" back ", 111.31); PricingMap. Put (" notes ", 31.7); PricingMap. Put (" KO ", 46.00); PricingMap. Put (" DIS ", 101.92); PricingMap. Put (XOM, 78.7); PricingMap. Put (" GE ", 24.9); PricingMap. Put (" GS ", 217.62); PricingMap. Put (" HD ", 155.82); PricingMap. Put (" IBM ", 144.29); PricingMap. Put (" id ", 35.66); PricingMap. Put (JNJ, 130.8); PricingMap. Put (" & ", 89.75); PricingMap. Put (MCD, 159.81); PricingMap. Put (" & ", 63.89); PricingMap. Put (MSFT, 73.65); PricingMap. Put (adddy.pk, 52.78); PricingMap. Put (" pounds ", 33.92); PricingMap. Put (" PG ", 92.79); PricingMap. Put (" three ", 117.00); PricingMap. Put (UTX, 110.12); PricingMap. Put (UNH, 198.00); PricingMap. Put (" VZ ", 47.05); PricingMap. Put (" V ", 103.34); PricingMap. Put (WMT, 80.05); } } __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

To discover the Portfolio service, we need to access a DiscoveryClient. This can be easily done with Spring’s @Autowired annotation

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__ @Autowired DiscoveryClient client; __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

We then address our service with this DiscoveryClient instance in the service call:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__List<ServiceInstance> instances = client.getInstances("portfolio-service"); ServiceInstance instance = instances.stream().findFirst().orElseThrow(() -> new RuntimeException("not found")); __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Once addressed to the service, we can use it to execute our request. This request is a combination of API calls we created in portflo-service.

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__String url = String.format("%s/portfolios/customer/%d/portfolio/%d", instance.getUri(), customerId, portfolioId); __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Finally, we use a RestTemplate to perform our GET request.

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Object[] portfolio = restTemplate.getForObject(url, Object[].class); __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Note that for RestControllers (as with SpringMVC RequestController), path variables can be extracted from the @PathVariable annotation, not from @PathParam as Jersey does.

A Spring RestController is used here to publish the pricing service.

The document

We’ve built our microservices against all odds, but they don’t add any value if the world doesn’t know how to use them.

To do this, we use a tool called Swagger. Swagger is an easy-to-use tool that not only generates documentation for our API calls, but also provides an easy-to-use Web client that can reference those documents.

First, let’s specify Swagger in the POM file:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__<dependency> < the groupId > IO. Springfox < / groupId > < artifactId > springfox - swagger2 < / artifactId > < version > 2.7.0 < / version > < / dependency > < the dependency > < groupId > IO. Springfox < / groupId > < artifactId > springfox swagger - UI < / artifactId > < version > 2.7.0 < / version > </dependency> __Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Next, we need to tell Swagger which classes we want to generate documentation for. We need to introduce a new class called SwaggerConfig that contains the various configurations for Swagger.

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__@Configuration
@EnableSwagger2
public class SwaggerConfig {
   @Bean
   public Docket api() {
       return new Docket(DocumentationType.SWAGGER_2)
               .select()
               .apis(RequestHandlerSelectors.any())
               .paths(PathSelectors.regex("/pricing.*"))
               .build();
   }
}__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

We can look at what this class does. First, we annotate @enablesWagger2 to indicate that it is a Swagger configuration.

Next, we create a Docket Bean that tells Swagger which apis to expose. In the example above, we tell Swagger to expose all paths that start with “/pricing”. Alternatively, you can specify a class file instead of a path to generate the document:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__.apis(RequestHandlerSelectors.basePackage("com.restms.demo"))
.paths(PathSelectors.any())__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Restart the pricing service, and call http://localhost:57216/swagger-ui.html on the browser.

Click the “List Operations” button to view detailed service Operations.

Click “Expand Opeartions” to create a form based query call. To provide some parameters, click “Try it out!” And wait for the response result:

(Click image to enlarge)

You can add more color to methods by adding Swagger annotations.

For example, using the @ ApiOperation PricingImpl annotations to decorate the existing method. The getPricedPortfolio:

__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__@ApiOperation(value = "Retrieves a  fully priced portfolio", notes = "Retrieves fully priced portfolio given customer id and portfolio id") @GetMapping("/customer/{customer-id}/portfolio/{portfolio-id}") public List<String> getPricedPortfolio(@PathVariable("customer-id") Integer customerId, @PathVariable("portfolio-id") Integer portfolioId)__Wed Jan 10 2018 10:09:25 GMT+0800 (CST)____Wed Jan 10 2018 10:09:25 GMT+0800 (CST)__Copy the code

Restart and refresh Swagger-UI to view the newly created document:

There are a lot of things you can do with Swagger. Check out its documentation for more details.

About the author

Victor Grazi works on core platform tools at Nomura Securities, is a technology consultant and Java evangelist. He is a tech conference regular, leading the “Java Concurrent Animated” and “Bytecode Explorer” open source projects. He became an Oracle Java Champion in Java Champions as an editor of the InfoQ Java queue.

Getting Started with Microservices in SpringBoot

Thanks to Luo Yuanhang for correcting this article.