Microservices is a software architectural style that structures an application as a collection of small, loosely coupled, and independently deployable services. Each microservice represents a specific business capability and can be developed, deployed, and scaled independently of other services.
In a microservices architecture, the application is broken down into a set of smaller, self-contained services that communicate with each other through well-defined APIs (Application Programming Interfaces). These services can be developed using different technologies, programming languages, and frameworks, as long as they can communicate with each other effectively.
However, adopting a microservices architecture also introduces challenges, such as managing distributed systems, ensuring consistent communication, dealing with data consistency across services, and monitoring the interactions between services.
Overall, microservices provide a modular and scalable approach to building complex applications by breaking them down into smaller, more manageable components that can be developed and deployed independently.
This Project is about containerizing an E-Commerce Application. This Application is made up of Microservices that are loosely coupled together.
ARCHITECTURAL DESIGN
OVERVIEW
We access the E-commerce Application through the Nginx (API Gateway) which listens for requests and routes to the Client microservice (Angular) using the URL which loads the frontend pages of the website. The client api uses an nginx configuration to route request to the backend services which is the Emart api and Books api. E-mart api (NodeJS) on /api which uses a NoSQL database service - mongoDB and the Books api (Java) which uses a MySQL database and is accessed on /webapi.
The Four microservices to be containerized include:
Node.js microservice: A backend service built with Node.js and Express.
Angular microservice: A frontend application built with Angular.
Java microservice: A backend service built with Java and Spring Boot.
Nginx: A web server used for routing and load balancing.
Create a folder
$ mkdir Docker-Engine
$ cd Docker-Engine
Pick an ubuntu box from the Vagrant cloud and run
$ vagrant init <vagrant-box>
Open the Vagrantfile and assign a unique IP address and increase the RAM size for the setup to run faster.
Then run $ vagrant up to bring up the VM
Clone the repository $ git clone https://github.com/dybran/Containerizing-Microservices-Project.git
$ cd Containerizing-Microservices-Project/E-martApp-Project
Install Docker Engine using the Documentation.
Then $ vagrant ssh to login
Add the vagrant user to the docker group
$ usermod -aG docker vagrant
Log out and login then run
$ id vagrant
N/B: Make sure it is in the same folder as the vagrantfile.
This is a Mono Repository which means that all the micro services source code is in one repository. Based on requirement, the micro services source codes are to be in different repository which is beneficial to create seperate CI/CD pipelines for the microservices.
Write the Dockerfiles
For Client
FROM node:14 AS CLIENT_WEB_ARTIFACT
WORKDIR /usr/src/app
COPY ./ ./client
RUN cd client && npm install && npm run build --prod
# Use official nginx image as the base image
FROM nginx:latest
# Copy the build output to replace the default nginx contents.
COPY --from=CLIENT_WEB_ARTIFACT /usr/src/app/client/dist/client/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Expose port 4200
EXPOSE 4200
This is a multistage Dockerfile which builds the artifact and copies the artifact and the ngnix.conf file to the desired location.
N/B: Multistage Dockerfile is used so that the dependencies downloaded during the build process is not copied to the desired location. The dependencies increases the size of the image.
For Java api - Emart api
FROM openjdk:8 AS JAVA_API_ARTIFACT
WORKDIR /usr/src/app/
RUN apt update && apt install maven -y
COPY ./ /usr/src/app/
RUN mvn install -DskipTests
FROM openjdk:8
WORKDIR /usr/src/app/
COPY --from=JAVA_API_ARTIFACT /usr/src/app/target/book-work-0.0.1-SNAPSHOT.jar ./book-work-0.0.1.jar
EXPOSE 9000
ENTRYPOINT ["java","-jar","book-work-0.0.1.jar"]
# Test
This multistage Dockerfile builds the artifact and copies the artifact to the desired location.
For nodeapi - Books api
FROM node:14 AS NODE_API_ARTIFACT
WORKDIR /usr/src/app
COPY ./ ./nodeapi/
RUN cd nodeapi && npm install
FROM node:14
WORKDIR /usr/src/app/
COPY --from=NODE_API_ARTIFACT /usr/src/app/nodeapi/ ./
RUN ls
EXPOSE 5000
CMD ["/bin/sh", "-c", "cd /usr/src/app/ && npm start"]
# Test3
For nginx
We only need the configuration file - default.conf to be attached to an nginx container which will be declared in the docker-compose.yml file.
The default.conf file
#upstream api {
# server api:5000;
#}
#upstream webapi {
# server webapi:9000;
#}
upstream client {
server client:4200;
}
server {
listen 80;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://client/;
}
location /api {
# rewrite ^/api/(.*) /$1 break; # works for both /api and /api/
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_http_version 1.1;
proxy_pass http://api:5000;
}
location /webapi {
# rewrite ^/webapi/(.*) /$1 break;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
## proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://webapi:9000;
}
}
The default.conf file is configured to send and recieve requests from Client (/), Emart api (/api), Books api (/webapi)
Write the Docker compose file
With Docker Compose, you can define and orchestrate the deployment of complex multi-container applications. Instead of managing each container individually, you define the relationships and dependencies between the client, javaapi, nodeapi and nginx in the Compose file, which simplifies the process of setting up and running the application.
docker-compose.yml file
version: "3.8"
services:
client:
build:
context: ./client
ports:
- "4200:4200"
container_name: client
depends_on:
- api
- webapi
api:
build:
context: ./nodeapi
ports:
- "5000:5000"
restart: always
container_name: api
depends_on:
- nginx
- emongo
webapi:
build:
context: ./javaapi
ports:
- "9000:9000"
restart: always
container_name: webapi
depends_on:
- emartdb
nginx:
restart: always
image: nginx:latest
container_name: nginx
volumes:
- "./nginx/default.conf:/etc/nginx/conf.d/default.conf"
ports:
- "80:80"
emongo:
image: mongo:4
container_name: emongo
environment:
- MONGO_INITDB_DATABASE=epoc
ports:
- "27017:27017"
emartdb:
image: mysql:8.0.33
container_name: emartdb
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=emartdbpass
- MYSQL_DATABASE=books
N/B:
- In the MONGO_INITDB_DATABASE=epoc, the epoc is a requirement in nodeapi/config/keys.js developed by the developers.
- In the MYSQL_ROOT_PASSWORD=emartdbpass, emartdbpass is found in the javaapi/src/main/resources/application.properties.
- For the container image to be created, depends_on should be present.
Build and Run
Switch to root user
$ sudo -i
Change into /vagrant/ directory
$ cd /vagrant/
Change into the E-martApp-Project
$ cd Containerizing-Microservices-Project/E-martApp-Project
Run the command
$ docker compose up -d
Display the images
$ docker images
Check for running containers
$ docker ps
Access the Application on browser
Login
We can stop the containers by running the command
$ docker compose down
Whenever developers make changes to the codes, we pull the updated code from github. This can also be automated using a CI/CD pipeline.
$ git pull
and run the $ docker compose build command to build the changes.
In microservices, it is encouraged to make small changes multiple times than making big changes at once.
