Skip to main content

Writing Java microservices with WSO2 Microservices Framework For Java (MSF4J)

Microservices are going all over the enterprise. It changed the way people write software within enterprise eco system. The advantages of containerized deployments and CI/CD processes and cloud-native application architectures provided the base platform for wider microservices adoption. When it comes to enterprise software, Java has been the first choice for decades and still it is the case. Writing a microservice in Java is pretty simple and straightforward. You can just write a JAX-RS application and run that within a web container like tomcat or use an embedded server like jetty. Even though you can write a simple micro service like that, when deploying this microservice within a production system, it needs lot more capabilities. Some of them are
  • Monitoring
  • Performance
  • Error handling
  • Extensibility


Microservices frameworks comes into picture with these types of requirements. WSO2 Microservices Framework for Java (MSF4J) is a framework designed from scratch to fullfill these requirements and many others.


In this article, I will be guiding the user through a 5 step tutorial on how to implement Java microservices with WSO2 MSF4J.


Create your first microservice

If you have Java and Maven installed in your computer, creating your first microservice is as easy as just entering the below mentioned command in a terminal.


mvn archetype:generate -DarchetypeGroupId=org.wso2.msf4j -DarchetypeArtifactId=msf4j-microservice -DarchetypeVersion=1.0.0 -DgroupId=org.example -DartifactId=stockquote -Dversion=0.1-SNAPSHOT -Dpackage=org.example.service -DserviceClass=HelloService


Once you execute the above command, it will create a directory called “stockquote” and within that directory, you will find the artifacts related to your first microservice.


  • pom.xml - This is the maven project file for the created microservice
  • src - This directory contains the source code for the microservice


With the above command, we specify the package name as “org.example.service” and the service class as “HelloService”. Within the src directory, you can find the package hierachy and the source file with the name “HelloService.java”. In addition to this source file, there is another source file with the name “Application.java” which will be used to run the microservice in the pre-built service runtime.
After generating the microservice, you can create an IDE project by using either of the below mentioned commands based on the preferred IDE.


mvn idea:idea (for Intellij Idea)
mvn eclipse:eclipse (for eclipse)


Once this is done, you can open the generated source files using the preferred IDE. Let’s have a look at the source files.


HelloService.java


@Path("/service")
public class HelloService {

  @GET
  @Path("/")
  public String get() {
      // TODO: Implementation for HTTP GET request
      System.out.println("GET invoked");
      return "Hello from WSO2 MSF4J";
  }
// more code lines here


This is the main service implementation class which will execute the business logic and have the microservice related configurations through annotations which are a sub set of JAX-RS annotations.


  • @Path - This annotation is used to specify the base path of the API as well as resource level path (We have “/service” as the base path and “/” as the resource path)
  • @GET - This is the http method attached to a given method (for get() method)


In the above class, change the base path context from “/service” to “/stockquote” by changing the “@Path” annotation at the class level.


@Path("/stockquote")
public class HelloService {


In addition to this class, there is another class generated with the command which is “Application.java”.


Application.java


public class Application {
  public static void main(String[] args) {
      new MicroservicesRunner()
              .deploy(new HelloService())
              .start();
  }
}


This class is used to run the microservice within the msf4j runtime. Here we create an instance of the “MicroservicesRunner” class with an object of the “HelloService” class which we have implemented our microservice logic and then initiating the object with “start” method call.


We are all set to run our first microservice. Go inside the stockquote directory and execute the following maven command to build the microservice.


mvn clean install


After the command completion, you will see that there is a jar file generated at the target/ directory with the name “stockquote-0.1-SNAPSHOT.jar”. This is your “fat” jar which contains the “HelloService” as well as msf4j run time. Now you can run the microservice with the following command.


java -jar target/stockquote-0.1-SNAPSHOT.jar


If all goes well, you will see the following log lines in the console which will confirm that your microservice is up and running.


2017-12-15 15:17:03 INFO  MicroservicesRegistry:76 - Added microservice: org.example.service.HelloService@531d72ca
2017-12-15 15:17:03 INFO  NettyListener:56 - Starting Netty Http Transport Listener
2017-12-15 15:17:04 INFO  NettyListener:80 - Netty Listener starting on port 8080
2017-12-15 15:17:04 INFO  MicroservicesRunner:122 - Microservices server started in 180ms


Now it is time to send a request and see whether this service actually doing what it is supposed to do (which is echoing back the message “Hello from WSO2 MSF4J”). Open another terminal and run the curl command mentioned below


curl -v http://localhost:8080/stockquote


You will get the following result once the command is executed


Hello from WSO2 MSF4J


Add GET/POST methods to your microservice

Let’s create a microservice which produces something useful. Let’s expand the generated HelloService class to provide stock details based on the user input. We can rename the HelloService class as “StockQuoteService” and implement the methods for “GET” and “POST” operations.


StockQuoteService.java


@Path("/stockquote")
public class StockQuoteService {

  private Map<String, Stock> quotes = new HashMap<>();

  public StockQuoteService() {
      quotes.put("IBM", new Stock("IBM", "IBM Inc.", 90.87, 89.77));
  }

  @GET
  @Path("/{symbol}")
  @Produces("application/json")
  public Response get(@PathParam("symbol") String symbol) {
      Stock stock = quotes.get(symbol);
      return stock == null ?
              Response.status(Response.Status.NOT_FOUND).entity("{\"result\":\"Symbol not found = " + symbol + "\"}").build() :
              Response.status(Response.Status.OK).entity(stock).build();
  }

  @POST
  @Consumes("application/json")
  public Response addStock(Stock stock) {
      if(quotes.get(stock.getSymbol()) != null) {
          return Response.status(Response.Status.CONFLICT).build();
      }
      quotes.put(stock.getSymbol(), stock);
      return Response.status(Response.Status.OK).
              entity("{\"result\":\"Updated the stock with symbol = " + stock.getSymbol() + "\"}").build();
  }
Here, we have used a new annotation “@Produces” to specify the response message content type as “application/json”. Also, the get method is returning a “Response” object which is something native to the msf4j runtime. Also we have used “@PathParam” annotation to access the path parameter variable we have defined within the “@Path” annotation.


In this class, we have used a new class “Stock” which holds the information about a particular stock symbol. The source code for this class can be found in github location mentioned at the end of the tutorial.


Let’s go inside the source directory “step2-get-post/stockquote” and execute the following maven command.


mvn clean install


Now the microservice flat-jar file is created in the target directory. Let’s run it using the following command.


java -jar target/stockquote-0.1-SNAPSHOT.jar


Now the microservice is up and running on default 8080 port. Let’s execute the “GET” and “POST” methods with the following commands.




{"symbol":"IBM","name":"IBM Inc.","high":90.87,"low":89.77}


curl -v -X POST -H "Content-Type:application/json" -d '{"symbol":"GOOG","name":"Google Inc.", "high":190.23, "low":187.45}' http://localhost:8080/stockquote


{"result":"Updated the stock with symbol = GOOG"}


Add interceptors to your microservice

Now we have written a useful microservice with msf4j and it is working as expected. Interceptors is a component which can be used to intercept all the messages coming into a particular microservice. Interceptor will access the message prior to the microservice logic execution. It can be used for purposes like
  • authentication
  • throttling
  • rate limiting
and the interceptor logic can be implemented as a separate component from the business logic of the microservice. It can also be shared across multiple microservices with the model supported by msf4j.


Let’s see how to implement an interceptor logic.


RequestLoggerInterceptor.java


public class RequestLoggerInterceptor implements RequestInterceptor {

  private static final Logger log = LoggerFactory.getLogger(RequestLoggerInterceptor.class);

  @Override
  public boolean interceptRequest(Request request, Response response) throws Exception {
      log.info("Logging HTTP request { HTTPMethod: {}, URI: {} }", request.getHttpMethod(), request.getUri());
      String propertyName = "SampleProperty";
      String property = "WSO2-2017";
      request.setProperty(propertyName, property);
      log.info("Property {} with value {} set to request", propertyName, property);
      return true;
  }
}


The above class has implemented the “RequestInterceptor” interface which is coming from msf4j runtime. It is used to intercept requests coming into a microservice. Within this class, it has overriden the “interceptRequest” method which will execute once the request is received. Interceptor logic will be written here.


Similarly, response can be intercepted by using the “ResponseLoggerInterceptor” which implements the “ResponseInterceptor” interface of the msf4j runtime.


Once the interceptor logic is implemented, we need to engage this interceptor with the microservice using the annotations.


StockQuoteService.java


@Path("/stockquote")
public class StockQuoteService {

  private Map<String, Stock> quotes = new HashMap<>();

  public StockQuoteService() {
      quotes.put("IBM", new Stock("IBM", "IBM Inc.", 90.87, 89.77));
  }

  @GET
  @Path("/{symbol}")
  @Produces("application/json")
  @RequestInterceptor(RequestLoggerInterceptor.class)
  @ResponseInterceptor(ResponseLoggerInterceptor.class)
  public Response get(@PathParam("symbol") String symbol) {
      Stock stock = quotes.get(symbol);
      return stock == null ?
              Response.status(Response.Status.NOT_FOUND).build() :
              Response.status(Response.Status.OK).entity(stock).build();
  }


In this main microservice class, we have used the “@RequestInterceptor” and “@ResponseInterceptor” annotations to bind the interceptor classes which has the implementation logic.


Let’s go inside the source directory “step3-interceptor/stockquote” and execute the following maven command.


mvn clean install


Now the microservice flat-jar file is created in the target directory. Let’s run it using the following command.


java -jar target/stockquote-0.1-SNAPSHOT.jar


Now the microservice is up and running on default 8080 port. Let’s execute the “GET” and method with the following commands.




{"symbol":"IBM","name":"IBM Inc.","high":90.87,"low":89.77}


At the terminal window which has the microservice started, you will see the below log entries which are printed from the interceptors which was written.


2017-12-18 12:08:01 INFO  RequestLoggerInterceptor:19 - Logging HTTP request { HTTPMethod: GET, URI: /stockquote/IBM }
2017-12-18 12:08:01 INFO  RequestLoggerInterceptor:23 - Property SampleProperty with value WSO2-2017 set to request
2017-12-18 12:08:01 INFO  ResponseLoggerInterceptor:18 - Logging HTTP response
2017-12-18 12:08:01 INFO  ResponseLoggerInterceptor:21 - Value of property SampleProperty is WSO2-2017


Add custom erros with ExceptionMapper

One of the major requirements when writing microservices is the ability to handle errors and provide customized errors. This can be achieved by using the ExceptionMapper concept of the msf4j.


ExceptionMapper has 2 parts.
  • Exception type
  • Exception Mapper 


First, user has to define the Exception type as a Java Exception.


public class SymbolNotFoundException extends Exception {
  public SymbolNotFoundException() {
      super();
  }

  public SymbolNotFoundException(String message) {
      super(message);
  }

  public SymbolNotFoundException(String message, Throwable cause) {
      super(message, cause);
  }

  public SymbolNotFoundException(Throwable cause) {
      super(cause);
  }

  protected SymbolNotFoundException(String message, Throwable cause,
                                    boolean enableSuppression, boolean writableStackTrace) {
      super(message, cause, enableSuppression, writableStackTrace);
  }
}


Then the ExceptionMapper can be used to map this exception to a given customized error message.


public class SymbolNotFoundMapper implements ExceptionMapper<SymbolNotFoundException> {

  public Response toResponse(SymbolNotFoundException ex) {
      return Response.status(404).
              entity(ex.getMessage() + " [from SymbolNotFoundMapper]").
              type("text/plain").
              build();
  }
}


The “SymbolNotFoundMapper” has implemented the “ExceptionMapper” class and within that, we specify the generic type of the “SymbolNotFoundException” which maps to this exception mapper. Inside the “toResponse” method, the customized response generation can be done.


Now, let’s see how this exception mapper is binded to the main microservice. In the “StockQuoteService” class, within the method which binds to the relevant HTTP method, specific exception needs to be thrown as mentioned below.


StockQuoteService.java


@Path("/stockquote")
public class StockQuoteService {

  private Map<String, Stock> quotes = new HashMap<>();

  public StockQuoteService() {
      quotes.put("IBM", new Stock("IBM", "IBM Inc.", 90.87, 89.77));
  }

  @GET
  @Path("/{symbol}")
  @Produces("application/json")
  @Timed
  public Response get(@PathParam("symbol") String symbol) throws SymbolNotFoundException {
      Stock stock = quotes.get(symbol);
      if (stock == null) {
          throw new SymbolNotFoundException("Symbol " + symbol + " not found");
      }
      return Response.status(Response.Status.OK).entity(stock).build();
  }


In the above implementation, “get” method is throwing “SymbolNotFoundException” which is mapped to the “SymbolNotFoundMapper” which we have mentioned above. When there is an error occur within this method, it will respond back with the customized error.


Let’s go inside the source directory “step4-exception-mapper/stockquote” and execute the following maven command.


mvn clean install


Now the microservice flat-jar file is created in the target directory. Let’s run it using the following command.


java -jar target/stockquote-0.1-SNAPSHOT.jar


Now the microservice is up and running on default 8080 port. Let’s execute the “GET” and method with the following commands.




Symbol WSO2 not found [from SymbolNotFoundMapper]


Monitor your microservice with WSO2 Data Analytics Server (WSO2 DAS)

Now with microservices are implemented with error handling and interceptors, it is essential to monitor these services. WSO2 MSF4J comes with a built in set of annotations to monitor the microservices. These monitoring data can be published into
  • Terminal output
  • WSO2 Data Analytics Server
  • JMX
  • CSV file
  • SLF4J API


Details about the monitoring annotations can be found at the following link.


StockQuoteService.java


@Path("/stockquote")
public class StockQuoteService {

  private Map<String, Stock> quotes = new HashMap<>();

  public StockQuoteService() {
      quotes.put("IBM", new Stock("IBM", "IBM Inc.", 90.87, 89.77));
  }

  @GET
  @Path("/{symbol}")
  @Produces("application/json")
  @Timed
  @HTTPMonitored(tracing = true)
  public Response get(@PathParam("symbol") String symbol) {
      Stock stock = quotes.get(symbol);
      return stock == null ?
              Response.status(Response.Status.NOT_FOUND).build() :
              Response.status(Response.Status.OK).entity(stock).build();
  }

  @POST
  @Consumes("application/json")
  @Metered
  @HTTPMonitored(tracing = true)
  public Response addStock(Stock stock) {
      if(quotes.get(stock.getSymbol()) != null) {
          return Response.status(Response.Status.CONFLICT).build();
      }
      quotes.put(stock.getSymbol(), stock);
      return Response.status(Response.Status.OK).
              entity("http://localhost:8080/stockquote/" + stock.getSymbol()).build();
  }


Here, we have used “@Metered” and “@Timed” annotations for measuring the metrics for respective HTTP methods. In addition to that “@HTTPMonitored” annotation publishes metrics data to WSO2 Data Analytics Server.


Let’s go inside the source directory “step5-metrics/stockquote” and execute the following maven command.
mvn clean install

Now the microservice flat-jar file is created in the target directory. Let’s run it using the following command.

java -jar target/stockquote-0.1-SNAPSHOT.jar

We need to setup the WSO2 DAS to test the HTTP Monitoring dashboard.

Download WSO2 DAS

Download WSO2 DAS and unpack it to some directory. This will be the DAS_HOME directory.

Configure DAS

Download the das-setup configurations from the following github location. https://github.com/wso2/msf4j/tree/master/analytics/das-setup

Run "das-setup/setup.sh" to setup DAS. Note that the DAS Home directory in the above step has to be provided as an input to that script.The setup script will also copy the already built MSF4J HTTP Monitoring Carbon App (CAPP) to DAS.

Start DAS

From DAS_HOME, run, bin/wso2server.sh to start DAS and make sure that it starts properly. You need to start the DAS with following command to avod the SSLCertificate exception occurs due to self-signed certificates.


sh bin/wso2server.sh -Dorg.wso2.ignoreHostnameVerification=true

Execute following Commands

1. curl -v http://localhost:8080/stockquote/IBM
2. curl -v -X POST -H "Content-Type:application/json" -d '{"symbol":"GOOG","name":"Google Inc.", "high":190.23, "low":187.45}' http://localhost:8080/stockquote


If everything works fine, you can observe the metrics information printed at the console similar to below


org.example.service.StockQuoteService.get
            count = 4
        mean rate = 0.01 calls/second
    1-minute rate = 0.00 calls/second
    5-minute rate = 0.04 calls/second
   15-minute rate = 0.12 calls/second
              min = 1.81 milliseconds
              max = 85.22 milliseconds
             mean = 2.47 milliseconds
           stddev = 1.47 milliseconds
           median = 2.20 milliseconds
             75% <= 3.34 milliseconds
             95% <= 3.34 milliseconds
             98% <= 3.34 milliseconds
             99% <= 3.34 milliseconds


Access the HTTP Monitoring dashboard

Go to http://localhost:9763/monitoring/. If everything works fine, you should see the metrics & information related to your microservices on this dashboard. Please allow a few minutes for the dashboard to be updated because the dashboard update batch task runs every few minutes.
Source code for this tutorial can be found at the following github repository.



Comments

  1. This blog is so informative for providing a valuable information about sms api java.

    ReplyDelete
  2. Really nice blog post.provided a helpful information.I hope that you will post more updates like this Oracle
    Course

    ReplyDelete

Post a Comment

Popular posts from this blog

WSO2 ESB tuning performance with threads

I have written several blog posts explaining the internal behavior of the ESB and the threads created inside ESB. With this post, I am talking about the effect of threads in the WSO2 ESB and how to tune up threads for optimal performance. You can refer [1] and [2] to understand the threads created within the ESB. [1] http://soatutorials.blogspot.com/2015/05/understanding-threads-created-in-wso2.html [2] http://wso2.com/library/articles/2012/03/importance-performance-wso2-esb-handles-nonobvious/ Within this blog post, I am discussing about the "worker threads" which are used for processing the data within the WSO2 ESB. There are 2 types of worker threads created when you start sending the requests to the server 1) Server Worker/Client Worker Threads 2) Mediator Worker (Synapse-Worker) Threads Server Worker/Client Worker Threads These set of threads will be used to process all the requests/responses coming to the ESB server. ServerWorker Threads will be used to pr

How puppet works in your IT infrstructure

What is Puppet? Puppet is IT automation software that helps system administrators manage infrastructure throughout its lifecycle, from provisioning and configuration to orchestration and reporting. Using Puppet, you can easily automate repetitive tasks, quickly deploy critical applications, and proactively manage change, scaling from 10s of servers to 1000s, on-premise or in the cloud. How the puppet works? It works like this..Puppet agent is a daemon that runs on all the client servers(the servers where you require some configuration, or the servers which are going to be managed using puppet.) All the clients which are to be managed will have puppet agent installed on them, and are called nodes in puppet. Puppet Master: This machine contains all the configuration for different hosts. Puppet master will run as a daemon on this master server. Puppet Agent: This is the daemon that will run on all the servers, which are to be managed using p

How to configure timeouts in WSO2 ESB to get rid of client timeout errors

WSO2 ESB has defined some configuration parameters which controls the timeout of a particular request which is going out of ESB. In a particular  scneario, your client sends a request to ESB, and then ESB sends a request to another endpoint to serve the request. CLIENT->WSO2 ESB->BACKEND The reason for clients getting timeout is that ESB timeout is larger than client's timeout. This can be solved by either increasing the timeout at client side or by decreasing the timeout in ESB side. In any of the case, you can control the timeout in ESB using the below properties. 1) Global timeout defined in synapse.properties (ESB_HOME\repository\conf\) file. This will decide the maximum time that a callback is waiting in the ESB for a response for a particular request. If ESB does not get any response from Back End, it will drop the message and clears out the call back. This is a global level parameter which affects all the endpoints configured in ESB. synapse.global_timeout_inte