Interested in microservices, and how they can be used for increased agility and scalability?
Microservices is an architectural style and pattern that structures an application as a collection of coherent services. Each service is highly maintainable, testable, loosely coupled, independently deployable, and precisely focused.
This course takes a hands-on look at microservices using Python, Flask, and Docker. You'll learn how Flask can be used to quickly prototype and build microservices, as well as how to use Docker to host and deploy them.
We start by looking at various problems associated with monolithic architectures and how microservices address them. We then move on to designing and building a basic shopping cart system, focusing on each of the microservices that make up the overall system.
If you have any feedback relating to this course, feel free to get in touch with us at support@cloudacademy.com.
Learning Objectives
- Obtain a solid understanding of microservices: the benefits, the challenges, and how using microservices differs from a monolithic approach
- Learn how to design and build microservices using Python and Flask
- Learn how to deploy microservices using Docker
Intended Audience
This course is intended for anyone who wants to build and deploy microservices using Python, Flask, and Docker.
Prerequisites
Python 3.x programming experience is required to get the most out of this course. If you want to follow along with this course, you'll need Python 3.7, an IDE (PyCharm Community Edition - free), and Docker (Docker Desktop).
Resources
The complete source code for the project demonstrated within the course is located here:
The repository contains the following 4 projects:
- user-service
- product-service
- order-service
- frontend
Open the ‘frontend’ project and configure the virtual environment and open ‘config.py’. Import os from dotenv import load_dotenv. Make sure the ‘.env’ file exists in project root and load that file. Create ‘Config’ class.
Set ‘SECRET_KEY’ value by generating a random token from the secrets package. Also set the random value for ‘WTF_CSRF_SECRET_KEY’ by the same process. Create the ‘DevelopmentConfig’ class. Within that class set ENV to development and DEBUG to True.
Create ‘ProductionConfig’ and write down the pass keyword. Open .env file and set the required configuration. Let’s edit __init__.py. Import config, import os, import Flask-Login, import bootstrap, import LoginManager.
Create a global object of LoginManager and Bootstrap. Optionally define UPLOAD_FOLDER to where applications use to store the images. It can be used to upload new product images. Start create_app() to initialize the core application.
Within create_app create the flask app object. Set the flask app config UPLOAD_FOLDER attribute. Load the environment specific settings from ‘.env’. Initialize the login_manager and set the login_message and login_view. Initialize the bootstrap plugin. Start the app context. Here we define the blueprint. Finally return the app.
Open run.py. from application import create_app(). Configure run.py as the entry point of the application. Declare FLASK_APP variable in ‘.flaskenv’. Within application/frontend folder, open up the __init__.py. From flask import Blueprint. Create a blueprint object. Import views.
Within the app context, import the blueprint and register it. Open forms.py to create forms required for websites. From flask_wtf, import FlaskForm. From wtforms, import StringField, PasswordField, SubmitField, HiddenField, and IntegerField. From wtforms.validators, import DataRequired and Email.
Declare a class name LoginForm inherited from FlaskForm. Create a StringField object named ‘username’. Also use the DataRequired validator. Create a PasswordField object named ‘password’ with the validator of ‘DataRequired’. Create a SubmitField object named submit. Write down the RegistrationForm.
Create StringField objects of username, first_name, last_name and email. Email object has an additional validator named Email. Create a PasswordField object with the DataRequired validator.
Lastly, create a SubmitField object. Write a class named OrderItemForm. Create a HiddenField object named product_id with DataRequired validator. Create an IntegerField object named quantity with the validator DataRequired. Create a HiddenField object named order_id. Lastly, create a SubmitField object.
Declare a class named ItemForm. Create a HiddenField object with the DataRequired validator named product_id to track products in order. Create another HiddenField object named quantity to track item quantity. Import requests. Start a class UserClient. Within that class write down a static method post_login with the form as argument.
Declare a variable api_key and set its value to False. Now declare a payload dictionary where first key value pair is username and form.username.data, and the second key value pair is password and form.password.data. Save the api url into a variable.
Create the POST request with the url and payload. If there is a response, then convert it to json format and save in a variable d. If there is an api_key value available in the dictionary then save that value in a variable api_key and return it.
For testing we need to hard-code some values until we create some logic in ‘views.py’. So remove the ‘form’ argument from post_login function and replace hardcoded username and password values with form.username.data and form.password.data.
Open ‘flask shell’. Import UserClient and run the function UserClient.post_login(), it will return the api_key in response. Create a get_user() method to return the logged-in user information. Define the headers dictionary with the key ‘Authorization’ and the value of api_key saved from session.
Write down the url in a variable and pass a ‘GET’ request to url with headers defined above. Save the response and convert it to json and return it. Before testing replace session[‘user_api_key’] with the hardcoded value of api_key variable, later we use sessions in HTML templates.
Test the api as follows. Import application.frontend.api.UserClient Call UserClient.get_user() to get the user information. After testing rollback, the change and replace api_key in headers dictionary with session[‘user_api_key’]. You can also remove the api_key variable above headers. post_user_create() take form as argument.
Create a payload and get the values for email, password, first_name, last_name and username. Save the required URL in the variable. In the end, send POST request to the URL including the payload. Upon successful user creation, convert the response to JSON format.
To test the api just remove form argument from post_user_create method and pass payload dictionary variable with hardcoded values. Assignment: Run the flask shell and create the user using UserClient.post_user_create(). Rollback the changes after testing.
Create a does_exist with username as argument to check if username already exists or not. We will use this method at the time of registration of the user. Save the url in a variable and pass it to requests.request with ‘GET’ method.
Return the response if the status code is 200, denotes the existence of username otherwise username is available for registration which we see when working with views. To test, open ‘flask shell’ and import UserClient and run the UserClient.does_exist() with username to check within parentheses as string.
Import requests. Declare a class ProductClient. Within class, write down a static method named get_products(). Use requests to get the product list from an API endpoint. Convert response to JSON and return. To test the program, open the embedded python console and start ‘flask shell’.
Import the product client and call the get_products() function. The next method in ProductClient is get_product(slug) which fetches a single product based upon its slug. Access the endpoint to get a single product based upon its slug. Convert the response to JSON and return.
From flask, import session, import requests. Start a new class OrderClient. Within that class add a static method named get_order. Make a header having basic authorization with api_key. Create a request for API endpoint ‘/api/order’ and pass the headers. Convert the response to json and return the order.
Now it is time to test OrderClient.get_order(). Replace user_api_key session value with a hard-coded api_key value for testing purpose only. We will use this api_key during testing all methods of OrderClient. Import OrderClient and run get_order().
If the user has an open order then you will get that information, otherwise, you will get a ‘No order found’ response. Write another static method post_add_to_cart(product, qty) where users add a product to cart. The arguments required in the post_add_to_cart method is product_id and qty which is set to 1 by default.
So, make a payload dictionary with the product_id and qty. Save the URL in a variable. Create header to be passed to URL. In the end, pass the request to URL with payload and headers information. Convert the response to JSON format and return it.
To test the method, import the class and run the post_add_to_cart with product information, make sure to use a test api_key value from the database unless we use sessions in the ‘frontend’. post_checkout() method checks out the order.
Configure the URL with header information and send a post request. Save the response in a variable, convert the response in JSON format, and return it. In the end, reset the order in session. Finally, we come to a point where we write down views as blueprints and direct the output to HTML templates to see the frontend website in action.
Open views.py. Here we define what is going to be displayed on the home page. First, check if a user is authenticated, display the order of that user, from the session. Now display the products using ProductClient.get_products(). To avoid any sort of error to be displayed on the homepage, we use try except block and handle any exception in case we are not able to connect to the product API.
In the end, render the HTML template ‘home/index.html’ page and pass the products to the HTML template. Open index.html in templates/home folder. First, check if there is any product available then loop through the products passed from view. Otherwise display no products available message on the frontend home page.
Assignment: Run the project and verify if products have been displayed on the home page. First, we write down the logic for the registration page. Add ‘/register’ blueprint with ‘GET’ and ‘POST’ methods. Here we use the GET method to display the registration form and POST method to submit the form data.
Define a register method. Create form object from ‘RegistrationForm’. If the request is POST then perform the ‘validate_on_submit’ that collects and validates form data fields before submit. In the next step, save username from form.username.data.
Now check the existence of username using UserClient. If the username already exists then display an error flash message to input another username and return the registration page, otherwise, send the form to User Service via UserClient.post_user_create(form). If user created successfully and redirect to the login page. Otherwise if ‘form.validate_on_submit’ returns false then return an ‘Errors found’ flash message.
In the end, use the render_template function to render HTML template and pass form data. In register.html add a form with Post method. In jinja templates, double curly } braces allow us to evaluate an expression. The first flask tag would be form.hidden_tag(), this template argument generates a hidden field that includes a token that is used to protect the form against CSRF attacks.
Next, use a macro render_field to render a form field over jinja templates. The first field is username. Next is first_name. Then last_name. The final fields are email and password. In the end, use submit to submit the form, with some bootstrap classes.
For login add a route ‘/login’ with ‘GET’ and ‘POST’ methods. Start login method. Check if the user is already authenticated, then redirect the user to the home page, as there is no need to login. Create a form object from LoginForm. If the request method is POST and form.validate_on_submit() return True.
Pass the form object to UserClient.post_login as argument and save the response in api_key. Next if api_key is available then store it in session. Get the user in JSON format and save it in session also. If there is any Order placed by the logged-in user, fetch the Order in JSON format and save it in session.
Afterwards, return a success message to the user and redirect to the home page. If there is some error then return a message ‘Cannot login’. With all the logic defined above render the HTML template ‘login/index.html’ with the form object.
Open ‘login/index.html’ and verify if the form is secured with CSRF. Render the form field of username and password and submit the form. Logout routes will clear the session and return the user to the home page. Run the application and login with the username password previously created. After successful login, log out of it.
In this route, we define what is going to be displayed on the product detail view, based on the selected product. We select the product based upon its slug. First, get the product using the Product API client. And save the response.
Prepare a form object with retrieved product id from the last step. If the request is POST, perform the following tasks, If the user is not in session then show an error and redirect the user to the login page. Otherwise use OrderClient.post_add_to_cart() to add the product in Order.
Now save the Order in session and return the success flash message. At the end, render the HTML template and pass the product and form to it. In HTML template display product name with image and price. Over product price, add a form with POST method. Add form.hidden_tag().
Add a submit button with the label ‘Add to Cart’. ‘/checkout’ routes with GET query does the following tasks. It checks if the user is logged in to the website, otherwise redirect the user to the login page. If there is no order found in session then redirect the user to the home page with the flash message ‘No order found’.
Afterwards, get the order from OrderClient And display the number of items from it. If an order has zero items in it then redirect the user to the home page with ‘No order found’ message. Next check out the order from Order Service using OrderClient.post_checkout() and redirect the user to the thank you page.
The Thank You page first checks if the user is logged in and the order is available in gthe session. Release the order from the session using session.pop() and display the thankyou message. Run the application now.
From the product detail page, ‘Add to cart’, the product item is added to the Order with the message ‘Order has been updated’. For count logic go to _macro_basket.html in templates. Now click checkout to close the order. But before building the application for docker, modify the API urls of User, Product, and Order to user docker network instead of localhost for communication within User, Product, Order, and Frontend applications.
In the Dockerfile, Base image is python:3.7. Copy the requirements.txt. Change the working directory to /frontendapp. Run the command ‘pip install -r requirements.txt’. Copy all project files to docker. Specify ENTRYPOINT for the docker. Define CMD.
Run the docker build command to create the docker image. Use the docker run command to run the frontend in a docker container. You may use the ‘docker ps’ command to verify if the order-service container is running.
Saqib is a member of the content creation team at Cloud Academy. He has over 15 years of experience in IT as a Programmer, with experience in Linux and AWS admin. He's also had DevOps engineer experience in various startups and in the telecommunications sector. He loves developing Unity3D games and participating in Game Jams.