Docker Setup for Service Based Architecture Application

What is Service Based Architecture ?

At first Micro Service Architecture & Service Based Architecture looks similar but both are different from each other.

Micro service architecture advocates smaller components. An application can consist of hundred or thousands of micro services whereas Service based architecture advocates breaking the code apart in the domain-centric way. An application can consist of 10–12 deployable services. These services may have separate database or they may share same database.

Managing few micro services is easy but as number of micro services increases, challenges to manage them is not an easy task. Number of network calls also increases.

In case of Service based architecture, number of services are limited therefore managing it is not a challenge. Also number of network call is less therefore should give better performance.

ThoughtWorks director Neal Ford argued in a talk that organizations transition more easily from a monolithic architecture to a service-based architecture than to a microservices architecture
Ref: https://www.infoq.com/news/2016/10/service-based-architecture

Why we chose Service Based Architecture Over Micro Services

Background: We are building an ERP Software. It is going to use by 50–100 people at a time. We are team of 3 developer and we need to deliver first release in 3 months.

We aim to build scalable and maintainable product. Monolith is out of option. We had 2 options, Micro Service OR Service Based Architecture. Micro Service requires complex setup and will double our efforts. As we have limited team size and our timelines are fixed therefore Service Based Architecture with common database made more sense for us.

Challenges we faced

We had 8 repository, one for each services. Setting up project on local for new developer is very time consuming. Every service needed to setup separately.

Apart from setting up all services. We need to install postgres, redis & elasticsearch. If you are stuck while installing any one of it then it may eat up whole day.

Also starting up application required starting all 8 services manually (which is not interesting thing to do everyday)

Docker for our rescue

We created a single repository for all services. Now getting all changes on local is just a git pull command away.

With docker, we can setup all services with all dependency with just one command.

docker-compose build

And we start our application (all services) by

docker-compose up

Setting up docker compose for an application which consist of 8 services (4 Rails-Api Backend & 4 React Frontend)

Application Directory structure looks like:

project


└───service-1-api

|───service-1-web

└───service-2-api

|───service-2-web

└───service-3-api

|───service-3-web

└───service-4-api

|───service-4-web

└───docker-compose.yml

└───Dockerfile

└───Dockerfile-React
* Dockerfile is for api images
* Dockerfile-React is for react application images
Of course our services are not named as service-1 & service-2. I have changed it deliberately for privacy.

Our docker-compose.yml:

version: '3.6'
services:
db:
image: postgres
redis:
image: 'redis:latest'
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:6.5.4
container_name: elasticsearch
environment:
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "discovery.zen.ping.unicast.hosts=elasticsearch"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata1:/usr/share/elasticsearch/data
ports:
- 9200:9200
service-1-api:
build:
context: './service-1-api'
dockerfile: $PWD/Dockerfile
volumes:
- $PWD/service-1-api:/app
command: bundle exec puma -p 3000
ports:
- 3000:3000
depends_on:
- db
service-1-web:
build:
context: './service-1-web'
dockerfile: $PWD/Dockerfile-React
volumes:
- $PWD/service-1-web/:/app/
ports:
- 3001:3001
environment:
NODE_ENV: development
CHOKIDAR_USEPOLLING: 'true'
service-2-sidekiq:
depends_on:
- db
- redis
- elasticsearch
build:
context: './service-2-api'
dockerfile: $PWD/Dockerfile
command: bundle exec sidekiq -C config/sidekiq.yml
volumes:
- $PWD/service-2-api:/app
service-2-api:
build:
context: './service-2-api'
dockerfile: $PWD/Dockerfile
volumes:
- $PWD/service-2-api:/app
command: bundle exec puma -p 3002
ports:
- 3002:3002
depends_on:
- db
- elasticsearch
- service-2-sidekiq
stdin_open: true
tty: true
service-2-web:
build:
context: './service-2-web'
dockerfile: $PWD/Dockerfile-React
volumes:
- $PWD/service-2-web/:/app/
command: npm start
ports:
- 3003:3003
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
service-3-sidekiq:
depends_on:
- db
- redis
- elasticsearch
build:
context: './service-3-api'
dockerfile: $PWD/Dockerfile
command: bundle exec sidekiq -C config/sidekiq.yml
volumes:
- $PWD/service-3-api:/app
service-3-api:
build:
context: './service-3-api'
dockerfile: $PWD/Dockerfile
volumes:
- $PWD/service-3-api:/app
command: bundle exec puma -p 3004
ports:
- 3004:3004
depends_on:
- db
- elasticsearch
- service-3-sidekiq
stdin_open: true
tty: true
service-3-web:
build:
context: './service-3-web'
dockerfile: $PWD/Dockerfile-React
volumes:
- $PWD/service-3-web/:/app/
command: npm start
ports:
- 3005:3005
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
service-4-api:
build:
context: './service-4-api'
dockerfile: $PWD/Dockerfile
volumes:
- $PWD/service-4-api:/app
command: bundle exec puma -p 3006
ports:
- 3006:3006
depends_on:
- db
stdin_open: true
tty: true
service-4-web:
build:
context: './service-4-web'
dockerfile: $PWD/Dockerfile-React
volumes:
- $PWD/service-4-web/:/app/
working_dir: /app
command: npm start
ports:
- 3007:3007
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
volumes:
esdata1:
driver: local

*using this docker-compose.yml configuration, service restart is not required on code change.

Dockerfile:

FROM ruby:2.5.3-alpine
RUN apk add --update bash build-base postgresql-dev tzdata
RUN gem install rails -v '5.1.6'
WORKDIR /app
ADD Gemfile Gemfile.lock /app/
RUN bundle install
COPY . /app/

Dockerfile-React

FROM node:11.6.0-alpine
WORKDIR '/app'
# Install yarn and other dependencies via apk
RUN apk update && apk add yarn python g++ make && rm -rf /var/cache/apk/*
COPY package.json yarn.lock /app/
RUN yarn install
RUN yarn global add react-scripts
COPY . ./
CMD ["npm", "run", "start"]

For adding new gems in rails api service, add gem in Gemfile and build new image for that service, example:

docker-compose build service-1-api

For adding new package in react app service, use

docker-compose run service-1-web yarn add `package-name`

Conclusion:

Service Based Architecture is good alternative for applications where Manpower & Time are constraints.

In Next Blog I will write about deploying this Application on Amazon ECS(Elastic Container Service).