cft

Building Your First Microservice

In this article, we will try to build a ‘hello world’ web application using microservices architecture.


user

Srinaveen Desu

3 years ago | 13 min read

In this article, we will try to build a ‘hello world’ web application using microservices architecture. In my previous article, we have learned how to containerize an application. Now, the next part of the job would be to establish communication between them, and we would be able to say that we have built a microservice.

So coming to the big question, what do you understand by microservice? I guess the answer might have already struck your mind and it would most definitely be correct. As the name goes, microservice is an architecture style where each service is responsible for completing a specific job and these services communicate with each other to solve a bigger use-case problem.

That was enough of the story, the next step in advancing through this article is that we need to understand the use-case that we are trying to solve here. Our goal is to build two different services, containerize them, and establish communication between them.

Breaking it up further, we build two simple web applications using flask and flask-restful, one being a normal web application and the other being a REST-based web application. From the welcome page of the first service, when we are able to successfully talk to the other application(second service) we would accomplish our goal.

Before you go any further, make sure you install docker, docker-compose, and Python3.

Let’s start building something awesome.

Start by creating a directory, sub-directories, and files similar to the below structure.

$ mkdir microservices$ touch microservices/Docker-compose.yaml################## First Service #########################$ mkdir microservices/service1/app/templates$ touch microservices/service1/Dockerfile$ touch microservices/service1/app/app.py$ touch microservices/service1/app/requirements.txt$ touch microservices/service1/app/templates/index.html$ touch microservices/service1/app/templates/test_service.html################## Second Service #########################$ mkdir microservices/service2$ touch microservices/service2/Dockerfile$ touch microservices/service2/app/app.py$ touch microservices/service2/app/requirements.txt├── microservices
│ ├── Docker-compose.yaml
│ ├── service1
│ │ ├── app
│ │ │ ├── app.py
│ │ │ ├── requirements.txt
│ │ │ └── templates
│ │ │ ├── index.html
│ │ │ └── test_service.html
│ │ └── Dockerfile
│ ├── service2
│ │ ├── app
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ └── Dockerfile
│ └──

With this you have set up a blueprint for your microservice application and all you need now is to build the solution. Please note that the amount of code we would be writing is very minimal as the goal of this article is to understand building microservices using container technology.

The contents of service1/app/app.py is as follows:

from flask import Flask, render_template, request, flash, redirect, url_for

import requests
import os

app = Flask(__name__)
app.secret_key = 'thisisjustarandomstring'


@app.route('/', methods=['POST', 'GET'])
def index():
if request.method == "POST":
if request.form['submit_button'] == 'Test connection':
ret = os.system('ping welcome-service -w 1')
if ret == 0:
flash('The test was successful', "green")
return redirect(url_for("test"))
else:
flash('The test failed. Welcome-service is not UP', "red")
return redirect(url_for("index"))

elif request.form['submit_button'] == 'Reset':
return redirect(url_for("index"))
else:
pass
return render_template('index.html')


@app.route('/test', methods=['POST', 'GET'])
def test():
if request.method == "POST":
if request.form['submit_button'] == 'Reset':
return redirect(url_for("index"))
elif request.form['submit_button'] == 'Get Message':
data = requests.get('http://welcome-service:5051').json()
flash(str(data), "green")
return redirect(url_for('test'))
else:
pass
return render_template('test_service.html')


if __name__ == '__main__':
app.run(debug=True, port=5050, host="0.0.0.0")

Our first service hello-world-service is a flask based web application where we have two endpoints / and /test .

The former endpoint / is the welcome page where we have a button which checks if the connection to the second service welcome-serviceis up and running. (I am currently checking this by sending a ping request to the welcome-service., we don’t want to complicate the code right). The ping request code is as the following:

ret = os.system('ping welcome-service -w 1')
if ret == 0:
flash('The test was successful', "green")
return redirect(url_for("test"))
else:
flash('The test failed. Welcome-service is not UP', "red")
return redirect(url_for("index"))

The first function index gets the homepage index.html .It also has a button Test Connection, which when clicked makes a ping request to the other service and if successful, navigates to /test endpoint where we will be talking to the second servicewelcome-service .

The index home page
The index home page

The second function test is for handling the /test URL. On this page, we have two buttons namely reset and Get Message .

The former navigates us to the home page, while the latter talks to the second service and gets the message and displays the same. We talk to the second service by sending a simple GET request to the REST-based welcome-service . The code for the same is as follows:

data = requests.get('http://welcome-service:5051').json()

The /test page looks like the following:

The page navigated to when ping is successful to the second service
The page navigated to when ping is successful to the second service

The service1/app/templates/index.html code is as follows:

<html>
<body>
<h1>Hello World</h1>

<h3>Test Welcome service is up and running</h3>
<form action="" method="POST">
<br>
<input type="submit" name="submit_button" value="Test connection">
</form>

{% for category, message in get_flashed_messages(with_categories=true) %}
<ul>
<li>
<div style="color: {{ category }}">{{ message }}</div>
</li>
<form action="" method="POST">
<br>
<input type="submit" name="submit_button" value="reset">
</form>
</ul>
{% endfor %}
</body>
</html>

The service1/app/templates/test_service.html is as follows:

<html>
<body>
<h1>Hello World</h1>

<h3>Get message from the other microservice (welcome-service)</h3>
{% for category, message in get_flashed_messages(with_categories=true) %}
<ul>
<li>
<div style="color: {{ category }}">{{ message }}</div>
</li>

</ul>
{% endfor %}
<form action="" method="POST">
<input type="submit" name="submit_button" value="Reset">
<input type="submit" name="submit_button" value="Get Message">
</form>
</body>
</html>

The service1/app/requirements.txt is as follows:

flask
requests

Now, let’s build the second welcome-service .

The code for service2/app/app.py is as follows:

from flask import Flask
from flask_restful import Resource,Api
import socket

app = Flask(__name__)
api = Api(app)

class Welcome(Resource):
def get(self):
hostname = socket.gethostname()

return {'message': f'Welcome to microservice Demo! from {hostname}'}

api.add_resource(Welcome, '/')

if __name__ == '__main__':
app.run(debug=True,port=5051,host="0.0.0.0")

The code for service2/app/requirements.txt is as follows:

flask
flask-restful

This concludes the code for building the web-application as a service. Now, all that we need to do is to containerize these two web-application services and build our microservice.

The Dockerfile for the first service service1/Dockerfile is:

FROM python:3.8-alpine

MAINTAINER Srinaveen Desu

COPY ./app/requirements.txt /app/requirements.txt

WORKDIR /app

RUN apk add --update \
&& pip install --upgrade pip \
&& pip install -r requirements.txt \
&& rm -rf /var/cache/apk/*

COPY ./app /app

CMD python app.py

The Dockerfile for the second service service2/Dockerfile is:

FROM python:3.8-alpine

MAINTAINER Srinaveen Desu

COPY ./app/requirements.txt /app/requirements.txt

WORKDIR /app

RUN apk add --update \
&& pip install --upgrade pip \
&& pip install -r requirements.txt \
&& rm -rf /var/cache/apk/*

COPY ./app /app

CMD python app.py

Note: The two Docker files above are exact replica of what was illustrated in my previous article. To understand how they work please refer to the same.

Now coming to our final piece in our module which is responsible for building and bringing up our containers is the docker-compose.yaml file. It looks as follows:

version: '3.3' # version of compose format

services:
hello-world-service:
build: ./service1 # path is relative to docker-compose.yml location
hostname: hello-world-service
ports:
- 5050:5050 # host:container
networks:
sample:
aliases:
- hello-world-service

welcome-service:
build: ./service2
hostname: welcome-service
ports:
- 5051:5051 # host:container
depends_on:
- hello-world-service
networks:
sample:
aliases:
- welcome-service

networks:
sample:

version : The version of docker-compose.

services : This tells all the services that need to be running. In our case the two services are hello-world-service and welcome-service .

build : The build path for each service.

ports : Which host port does the host user talk to and which port is the container exposing for the host to talk to.

networks : We have created a network here sample .What we can infer from this is that, all the services that have this sample network come under one umbrella and these services would be able to communicate without any subnet isolation.

By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.

We can create different networks in case we need the isolation of services, but that is a whole different story to deal with. We can explore this in one of my future articles.

aliases : An alias hostname for the container that can be detected in the network that the container is being built on.

depends_on : All the services that the service depends on. If the dependent services are not working, the service would not come up.

That was a long way to go right. Well, if you have come this far, you just need to give the following two commands for the microservices to be up and running.

$ cd microservices
$ docker-compose build
Building hello-world-service
Step 1/7 : FROM python:3.8-alpine
---> ff6233d0ceb9
Step 2/7 : MAINTAINER Srinaveen Desu
---> Using cache
---> 982b0811ac75
Step 3/7 : COPY ./app/requirements.txt /app/requirements.txt
---> b5632d4567d2
Step 4/7 : WORKDIR /app
---> Running in cca14ebf75cb
Removing intermediate container cca14ebf75cb
---> 350210281c21
Step 5/7 : RUN apk add --update && pip install --upgrade pip && pip install -r requirements.txt && rm -rf /var/cache/apk/*
---> Running in 63900e048747
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
OK: 11 MiB in 35 packages
Requirement already up-to-date: pip in /usr/local/lib/python3.8/site-packages (20.2.3)
Collecting flask
Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
Collecting requests
Downloading requests-2.24.0-py2.py3-none-any.whl (61 kB)
Collecting Werkzeug>=0.15
Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
Collecting click>=5.1
Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
Collecting Jinja2>=2.10.1
Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
Collecting itsdangerous>=0.24
Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1
Downloading urllib3-1.25.10-py2.py3-none-any.whl (127 kB)
Collecting certifi>=2017.4.17
Downloading certifi-2020.6.20-py2.py3-none-any.whl (156 kB)
Collecting chardet<4,>=3.0.2
Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB)
Collecting idna<3,>=2.5
Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
Collecting MarkupSafe>=0.23
Downloading MarkupSafe-1.1.1.tar.gz (19 kB)
Building wheels for collected packages: MarkupSafe
Building wheel for MarkupSafe (setup.py): started
Building wheel for MarkupSafe (setup.py): finished with status 'done'
Created wheel for MarkupSafe: filename=MarkupSafe-1.1.1-py3-none-any.whl size=12627 sha256=fa06269f4b2a36ebef705f4c75182dc4abfaea6c197a63f25bb380e43c64a849
Stored in directory: /root/.cache/pip/wheels/0c/61/d6/4db4f4c28254856e82305fdb1f752ed7f8482e54c384d8cb0e
Successfully built MarkupSafe
Installing collected packages: Werkzeug, click, MarkupSafe, Jinja2, itsdangerous, flask, urllib3, certifi, chardet, idna, requests
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1 Werkzeug-1.0.1 certifi-2020.6.20 chardet-3.0.4 click-7.1.2 flask-1.1.2 idna-2.10 itsdangerous-1.1.0 requests-2.24.0 urllib3-1.25.10
Removing intermediate container 63900e048747
---> 799f84c7e58f
Step 6/7 : COPY ./app /app
---> 18c02158d2a2
Step 7/7 : CMD python app.py
---> Running in 8485b9c8152c
Removing intermediate container 8485b9c8152c
---> 12e8721d22d3Successfully built 12e8721d22d3
Successfully tagged microservices_hello-world-service:latest
Building welcome-service
Step 1/7 : FROM python:3.8-alpine
---> ff6233d0ceb9
Step 2/7 : MAINTAINER Srinaveen Desu
---> Using cache
---> 982b0811ac75
Step 3/7 : COPY ./app/requirements.txt /app/requirements.txt
---> Using cache
---> ae1e7a4b6a0a
Step 4/7 : WORKDIR /app
---> Using cache
---> 736d1540622c
Step 5/7 : RUN apk add --update && pip install --upgrade pip && pip install -r requirements.txt && rm -rf /var/cache/apk/*
---> Using cache
---> 212b805372e7
Step 6/7 : COPY ./app /app
---> Using cache
---> 76e3df9f1239
Step 7/7 : CMD python app.py
---> Using cache
---> 87365b00480bSuccessfully built 87365b00480b
Successfully tagged microservices_welcome-service:latest

The command docker-compose build builds the images of both the containers.

For bringing the services up we have to give the following command:

$ docker-compose up
Creating network "microservices_sample" with the default driver
Creating microservices_hello-world-service_1 ... done Creating microservices_welcome-service_1 ... done Attaching to microservices_hello-world-service_1, microservices_welcome-service_1
hello-world-service_1 | * Serving Flask app "app" (lazy loading)
hello-world-service_1 | * Environment: production
hello-world-service_1 | WARNING: This is a development server. Do not use it in a production deployment.
hello-world-service_1 | Use a production WSGI server instead.
hello-world-service_1 | * Debug mode: on
hello-world-service_1 | * Running on http://0.0.0.0:5050/ (Press CTRL+C to quit)
hello-world-service_1 | * Restarting with stat
hello-world-service_1 | * Debugger is active!
hello-world-service_1 | * Debugger PIN: 112-150-599
welcome-service_1 | * Serving Flask app "app" (lazy loading)
welcome-service_1 | * Environment: production
welcome-service_1 | WARNING: This is a development server. Do not use it in a production deployment.
welcome-service_1 | Use a production WSGI server instead.
welcome-service_1 | * Debug mode: on
welcome-service_1 | * Running on http://0.0.0.0:5051/ (Press CTRL+C to quit)
welcome-service_1 | * Restarting with stat
welcome-service_1 | * Debugger is active!
welcome-service_1 | * Debugger PIN: 278-737-742

The above command brings all the services into a running state and voilà, it’s time to play with your microservices.

In case you want to run these services in the background attach the -d flag in the docker command as follows:

docker-compose up -d

When you start containers using -d (detached mode), in order to stop the services we have to give the following command:

docker-compose stop

On the index page, when I click the Test Connection button, it navigates to /test page if the ping to the second service is working. On the second page, when I click the Get message button I see the following message:

We got a greeting message from the second service
We got a greeting message from the second service

reset button takes us back to the homepage.

That’s it for this microservice demo. We saw the two services running and talking to each other. There is a lot more that we can do here but for starters, I believe this could be a good place to begin with. What more can be done?

A few examples could be:

1) While the core main service is running in one container we could have the database running in another container;

2) One service handling the business logic, another service handling the logging of events in the application, another service responsible for queuing of messages between these services, while another one for the database. The use-cases are innumerable.

That was all for this article. Hope you had some fun learning cool stuff :)

Note: The code is for learning purpose only. Please don’t use the same in Production.

Upvote


user
Created by

Srinaveen Desu

A pythonista by nature. I like learning new technologies and building innovative solutions out of it.


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles