Building a Microservice Architecture with Spring Boot and Docker, Part III

Part III: Building Your First Microservice, its Container, and Linking Containers

We’re about ready to actually get started with building a microservice. We’ll start with creating a service to handle our employee object. The high-level steps we’ll follow are:

  • Set up a new Spring Boot project
  • Define our employee object
  • Wire up persistence
  • Expose web services
  • Define a Docker container to run our microservice, including linking to the Mongo container that we created in Part II
  • Run our container in our Docker Machine that we set up earlier

Setting up our Spring Boot project

We’ll be creating a folder under our project root (in a separate repository in production) to hold our service, and in this folder, we’ll create our build.gradle file. What’s nice about Spring Boot is that purely by defining the dependencies, it wires up a great deal of interoperability auto-magically. Our build.grade file looks like:

buildscript {
  repositories {
    jcenter()
  }
  dependencies { 
   classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.0.RELEASE'
  }
}
apply plugin: 'spring-boot'

repositories { jcenter()
}
dependencies {
  compile "org.springframework.boot:spring-boot-starter-actuator"
  compile "org.springframework.boot:spring-boot-starter-web"
}

Now, as we mentioned, we’ll be using an IDE for some of this, specifically Spring Tool Suite (STS), so we’ll have to add Gradle support. First, open STS, and on the dashboard page that opens, click the “IDE Extensions” link:

microservice part 3 pic 1

From that screen, select “Grade Support,” and click “Install.” Follow the prompts to completion, which will include restarting STS:

microservice part 3 pic 2

One restarted, select “Import project…,” select the (now available) Gradle project, point to your folder, and click “Build Model” — this will populate the list of projects with “Employee” — select it and click “Finish.” This will import the simple build.gradle we started the project with. Once the project is imported, the first thing is a Spring Boot configuration class. In this scenario, we’ll name them according to their service, so in this case: EmployeeBoot. Spring heavily leverages annotations and scanning, so minimal configuration is needed (imports omitted):

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class EmployeeBoot {

	   public static void main(String[] args) {
	        SpringApplication.run(EmployeeBoot.class);
	    }
	
}

Our Employee Class

Next, we’ll make our POJO class to hold employee information. We’ll keep it to the bare minimum fields for now and we’ll add more as necessary:
Employee.Class:

@Document(collection=”employees”)
public class Employee {

	@Id
	private String id; 
	
	private String email;
	private String fullName;
	private String managerEmail;

 // getters and setters omitted for brevity
}

Note the @Document annotation — this is what ties this object to be a Mongo document, and specifies the collection name for where the employee “document” should be stored.

Next we’ll define a Spring persistence repository to read and write these Employees:
EmployeeRepository.class:

public interface EmployeeRepository extends 
MongoRepository<Employee, String> {

}

The beauty of Spring is that’s all you need to write — just the interface that extends MongoRepository — not even an implementation. The two generic parameters indicate the type of the objects to persist (Employee), and the type of the unique identifier (String). Spring will wire up an implementation that handles 95% of all activities just from this declaration. The question that then follows is, how does Spring know where the database is? Spring will default to looking at localhost:27017. This obviously isn’t going to work, so we’ll need to set this straight. We could implement our own MongoTemplate bean, but fortunately Spring allows us to pass in the connection information via Java Properties. We can define a properties file or pass them in on the command line. We’ll go with the latter as it’s pretty easy to pass it in when building our container later on. The last file we’ll need to create is a REST endpoint or two to make sure that this works. We’ll make a quick Spring Controller, then we’ll be able to test this out.
EmployeeController.java:

@RestController
@RequestMapping("/employee")
public class EmployeeController {
	
	@Autowired
	EmployeeRepository employeeRepository;

	@RequestMapping(method = RequestMethod.POST)
	public Employee create(@RequestBody Employee employee){
		
		Employee result = employeeRepository.save(employee);
		return result;
	}
	
	@RequestMapping(method = RequestMethod.GET, value="/{employeeId}")
	public Employee get(@PathVariable String employeeId){
		return employeeRepository.findOne(employeeId);
	}
	
	
}

The RestController and RequestMapping class-level annotation tell Spring to expose this as a REST service accepting JSON, and expose it at the /employee URI path. The @Autowired annotation tells Spring to use its auto-generated implementation of the repository interface we defined above, and inject it into this controller.Now onto the specific operations — the @RequestMapping, when applied at the method level, indicate what method is to be used based on the HTTP verb used (POST vs GET in this case). Additionally, for GET, we indicate an {employeeId} in the URL path, such as using /employee/abcd1234 to look up the employee and return it.

No we have enough to test! First, we need to build and run our Spring Boot application. There are a number of ways to do this from within Eclipse, but we’ll start in the command line and work our way up. From your Employee folder, type (reference the part 3/step 2 branch from Git to skip to here): gradle build. This should compile the Spring Boot application into build/libs/Employee.jar. This jar includes everything needed to run the app, including an embedded servlet container (Tomcat by default).

Before we run this and test it, we need to back up a bit — where was our Mongo again? Looking at our “Docker PS” output, we remember that port 32777 on our VM was forwarded to our Mongo container 27017, and our VM IP was 192.168.99.100. As mentioned previously, we can pass this connection information to Spring by passing an environment property, so the command to run our app is:

java -Dspring.data.mongodb.uri=mongodb://192.168.99.100:32777/micros -jar build/libs/Employee.jar

Once the app starts (which should be within 1-4 seconds), you can hit the web service with your REST tool of choice

microservice part 3 pic 3

Don’t forget to include the HTTP header “Content-Type” with a value of “application/json.” You should receive a response of (your ID will differ):

{
  "id": "55fb2f1930e07c6c844b02ff",
  "email": "dan.greene@3pillarglobal.com",
  "fullName": "Daniel Greene",
  "managerEmail": null
}

We can test our GET method by calling:

http://localhost:8080/employee/55fb2f1930e07c6c844b02ff

You should get the same document back. Huzzah! Our service works!

Turning a Boot into a Container

Now we need to build our first customer container, which will run our Employee microservice. WE do this by defining a Dockerfile. This Dockerfile will define what it takes to take a “bare” image and turn it into our lean, mean, microservice machine. Let’s look at the file and then go through it step by step (reference part 3/ step 3 to jump to here):

FROM java:8
VOLUME /tmp
ADD build/libs/Employee.jar app.jar
EXPOSE 8080
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Dspring.data.mongodb.uri=mongodb://mongodb/micros", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
  • We start with a “standard” image that already includes Java 8 installed (called “Java” and tagged “8”)
  • We then define that a volume named /tmp should exist
  • We then add a file from the local filesystem, naming it “app.jar.” The re-naming isn’t necessary, just an option available
  • We state that we want to open port 8080 on the container
  • We run a command on the system to “touch” the file. This ensures a file modification date on the app.jar file
  • The ENTRYPOINT command is the “what to run to ‘start'” command — we run Java, setting our Spring Mongo property and a quick additional property to speed up the Tomcat startup time, and then point it at our jar

Now, we build the container image by running docker build -t microservicedemo/employee .

We can see the results by typing docker images 

REPOSITORY                  TAG             IMAGE ID            CREATED             VIRTUAL SIZE
microservicedemo/employee   latest          364ffd8162b9        15 minutes ago      846.6 MB

The question that follows is “how does our service container talk to the Mongo container?” For that, we get into container linking. When you run a container, you can pass an optional –link parameter, specifying the running container name that the new container needs to be able to communicate with. So with our command docker run -P -d --name employee --link mongodb microservicedemo/employee we fire up our new container image, exposing its ports (-P) in the background (-d), naming it employee (–name), and telling it to link to the container named “mongodb” (–link). Linking these does the following:

  • Creates a hosts file entry in the employee container pointing at the running location of the MongoDB container
  • Inserts a number of environment variables in the employee container to assist with any other programmatic access needed. To see them run: docker exec employee bash -c 'env |grep MONGODB'
  • Allows containers to communicate directly over ports exposed, so there is no need to worry about the hose machine part mapping. If you remember above, we set the Spring Mongo URL to MongoDB as the hostname (mongodb://mongodb/micros), so with the hostfile entries and Mongo running on the default port, the boot container can see the database

With our container running, we can now exercise the same web service running as a container this time (for me, port 8080 of the container was proxied to 32772 of the VM host):

microservice part 3 pic 4

We’ve made a lot of progress in this part of the series. We have two containers that work and can talk to each other. Next, we’ll add in some additional services/containers and look at the process for making updates and working with CI.

This blog is the third of four parts:

Dan Greene

Dan Greene

Director of Architecture

Dan Greene is the Director of Architecture at 3Pillar Global. Dan has 18 years of software design and development experience, with software and product architecture experience in areas including eCommerce, B2B integration, Geospatial Analysis, SOA architecture, Big Data, and Cloud Computing. He is an AWS Certified Solution Architect who worked at Oracle, ChoicePoint, and Booz Allen Hamilton prior to 3Pillar. Dan is a graduate of George Washington University. He is also a father, amateur carpenter, and runs obstacle races including Tough Mudder.

12 Responses to “Building a Microservice Architecture with Spring Boot and Docker, Part III”
  1. Thisadee P on

    Are you using “mongo” or “mongodb” as container name? in 2nd part you create it with ” docker run -P -d –name mongo mongo”

    Reply
  2. Pepster on

    Nooby question: If you want to scale out the Employee service and the MongoDB service to different hosts (what you probably want to do in production), do you still link them like this?

    Reply
  3. Jorge on

    Just one point: When you say “build.grade file” in the Setting up paragraph I guess you mean “build.gradle” ( typo)

    Great post anyway and very well explained

    Reply
  4. vasu on

    Hi ,

    Do you have complete source code or any repository for employee to build in spring boot?

    Thanks
    Vasu

    Reply
  5. Nilang on

    Hi Dan,

    Great article with excellent explanation. I am using Windows 10 and started both containers successfully but the employee container is still looking for mongodb on port 27017 and timing out. What could be the issue?

    Regards,

    Nilang

    Reply
    • Dan Greene on

      check to make sure that the container is listening properly:
      docker ps
      and look at the port listings. If the containers are in the same docker network (started from docker-compose makes them in the same network by default), they should be able to see each other w/o any port exposure to your machine

      Reply
  6. Jonathan Gimeno on

    To make it work I needed to add this dependency too.

    compile(“org.springframework.boot:spring-boot-starter-data-mongodb”)

    Reply
Leave a Reply

SUBSCRIBE TODAY


Sign up today to receive our monthly product development tips newsletter.