In this chapter, we will cover the following recipes:
Creating a project template using STS and Maven
Writing microservices with Spring Boot
Writing REST APIs with Spring MVC
Writing microservices with WildFly Swarm
Writing microservices with Dropwizard
Writing REST APIs with SparkJava
Microservices have gained a lot of traction recently. A microservice-based architecture is one way of designing your software. In such an architecture, applications are broken down into smaller services so that they can be deployed and managed separately. This takes away a lot of pain points that occur in traditional monolithic applications. With that being said, microservices can be built using any programming language. In fact, there are many libraries and frameworks that help programmers build microservices using Java, Scala, C#, JavaScript, Python, Ruby, and so on. In this book, we will focus more on building and deploying microservices with Java.
In a traditional microservice-based design, monolithic applications will be broken down into smaller services that can talk to other services either in a synchronous or asynchronous model, based on the need and use case. The first question that anyone would have when breaking down monolithic applications is "what are the potential services that my application can be broken down into?" There is no rule of thumb or straight-forward answer to this. But usually, one looks for independent functionalities. Each and every functionality can be considered to be built as its own service.
To illustrate this, let's take a look at an example application and see how it could be broken down into smaller, manageable and deployable microservices. The sample application we will be looking at is a biker tracking application. This application will have the following functionalities:
Web interface to monitor the user's progress on a map
REST API to consume the user's geolocation data constantly
Analytics code to perform calculations for biking route suggestions, weather predictions, biking gear suggestions, calories burnt, water intake, and so on
Let's take a look at how this application might have been designed as a monolithic application:

As you can see, the whole application is bundled as one artifact and therefore promotes a single point of failure (SPOF). If for some reason the analytics code crashes your JVM, we will lose the web interface, REST APIs, and analytics as a whole. Now, let's take a look at how this might be broken down into manageable microservices:

In this architecture diagram, you can see that each and every functionality is deployed as its own microservice. The service implementations have been broken down into a Notification Service, which will take care of sending notifications to the users, and the Geo Location Tracker Service, which keeps track of the geolocation (latitude and longitude) information of all the users. The Analytics code has been broken down into its own microservices. So if one type of analytics microservice goes down, the other microservices will keep functioning properly. You might have noticed that the REST APIs are missing. They are actually not missing, but integrated into their respective microservices.
Now let's not waste any more time and jump directly into building one part of this application. To be able to illustrate the extensive concepts that this book offers, I have chosen the geolocation tracker service as our example microservice. This service will be responsible for collecting the geolocation of all users of this application and then storing them in a data store.
Creating a project for your microservice is no different than creating a simple Java project. We will use Maven as our build framework as it is considered to be one of the most popular build frameworks. If you are comfortable using other frameworks, such as Gradle, SBT, or Ivy, feel free to use them. But keep in mind that the recipes throughout this book will use Maven extensively. Unless you are an expert in your preferred framework, I strongly recommend using Maven.
In order to create your microservice project, you will need the following software. Follow the instructions on their respective websites to install them:
JDK 1.8+
Maven 3.3.9+
Spring Tool Suite (STS) 3.8.0+
Make sure both Java and Maven are in your PATH
variable so that you can use the java
and mvn
commands on every terminal shell without having to set PATH
each time. Spring Tool Suite is a sophisticated version of Eclipse that has lot of Spring plugins and extensions. If you are familiar with other IDEs, feel free to use them. But for familiarity, this book will use STS for all recipes.
After you have installed the above-mentioned software, open Spring Tool Suite. The first time you open it, you will be requested to choose a workspace. Go ahead and enter your workspace location. In this recipe, we will learn how to create a template Maven project using STS and Maven. STS comes with Maven Integration out of the box. So we don't have to configure it any further. After your STS IDE has completed startup, follow the below instructions to create a new Maven project:
In your STS window, right-click anywhere on the Package Explorer, select New, and then select Maven Project, as shown in the following screenshot:
This will open a popup that will let you chose the type of Maven project you would like to create. We will skip the archetype selection by checking the box that says Create a simple project (skip archetype selection) and then hit Next:
In the next window, enter the following details to create your project:
Group Id:
com.packt.microservices
Artifact Id:
geolocation
Name:
geolocation
After you have entered the details, hit Finish:
This will create a simple Maven JAR module with all the required directories in place. Depending on your IDE settings, STS configures your new project with the default Java version. If you have not set any defaults, it will configure your project with Java 1.5. You can verify this by checking your project structure in STS. The following screenshot shows that STS uses Java 1.5 for your project:
We will use Java 8's lambda expressions in other chapters. So let's change the Java version from 1.5 to 1.8. In order to change the Java version, we will configure the
maven-compiler-plugin
in thepom.xml
file. Add the following section of code to yourpom.xml
file'sproject
section:<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
Save your
pom.xml
file, right-click on your project, choose Maven, and then hit Update Project... or use the keyboard shortcut Alt + F5. This will automatically change your project's Java version to 1.8.Our microservice project is now ready to play with.
If you are more comfortable using the command line to create Maven projects, issue the following command in your terminal to create the new project:
mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes \
-DgroupId=com.packt.microservices -DartifactId=geolocation \
-Dname=geolocation
After Maven creates the project, you should be able to import your project into your IDE. As this is something out of the scope of this book, we will not be looking at how to import an existing Maven project into your IDE.
Now that our project is ready, let's look at how to write our microservice. There are several Java-based frameworks that let you create microservices. One of the most popular frameworks from the Spring ecosystem is the Spring Boot framework. In this recipe, we will look at how to create a simple microservice application using Spring Boot.
Any application requires an entry point to start the application. For Java-based applications, you can write a class that has the main
method and run that class as a Java application. Similarly, Spring Boot requires a simple Java class with the main
method to run it as a Spring Boot application (microservice). Before you start writing your Spring Boot microservice, you will also require some Maven dependencies in your pom.xml
file.
Create a Java class called
com.packt.microservices.geolocation.GeoLocationApplication.java
and give it an emptymain
method:package com.packt.microservices.geolocation; public class GeoLocationApplication { public static void main(String[] args) { // left empty intentionally } }
Now that we have our basic template project, let's make our project a child project of Spring Boot's
spring-boot-starter-parent
pom
module. This module has a lot of prerequisite configurations in itspom.xml
file, thereby reducing the amount of boilerplate code in ourpom.xml
file. At the time of writing this,1.3.6.RELEASE
was the most recent version:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.6.RELEASE</version> </parent>
After this step, you might want to run a Maven update on your project as you have added a new parent module. If you see any warnings about the version of the
maven-compiler
plugin, you can either ignore it or just remove the<version>3.5.1</version>
element. If you remove the version element, please perform a Maven update afterward.Spring Boot has the ability to enable or disable Spring modules such as Spring MVC, Spring Data, and Spring Caching. In our use case, we will be creating some REST APIs to consume the geolocation information of the users. So we will need Spring MVC. Add the following dependencies to your
pom.xml
file:<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
We also need to expose the APIs using web servers such as Tomcat, Jetty, or Undertow. Spring Boot has an in-memory Tomcat server that starts up as soon as you start your Spring Boot application. So we already have an in-memory Tomcat server that we could utilize.
Now let's modify the
GeoLocationApplication.java
class to make it a Spring Boot application:package com.packt.microservices.geolocation; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure .SpringBootApplication; @SpringBootApplication public class GeoLocationApplication { public static void main(String[] args) { SpringApplication.run(GeoLocationApplication.class, args); } }
As you can see, we have added an annotation, @SpringBootApplication
, to our class. The @SpringBootApplication
annotation reduces the number of lines of code written by adding the following three annotations implicitly:
@Configuration
@ComponentScan
@EnableAutoConfiguration
If you are familiar with Spring, you will already know what the first two annotations do. @EnableAutoConfiguration
is the only annotation that is part of Spring Boot. The AutoConfiguration
package has an intelligent mechanism that guesses the configuration of your application and automatically configures the beans that you will likely need in your code.
You can also see that we have added one more line to the main
method, which actually tells Spring Boot the class that will be used to start this application. In our case, it is GeoLocationApplication.class
. If you would like to add more initialization logic to your application, such as setting up the database or setting up your cache, feel free to add it here.
Now that our Spring Boot application is all set to run, let's see how to run our microservice. Right-click on
GeoLocationApplication.java
from Package Explorer, select Run As, and then select Spring Boot App. You can also choose Java Application instead of Spring Boot App. Both the options ultimately do the same thing. You should see something like this on your STS console:If you look closely at the console logs, you will notice that Tomcat is being started on port number
8080
. In order to make sure our Tomcat server is listening, let's run a simplecurl
command. cURL is a command-line utility available on most Unix and Mac systems. For Windows, use tools such as Cygwin or even Postman. Postman is a Google Chrome extension that gives you the ability to send and receive HTTP requests. For simplicity, we will use cURL. Execute the following command on your terminal:curl http://localhost:8080
This should give us an output like this:
{"timestamp":1467420963000,"status":404,"error":"Not Found","message":"No message available","path":"/"}
This error message is being produced by Spring. This verifies that our Spring Boot microservice is ready to start building on with more features. There are more configurations that are needed for Spring Boot, which we will perform later in this chapter along with Spring MVC.
There are two types of communication models. One of them is synchronous, where the client waits for the server to respond to its request. The other is asynchronous, where the client fires a request and forgets. Though Servlet 3.0 and above let you create asynchronous servlets, in our recipes, we will focus on traditional servlet-based HTTP APIs for simplicity. We will also be looking at asynchronous communication in later chapters.
When it comes to building REST APIs, there are several frameworks to choose from. As we already set up the Spring ecosystem in our previous recipe, it would make more sense and be much easier to use Spring MVC to expose REST APIs.
The true advantage of Spring Boot is that you do not have to add any new dependencies to enable web support for your application. Spring Boot's parent pom
file (spring-boot-starter-parent
) takes care of that for you. Now let's take a look at how to write our first API. If you are familiar with Spring MVC, this should be really straight-forward for you:
Create a
Controller
class calledcom.packt.microservices.geolocation.GeoLocationController.java
, which will be responsible for basic CRUD operations for the geolocation of all users:package com.packt.microservices.geolocation; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/geolocation") public class GeoLocationController { }
There are two things to note here. The
@RestController
annotation indicates that we are going to use this controller to expose our REST APIs. It implicitly adds the@ResponseBody
annotation to all controller methods as that is something you would want to do when exposing your REST APIs using Spring MVC. The@RequestMapping
annotation specifies where your HTTP resource is located. We are setting@RequestMapping
on the controller level to apply it to all controller methods.Note
Using
@RequestMapping
on theController
class level to define a root resource path is considered to be one of the best practices. Instead of having to create API paths such as/getGeolocation
or/createGeolocation
, it is always a better practice to use the same path,/geolocation
, with theGET
method to get geolocation data and thePOST
method to create geolocation data.Before we jump into creating our APIs, we will need some classes for the domain object and service. Let's start with creating our domain object. Assume that our
GeoLocation
consists latitude and longitude. We will be defining both latitude and longitude asdouble
to provide better precision. Now we will have to say which user's geolocation it is. So we might want to add auserId
. We also need to say at what time the user was at the geolocation. So we might want to add a timestamp in EPOCH time format. The timestamp will be of typelong
. This is how your plain old java object (POJO) class will look:package com.packt.microservices.geolocation; import java.io.Serializable; import java.util.UUID; public class GeoLocation implements Serializable { private static final long serialVersionUID = 1L; private double latitude; private double longitude; private UUID userId; private long timestamp; public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } public double getLongitude() { return longitude; } public void setLongitude(double longitude) { this.longitude = longitude; } public UUID getUserId() { return userId; } public void setUserId(UUID userId) { this.userId = userId; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } }
As you can see, we have used the
java.util.UUID
class to represent theuserId
, assuming that this UUID uniquely identifies a user. We will not be creating the userPOJO
as it is out of scope for this recipe.In an ideal scenario, one would be using a NoSQL or relational database to store the geolocations. In this case, NoSQL sounds more suitable due to several reasons, including the fact that our data is time series data, in JSON format, unstructured but will change over time and we will have a humongous amount of data.
For simplicity purposes, we will be storing our geolocations in an in-memory
java.util.List<GeoLocation>
collection. Let's create our repository that holds all our geolocation objects,com.packt.microservices.geolocation.GeoLocationRepository.java
:package com.packt.microservices.geolocation; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.stereotype.Repository; @Repository public class GeoLocationRepository { private List<GeoLocation> geolocations = new ArrayList<GeoLocation>(); public void addGeoLocation(GeoLocation geolocation) { geolocations.add(geolocation); } public List<GeoLocation> getGeoLocations() { return Collections.unmodifiableList(geolocations); } }
Now let's take a look at how your
Service
interface will look:package com.packt.microservices.geolocation; import java.util.List; public interface GeoLocationService { public GeoLocation create(GeoLocation geolocation); public List<GeoLocation> findAll(); }
Both our repository and service have a very simple interface. Ideally in real-time applications, you might want to add more complicated methods that not only perform CRUD operations but also sort, filter, select only specific fields, and so on. Now let's take a look at our
com.packt.microservices.geolocation.GeoLocationServiceImpl.java
class:package com.packt.microservices.geolocation; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class GeoLocationServiceImpl implements GeoLocationService { @Autowired private GeoLocationRepository repository; @Override public GeoLocation create(GeoLocation geolocation) { repository.addGeoLocation(geolocation); return geolocation; } @Override public List<GeoLocation> findAll() { return repository.getGeoLocations(); } }
Note
It is always strongly recommended that you write unit test cases for any new code. But as that is a little out of scope for this book, we will not be writing unit test cases for any of the previous code. To learn more about unit testing Spring Boot applications, please take a look at Spring Boot's documentation at https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html.
Now that our domain and service classes are all set to go, let's modify our
Controller
class to save and find geolocations. Add the following snippet into yourController
class body:@Autowired private GeoLocationService service; @RequestMapping(method = RequestMethod.POST, produces = "application/json", consumes = "application/json") public GeoLocation create(@RequestBody GeoLocation geolocation) { return service.create(geolocation); } @RequestMapping(method = RequestMethod.GET, produces = "application/json") public List<GeoLocation> findAll() { return service.findAll(); }
In this implementation, there are a few things to notice. The @RequestMapping
annotation does not have a path
defined as it is already derived from the class-level annotation. For both the create
and findAll
methods, we are using the same path but different HTTP methods as per best practice. Since we are dealing only with JSON here, we have set the produces
and consumes
values to application/json
. The return types of the create
and findAll
methods are GeoLocation
and List<GeoLocation>
respectively. Spring MVC internally uses Jackson to convert them to their equivalent JSON strings.
That's it! We are now ready to test our application:
Let's try to create two geolocations using the
POST
API and later try to retrieve them using theGET
method. Execute the following cURL commands in your terminal one by one:curl -H "Content-Type: application/json" -X POST -d'{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 41.803488, "longitude": -88.144040}' http://localhost:8080/geolocation
This should give you an output similar to the following (pretty-printed for readability):
{ "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } curl -H "Content-Type: application/json" -X POST -d '{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 9.568012, "longitude": 77.962444}' http://localhost:8080/geolocation
This should give you an output similar to the following (pretty-printed for readability):
{ "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }
To verify whether your entities were stored correctly, execute the following cURL command:
curl http://localhost:8080/geolocation
This should give you an output similar to the following (pretty-printed for readability):
[ { "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }, { "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } ]
You now have a fully working version of your microservice. The remaining recipes in this chapter try to achieve the same logic with different frameworks, such as WildFly Swarm and Dropwizard. Later in this chapter, we will also look at another framework that helps you build REST APIs quickly called SparkJava (different from Apache Spark). If you will be using Spring Boot for your microservices, you can jump to the next chapter. If you are interested in any of the frameworks that were mentioned, jump to the appropriate recipe in this chapter.
WildFly Swarm is a J2EE application packaging framework from RedHat that utilizes the in-memory Undertow server to deploy microservices. In this recipe, we will create the same GeoLocation
API using WildFly Swarm and JAX-RS.
To avoid confusion and dependency conflicts in our project, we will create the WildFly Swarm microservice as its own Maven project. This recipe is just here to help you get started on WildFly Swarm. When you are building your production-level application, it is your choice to either use Spring Boot, WildFly Swarm, Dropwizard, or SparkJava based on your needs.
Similar to how we created the Spring Boot Maven project, create a Maven WAR module with the groupId
com.packt.microservices
and name/artifactId
geolocation-wildfly
. Feel free to use either your IDE or the command line. Be aware that some IDEs complain about a missing web.xml
file. We will see how to fix that in the next section.
Before we set up the WildFly Swarm project, we have to fix the missing
web.xml
error. The error message says that Maven expects to see aweb.xml
file in your project as it is a WAR module, but this file is missing in your project. In order to fix this, we have to add and configuremaven-war-plugin
. Add the following code snippet to yourpom.xml
file'sproject
section:<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build>
After adding the snippet, save your
pom.xml
file and perform a Maven update. Also, if you see that your project is using a Java version other than 1.8, follow the Creating a project template using STS and Maven recipe to change the Java version to 1.8. Again, perform a Maven update for the changes to take effect.Now, let's add the dependencies required for this project. As we know that we will be exposing our APIs, we have to add the JAX-RS library. JAX-RS is the standard JSR-compliant API for creating RESTful web services. JBoss has its own version of JAX-RS. So let's add that dependency to the
pom.xml
file:<dependencies> <dependency> <groupId>org.jboss.spec.javax.ws.rs</groupId> <artifactId>jboss-jaxrs-api_2.0_spec</artifactId> <version>1.0.0.Final</version> <scope>provided</scope> </dependency> </dependencies>
Note
The one thing that you have to note here is the provided scope. The provided scope in general means that this JAR need not be bundled with the final artifact when it is built. Usually, the dependencies with provided scope will be available to your application either via your web server or application server. In this case, when Wildfly Swarm bundles your app and runs it on the in-memory Undertow server, your server will already have this dependency.
The next step toward creating the
GeoLocation
API using Wildfly Swarm is creating the domain object. Use thecom.packt.microservices.geolocation.GeoLocation.java
file from the previous recipe.Now that we have the domain object, there are two classes that you need to create in order to write your first JAX-RS web service. The first of those is the
Application
class. TheApplication
class in JAX-RS is used to define the various components that you will be using in your application. It can also hold some metadata about your application, such as yourbasePath
(orApplicationPath
) to all resources listed in thisApplication
class. In this case, we are going to use/geolocation
as ourbasePath
. Let's see how that looks:package com.packt.microservices.geolocation; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/geolocation") public class GeoLocationApplication extends Application { public GeoLocationApplication() {} }
There are two things to note in this class; one is the
Application
class and the other is the@ApplicationPath
annotation-both of which we've already talked about.Now let's move on to the
resource
class, which is responsible for exposing the APIs. If you are familiar with Spring MVC, you can compare Resource classes to Controllers. They are responsible for defining the API for any specific resource. The annotations are slightly different from that of Spring MVC. Let's create a new resource class calledcom.packt.microservices.geolocation.GeoLocationResource.java
that exposes a simpleGET
API:package com.packt.microservices.geolocation; import java.util.ArrayList; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @Path("/") public class GeoLocationResource { @GET @Produces("application/json") public List<GeoLocation> findAll() { return new ArrayList<>(); } }
All the three annotations,
@GET
,@Path
, and@Produces
, are pretty self explanatory.
Before we start writing the APIs and the service class, let's test the application from the command line to make sure it works as expected. With the current implementation, any GET
request sent to the /geolocation
URL should return an empty JSON array.
So far, we have created the RESTful APIs using JAX-RS. It's just another JAX-RS project:
In order to make it a microservice using Wildfly Swarm, all you have to do is add the
wildfly-swarm-plugin
to the Mavenpom.xml
file. This plugin will be tied to the package phase of the build so that whenever the package goal is triggered, the plugin will create an uber JAR with all required dependencies. An uber JAR is just a fat JAR that has all dependencies bundled inside itself. It also deploys our application in an in-memory Undertow server. Add the following snippet to theplugins
section of thepom.xml
file:<plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>1.0.0.Final</version> <executions> <execution> <id>package</id> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin>
Now execute the
mvn clean package
command from the project's root directory, and wait for the Maven build to be successful. If you look at the logs, you can see thatwildfly-swarm-plugin
will create the uber JAR, which has all its dependencies. You should see something like this in your console logs:After the build is successful, you will find two artifacts in the target directory of your project. The
geolocation-wildfly-0.0.1-SNAPSHOT.war
file is the final WAR created by themaven-war-plugin
. Thegeolocation-wildfly-0.0.1-SNAPSHOT-swarm.jar
file is the uber JAR created by thewildfly-swarm-plugin
. Execute the following command in the same terminal to start your microservice:java -jar target/geolocation-wildfly-0.0.1-SNAPSHOT-swarm.jar
After executing this command, you will see that Undertow has started on port number 8080, exposing the geolocation resource we created. You will see something like this:
Execute the following cURL command in a separate terminal window to make sure our API is exposed. The response of the command should be
[]
, indicating there are no geolocations:curl http://localhost:8080/geolocation
Now let's build the service class and finish the APIs that we started. For simplicity purposes, we are going to store the geolocations in a collection in the service class itself. In a real-time scenario, you will be writing repository classes or DAOs that talk to the database that holds your geolocations. Get the
com.packt.microservices.geolocation.GeoLocationService.java
interface from the previous recipe. We'll use the same interface here.Create a new class called
com.packt.microservices.geolocation.GeoLocationServiceImpl.java
that extends theGeoLocationService
interface:package com.packt.microservices.geolocation; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class GeoLocationServiceImpl implements GeoLocationService { private static List<GeoLocation> geolocations = new ArrayList<> (); @Override public GeoLocation create(GeoLocation geolocation) { geolocations.add(geolocation); return geolocation; } @Override public List<GeoLocation> findAll() { return Collections.unmodifiableList(geolocations); } }
Now that our service classes are implemented, let's finish building the APIs. We already have a very basic stubbed-out
GET
API. Let's just introduce the service class to the resource class and call thefindAll
method. Similarly, let's use the service's create method forPOST
API calls. Add the following snippet toGeoLocationResource.java
:private GeoLocationService service = new GeoLocationServiceImpl(); @GET @Produces("application/json") public List<GeoLocation> findAll() { return service.findAll(); } @POST @Produces("application/json") @Consumes("application/json") public GeoLocation create(GeoLocation geolocation) { return service.create(geolocation); }
We are now ready to test our application. Go ahead and build your application. After the build is successful, run your microservice: let's try to create two geolocations using the
POST
API and later try to retrieve them using theGET
method. Execute the following cURL commands in your terminal one by one:curl -H "Content-Type: application/json" -X POST -d '{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 41.803488, "longitude": -88.144040}' http://localhost:8080/geolocation
This should give you something like the following output (pretty-printed for readability):
{ "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } curl -H "Content-Type: application/json" -X POST -d '{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 9.568012, "longitude": 77.962444}' http://localhost:8080/geolocation
This command should give you an output similar to the following (pretty-printed for readability):
{ "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }
To verify whether your entities were stored correctly, execute the following cURL command:
curl http://localhost:8080/geolocation
This should give you an output like this (pretty-printed for readability):
[ { "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }, { "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } ]
Whatever we have seen so far will give you a head start in building microservices with WildFly Swarm. Of course, there are tons of features that WildFly Swarm offers. Feel free to try them out based on your application needs. I strongly recommend going through the WildFly Swarm documentation for any advanced usages. If you already know that you are going to be using WildFly Swarm for your microservices, you can skip the rest of the recipes in this chapter and jump to next chapter. The final two recipes in this chapter will show you how to create microservices using Dropwizard and how to create RESTful APIs with SparkJava.
Dropwizard is a collection of libraries that help you build powerful applications quickly and easily. The libraries vary from Jackson, Jersey, Jetty, and so on. You can take a look at the full list of libraries on their website. This ecosystem of libraries that help you build powerful applications could be utilized to create microservices as well. As we saw earlier, it utilizes Jetty to expose its services. In this recipe, we will create the same GeoLocation
API using Dropwizard and Jersey.
To avoid confusion and dependency conflicts in our project, we will create the Dropwizard microservice as its own Maven project. This recipe is just here to help you get started with Dropwizard. When you are building your production-level application, it is your choice to either use Spring Boot, WildFly Swarm, Dropwizard, or SparkJava based on your needs.
Similar to how we created other Maven projects, create a Maven JAR module with the groupId
com.packt.microservices
and name/artifactId
geolocation-dropwizard
. Feel free to use either your IDE or the command line. After the project is created, if you see that your project is using a Java version other than 1.8, follow the
Creating a project template using STS and Maven
recipe to change the Java version to 1.8. Perform a Maven update for the change to take effect.
The first thing that you will need is the dropwizard-core
Maven dependency. Add the following snippet to your project's pom.xml
file:
<dependencies> <dependency> <groupId>io.dropwizard</groupId> <artifactId>dropwizard-core</artifactId> <version>0.9.3</version> </dependency> </dependencies>
Guess what? This is the only dependency you will need to spin up a simple Jersey-based Dropwizard microservice.
Before we start configuring Dropwizard, we have to create the domain
object, service
class, and resource
class. Follow the steps from the previous recipe to create the following four files:
com.packt.microservices.geolocation.GeoLocation.java
com.packt.microservices.geolocation.GeoLocationService.java
com.packt.microservices.geolocation.GeoLocationServiceImpl.java
com.packt.microservices.geolocation. GeoLocationResource.java
Let's see what each of these classes does. The GeoLocation.java
class is our domain object that holds the geolocation information. The GeoLocationService.java
class defines our interface, which is then implemented by the GeoLocationServiceImpl.java
class. If you take a look at the GeoLocationServiceImpl.java
class, we are using a simple collection to store the GeoLocation
domain objects. In a real-time scenario, you will be persisting these objects in a database. But to keep it simple, we will not go that far.
To be consistent with the previous recipe, let's change the path of GeoLocationResource
to /geolocation
. To do so, replace @Path("/")
with @Path("/geolocation")
on line number 11 of the GeoLocationResource.java
class.
We have now created the service
classes, domain
object, and resource
class. Let's configure Dropwizard.
In order to make your project a microservice, you have to do two things:
Create a Dropwizard configuration class. This is used to store any meta-information or resource information that your application will need during runtime, such as DB connection, Jetty server, logging, and metrics configurations. These configurations are ideally stored in a YAML file, which will then be mapped to your
Configuration
class using Jackson. In this application, we are not going to use the YAML configuration as it is out of scope for this book.Note
If you would like to know more about configuring Dropwizard, refer to their Getting Started documentation page at http://www.dropwizard.io/0.7.1/docs/getting-started.html .
Let's create an empty
Configuration
class calledGeoLocationConfiguration.java
:package com.packt.microservices.geolocation; import io.dropwizard.Configuration; public class GeoLocationConfiguration extends Configuration { }
The YAML configuration file has a lot to offer. Take a look at a sample YAML file from Dropwizard's Getting Started documentation page to learn more. The name of the YAML file is usually derived from the name of your microservice. The microservice name is usually identified by the return value of the overridden method
public String getName()
in yourApplication
class. Now let's create theGeoLocationApplication.java
application class:package com.packt.microservices.geolocation; import io.dropwizard.Application; import io.dropwizard.setup.Environment; public class GeoLocationApplication extends Application<GeoLocationConfiguration> { public static void main(String[] args) throws Exception { new GeoLocationApplication().run(args); } @Override public void run(GeoLocationConfiguration config, Environment env) throws Exception { env.jersey().register(new GeoLocationResource()); } }
There are a lot of things going on here. Let's look at them one by one. Firstly, this class extends
Application
with theGeoLocationConfiguration
generic. This clearly makes an instance of yourGeoLocationConfiguraiton.java
class available so that you have access to all the properties you have defined in yourYAML
file at the same time mapped in theConfiguration
class. The next one is therun
method. Therun
method takes two arguments: yourconfiguration
andenvironment
. TheEnvironment
instance is a wrapper to other library-specific objects such asMetricsRegistry
,HealthCheckRegistry
, andJerseyEnvironment
. For example, we could register our Jersey resources using theJerseyEnvironment
instance. Theenv.jersey().register(new GeoLocationResource())
line does exactly that. Themain
method is pretty straight-forward. All it does is call therun
method.Before we can start the microservice, we have to configure this project to create a runnable uber JAR. Uber JARs are just fat JARs that bundle their dependencies in themselves. For this purpose, we will be using the
maven-shade-plugin
. Add the following snippet to thebuild
section of thepom.xml
file. If this is your first plugin, you might want to wrap it in a<plugins>
element under<build>
:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.3</version> <configuration> <createDependencyReducedPom>true</createDependencyReducedPom> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.packt.microservices.geolocation.GeoLocationApplication</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
The previous snippet does the following:
It creates a runnable uber JAR that has a reduced
pom.xml
file that does not include the dependencies that are added to the uber JAR. To learn more about this property, take a look at the documentation ofmaven-shade-plugin
.It utilizes
com.packt.microservices.geolocation.GeoLocationApplication
as the class whosemain
method will be invoked when this JAR is executed. This is done by updating theMANIFEST
file.It excludes all signatures from signed JARs. This is required to avoid security errors.
Now that our project is properly configured, let's try to build and run it from the command line. To build the project, execute
mvn clean package
from the project's root directory in your terminal. This will create your final JAR in the target directory. Execute the following command to start your microservice:java -jar target/geolocation-dropwizard-0.0.1-SNAPSHOT.jar server
The
server
argument instructs Dropwizard to start the Jetty server. After you issue the command, you should be able to see that Dropwizard has started the in-memory Jetty server on port 8080. If you see any warnings about health checks, ignore them. Your console logs should look something like this:We are now ready to test our application. Let's try to create two geolocations using the
POST
API and later try to retrieve them using theGET
method. Execute the following cURL commands in your terminal one by one:curl -H "Content-Type: application/json" -X POST -d '{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 41.803488, "longitude": -88.144040}' http://localhost:8080/geolocation
This should give you an output similar to the following (pretty-printed for readability):
{ "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } curl -H "Content-Type: application/json" -X POST -d '{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 9.568012, "longitude": 77.962444}' http://localhost:8080/geolocation
This should give you an output like this (pretty-printed for readability):
{ "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }
To verify whether your entities were stored correctly, execute the following cURL command:
curl http://localhost:8080/geolocation
It should give you an output similar to the following (pretty-printed for readability):
[ { "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }, { "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } ]
Excellent! You have created your first microservice with Dropwizard. Dropwizard offers more than what we have seen so far. Some of it is out of scope for this book. I believe the metrics API that Dropwizard uses could be used in any type of application. Therefore, we will look at how to use it in later chapters.
In the previous recipes, we saw how to create a microservice using various frameworks such as Spring Boot, WildFly Swarm, and Dropwizard. This recipe is going to be a little different for the fact that we are going to see how to create a self-managed RESTful API using a framework called SparkJava. Not to be confused with Apache Spark, the SparkJava framework claims to be a micro-framework for building web applications. Their HTTP API was inspired by Ruby's Sinatra framework. It is so simple that bringing up an HTTP GET
API requires fewer than ten lines of code. Owing to this, SparkJava is something that could be considered when you would like to quickly build HTTP-based microservices.
To avoid confusion and dependency conflicts in our project, we will create the SparkJava microservice as its own Maven project. This recipe is just here to help you get started with SparkJava. When you are building your production-level application, it is your choice to either use Spring Boot, WildFly Swarm, Dropwizard, or SparkJava based on your needs.
Similar to how we created other Maven projects, create a Maven JAR module with the groupId
com.packt.microservices
and name/artifactId
geolocation-sparkjava
. Feel free to use either your IDE or the command line. After the project is created, if you see that your project is using a Java version other than 1.8, follow the Creating a project template using STS and Maven recipe to change the Java version to 1.8. Perform a Maven update for the change to take effect.
The first thing that you will need is the SparkJava dependency. Add the following snippet to your project's pom.xml
file:
<dependencies> <dependency> <groupId>com.sparkjava</groupId> <artifactId>spark-core</artifactId> <version>2.5</version> </dependency> </dependencies>
We now have to create the domain object and service class. Follow the Writing microservices with WildFly Swarm recipe to create the following three files:
com.packt.microservices.geolocation.GeoLocation.java
com.packt.microservices.geolocation.GeoLocationService.java
com.packt.microservices.geolocation.GeoLocationServiceImpl.java
Let's see what each of these classes does. The GeoLocation.java
class is our domain object that holds the geolocation information. The GeoLocationService.java
interface defines our interface, which is then implemented by the GeoLocationServiceImpl.java
class. If you take a look at the GeoLocationServiceImpl.java
class, we are using a simple collection to store the GeoLocation
domain objects. In a real-time scenario, you will be persisting these objects in a database. But to keep it simple, we will not go that far.
The next thing that SparkJava needs is a controller. If you are familiar with Spring MVC, you can relate this controller to that of Spring MVC's. The controller has a collection of routes defined for each URL pattern in your API. Follow these steps:
Let's create our controller
com.packt.microservices.geolocation.GeoLocationController.java
with a stubbed-outGET
API:package com.packt.microservices.geolocation; import static spark.Spark.*; public class GeoLocationController { public static void main(String[] args) { get("/geolocation", (req, resp) -> "[]"); } }
The quickest way to test this is by running this class as a Java application. If you get
SLF4J
errors in your console after you start the application, add the following Maven dependency to yourpom.xml
file and restart your application:<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.21</version> </dependency>
The
slf4j-simple
dependency routes all theSLF4J
log messages to theSystem.err
stream.Your console logs should look something like this after the restart:
From the logs, we can clearly see that the service is running on port
4567
.Execute the following
curl
command in a terminal window to make sure our API is exposed. The response of the following command should be[]
, indicating there are no geolocations:curl http://localhost:4567/geolocation
Now let's finish building the APIs. We already have a very basic stubbed-out
GET
API. Let's just introduce the service class to the controller and call thefindAll
method. Similarly, let's use the service'screate
method forPOST
API calls. Before we do that, we need to do one more thing. By default, SparkJava does not perform JSON serialization and deserialization. We will be using a library calledgson
to do that. So add the following dependency to yourpom.xml
file:<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.7</version> </dependency>
Now let's replace the
main
method ofGeoLocationController.java
with this:public static void main(String[] args) { GeoLocationService service = new GeoLocationServiceImpl(); Gson gson = new Gson(); get("/geolocation", (req, resp) -> { return service.findAll(); }, gson::toJson); post("/geolocation", (req, resp) -> { return service.create(gson.fromJson(req.body(), GeoLocation.class)); }, gson::toJson); }
Yes, there are too many things happening here. Let's try to understand them one by one:
The
get
method now uses the service'sfindAll
methodThe third argument to the
get
method is theResponseTransformer
, which says how your response should be transformed before being sent to the clientSince the
ResponseTransformer
is aFunctionalInterface
with just one methodrender
that takes in the rendering logic as an object, we are passing the method reference to Gson'stoJson
method as the rendering logic here.
The
post
method, which uses the service'screate
method, uses Gson to transform the request body to aGeoLocation
value.
We are now ready to test our application. Restart the application. Let's try to create two geolocations using the POST
API and later try to retrieve them using the GET
method:
Execute the following cURL commands in your terminal one by one:
curl -H "Content-Type: application/json" -X POST -d '{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 41.803488, "longitude": -88.144040}' http://localhost:4567/geolocation
This should give you an output similar to the following (pretty-printed for readability):
{ "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } curl -H "Content-Type: application/json" -X POST -d '{"timestamp": 1468203975, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "latitude": 9.568012, "longitude": 77.962444}' http://localhost:4567/geolocation
This should give you an output like this (pretty-printed for readability):
{ "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }
To verify whether your entities were stored correctly, execute the following cURL command:
curl http://localhost:4567/geolocation
It should give you output similar to the following (pretty-printed for readability):
[ { "latitude": 41.803488, "longitude": -88.14404, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 }, { "latitude": 9.568012, "longitude": 77.962444, "userId": "f1196aac-470e-11e6-beb8-9e71128cae77", "timestamp": 1468203975 } ]
There are several other configurations that you can make to SparkJava, such as changing the default port, using query parameters, and using path parameters. I'll leave that to you to experiment.
At the beginning of this chapter, we quickly saw an overview of what microservices are and how they benefit organizations by making it easier to manage and deploy independent services. We looked at an example of a geolocation tracker application to see how it can be broken down into smaller and manageable services. Next, we saw how to create the GeoLocationTracker
service using the Spring Boot framework. We also learned how to expose our APIs that consume and read geolocations using Spring MVC. Next, we saw how to build the same application using WildFly Swarm, and JAX-RS. Later, we built the same application using Dropwizard. Finally, we saw how to implement the same service using the SparkJava framework.
I hope you now have a good understanding of what microservices are and how to create them using your favorite framework. The choice of framework completely depends on your needs and your current ecosystem. We strongly recommend you evaluate them before picking one. In the next chapter, we will learn how to package this microservice and later containerize it using Docker.