Microservices with Spring Boot

2016-09-24

A while ago I was working on a demo project using Vue.js, you can find it here. The demo only included front-end code, no back-end components at all. I was looking for a modern approach to create a scalable back-end, hence microservices!

A book that you should read when starting out with miroservices is "Building Microservices
Designing Fine-Grained Systems"
 by Sam Newman, it helped me enormously to understand all of the concepts... There is also a video training course by Josh Long and Phil Webb, a very helpful resource that should help you getting started.

What?

A back-end written in Java using Spring Boot that helps tourists find lovely attractions in a country of intrest. Tourists are also able to add attractions to their favorites.

Code

All of the code that will be referenced during this post can be found on Github.

Setup

This setup might seem a bit chaotic at first and my 'diagram-skills' don't really help but I'll try my best at explaining it:

You can find the diagram also on imgur

Configuration

A configuration server starts and pulls the configuration for several components from a very simple git repository, it looks like this:

This is the content for 'travel-attraction-service.yml', it contains information about which port the server should run on, how to connect to its database and where to find Eureka (Netflix service registry):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
  port: 8082

spring:
  database:
    driverClassName: org.postgresql.Driver
  datasource:
    url: jdbc:postgresql://localhost:5432/travel-attraction
    username: postgres
    password: postgres
  jpa:
    hibernate:
      ddl-auto: validate
    database-platform: org.hibernate.dialect.PostgreSQLDialect

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

All components that require configuration will get them from the configuration server (runs on port 8888). Example for the attraction service:

1
2
3
4
5
6
7
spring:
  application:
    name: travel-attraction-service
  cloud:
    config:
      label: develop
      uri: http://localhost:8888

Eureka, service registry

Almost every component in the setup registers itself with Eureka (except the configuration server).

Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.

Eureka keeps track of all registered services and uses naming convention to map/load balance calls. The use of Eureka will be become more clear in the next section.

API Gateway

All requests from the client (postman, angular webapp...) will be send to the API Gateway, this component is responsible for routing requests to their desired endpoint. We use Zuul from Netflix in this setup.

Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.

The configuration looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
  port: 8765

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

zuul:
  routes:
    travel-auth-server: /auth/**
    travel-country-service: /api/country-service/**
    travel-attraction-service: /api/attraction-service/**
    travel-favorite-service: /api/favorite-service/**

Lets analyse the following line under zuul.routes:

1
travel-auth-server: /auth/**

All requests starting with '/auth' will be routed to the authentication server, it uses Eureka behind the scenes to map the name (travel-auth-server) to a real IP.

Authentication server

For the authorization I use OAuth2, I am not going to discuss the framework in detail, I am no security experts by any means, you'll find enough material on the interwebs.

Simply put, a request send to the token endpoint with the correct credentials (password grant type) will return an access token that can be used to send authorized request to other services in the stack.

Request:

1
http://localhost:8765/auth/oauth/token?grant_type=password&username=travel-admin&password=travel-password

Response:

1
2
3
4
5
6
7
8
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzQ3MjA2NzksInVzZXJfbmFtZSI6InRyYXZlbC1hZG1pbiIsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZTRmMjljNGItYTUzOS00M2VmLTk0ZWMtYTE2Yjk2NjlmODQ3IiwiY2xpZW50X2lkIjoidHJhdmVsLWNsaWVudCIsInNjb3BlIjpbIkNPVU5UUllfUkVBRCIsIkFUVFJBQ1RJT05fUkVBRCIsIkZBVk9SSVRFX1JFQUQiXX0.flAbcPDAxyyQyzuMtdfH_4jwIFOrsnDnELO3-6EiSpu1DeQsa5SoQu4bKHKyKhk80eBCWNYyVUIgz7lvyifLuGF4O7uZghlnNpgM97IPDdwu_ocs1ku6ZD9ls2ymQXm5E0m3od4Cnlhr6NCp5m0jbI6WpwErng6eFP_wdqD_o1moGr_2qbY-gzy0sN3-SftGPCniTV4GUZNlUQd73PGMN6DghyYwyDq3banKELP68duAx1jQyyI_bVroyfjge5Uf1NVOuhQI3fXxh-lcw8XUhnPQ0IVQuQ_zNbO067DbX2O6NH-RKggH425O6AcXg1FnaspDHdu8v8BEsPINn-OMbg",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ0cmF2ZWwtYWRtaW4iLCJzY29wZSI6WyJDT1VOVFJZX1JFQUQiLCJBVFRSQUNUSU9OX1JFQUQiLCJGQVZPUklURV9SRUFEIl0sImF0aSI6ImU0ZjI5YzRiLWE1MzktNDNlZi05NGVjLWExNmI5NjY5Zjg0NyIsImV4cCI6MTQ3NzIyNjI3OSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiIwY2Y1YzI4Yi01ZGQ5LTRjM2QtYWMwNC02MDlmZmI0MDdiYjUiLCJjbGllbnRfaWQiOiJ0cmF2ZWwtY2xpZW50In0.AaDtRQ1WvCfsg-fr-6VgyE4QA11g9RSFhr3RZUE4atyg-Ng6WVu04fnGq6l4vDWYgkonc8kLbW4Xoj9zZyeZVbsIp2pSvGSZakVG9bFhym3pq4hKR4lrB-yXTwrU3KTQ39M7G5KNDOcF62bOVxI7iz5DNDH5e2hrkqdeeA3OlKeI518zEkx63FEAVG6yJRpJfGOM6ydt_Pmm2lLzBQAggM4T_UmP7fPngvZGwpyR5yrJlp2473qiuzlLflwWXCSS_9dwZBevGIcmaSeK_s57vkRws15KIyTPsyCIapItpnaLg6xCeYWIY7zUoLjsMhpJUcJIqHM7tNBKWtbCiSV-YQ",
  "expires_in": 86399,
  "scope": "COUNTRY_READ ATTRACTION_READ FAVORITE_READ",
  "jti": "e4f29c4b-a539-43ef-94ec-a16b9669f847"
}

Notes:

  • Each call to a secured component needs have an 'Authorization' header with a value of 'Bearer ACCESS_TOKEN'
  • The user details and roles are stored in the travel-auth database, more info in the database section.

Services

An authorized user (one that has an access token) can send request to all secured services. In this setup we have 3: favorite, country and attraction service.

For example, country-service request:

1
http://localhost:8765/api/country-service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
  {
    "name": "Belgium",
    "iso": "BE"
  },
  {
    "name": "Netherlands",
    "iso": "NL"
  },
  {
    "name": "Germany",
    "iso": "DE"
  },
  {
    "name": "France",
    "iso": "FR"
  }
]

Database

There are 4 postgresql databases that are being used in this setup:

  • travel-auth
  • travel-country
  • travel-attraction
  • travel-favorite

I recommend using pgAdmin as GUI administration tool for PostgreSQL. It's is import that you understand that each microservice should have its own database, this is a good article about the topic.

Provisioning of each database is done using Flyway.

Flyway is an open-source database migration tool. It strongly favors simplicity and convention over configuration.

Each service has a versioned SQL file(s) that contains the necessary statements, for example 'V1__init.sql':

1
2
3
4
5
6
7
8
9
10
CREATE TABLE country (
	id bigserial PRIMARY KEY,
	iso char(2) NOT NULL,
	name text NOT NULL
);

INSERT INTO country(name, iso) VALUES ('Belgium', 'BE');
INSERT INTO country(name, iso) VALUES ('Netherlands', 'NL');
INSERT INTO country(name, iso) VALUES ('Germany', 'DE');
INSERT INTO country(name, iso) VALUES ('France', 'FR');

Flyway will automatically create a metadata table that will keep track of already executed files.

Postman calls

Here is a collection of Postman calls that you can use to test the microservices.

Conclusion

Spring Boot makes creating microservices a piece of cake, their integration with components written by Netflix is a god sent. I do have some thinking left about the following:

  • How can I prevent port conflicts for 2 instances of a microservice, the configuration has a fixed port.
  • Should I keep the secret for the oAuth2 integration, seems kind of useless to me in a client side application where obfuscation is impossible.
  • Dockerizing the services shouldn't be to hard, maybe something for a future post?

Between coding the setup and writing this post some time has passed. I've continued reading articles on the topic and found out about Kubernetes by Google.

Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications.

It groups containers that make up an application into logical units for easy management and discovery. Kubernetes builds upon 15 years of experience of running production workloads at Google, combined with best-of-breed ideas and practices from the community.

Kubernetes seems to simplify things even further, components like Eureka could be completely replaced! It's is very interesting but also very new, the learning curve seems steep.

If you have a comment or question just drop me a line below.

Created by Jeroen Druwé