Build Microservices With Python. This Article Aims To Outline The Basics - by Ashish MJ - Dev Genius
Build Microservices With Python. This Article Aims To Outline The Basics - by Ashish MJ - Dev Genius
Become a member
1.3K 8
Microservices VS Monolithic
Getting Started
We will create a simple project by building an application(DealBazaar) that
allows users to place an order and view an order. So we will have 4 services
namely —
Portal Service — A service that just renders the html content and routes
the api to other services (this service doesn’t connect to any DB)
Product Management
Order Management
Order Management API’s and Model
Notification Gateway
Notification Gateway API and Models
Portal Service
GET / home and GET /viewOrder API’s just render the html page without the
calls to other services.
GET /order API
Login / Signup on Mailjet website and generate the API key and API
secret . Same will be used to send the mail post order submit . Replace
the values in the .env file of notification gateway service
2. Code
cb.py- script for all db operations and common for all the service
cb.py
1 import os
2 import uuid
3 from datetime import datetime
4 from dotenv import load_dotenv,find_dotenv
5 from couchbase.exceptions import (
6 CouchbaseException,
7 DocumentExistsException,
8 DocumentNotFoundException,
9 )
10 from src.cb import CouchbaseClient
11 from flask import Flask, request, jsonify
12 from flask_restx import Api, Resource, fields
13
14 app = Flask(__name__)
15
16 env_path = "./src/.env"
17 load_dotenv(env_path)
18
19 api = Api(app)
20 nsProduct = api.namespace("api/v1/products", "CRUD operations for Product")
21
22 productInsert = api.model(
23 "ProductInsert",
24 {
25 "productName": fields.String(required=True, description="Product Name"),
26 "productId": fields.String(required=True, description="Product's Unique ID"),
27 "price": fields.Float(required=True, description="Product Price"),
28 "tax": fields.Float(required=True, description="Product tax percentage"),
29 "description": fields.String(required=False, description="Description of product"),
30 "status": fields.String(required=True, description="Product Status"),
31 "url" : fields.String(required=True, description="Image Url of the Product")
32 },
33 )
34
35 product = api.model(
36 "Product",
37 {
38 "id": fields.String(required=True, description="Product's system generated Id"),
39 "productName": fields.String(required=True, description="Product Name"),
40 "productId": fields.String(required=True, description="Product's Unique ID"),
41 "price": fields.Float(required=True, description="Product Price"),
42 "tax": fields.Float(required=True, description="Product tax percentage"),
43 "description": fields.String(required=False, description="Description of product"),
44 "status": fields.String(required=True, description="Product Status"),
45 "url" : fields.String(required=True, description="Image Url of the Product"),
46 "createdAt" : fields.String(required=True, description="Time product is created")
47 },
47 },
48 )
49
50 @nsProduct.route("")
51 class Products(Resource):
52 # tag::post[]
53 @nsProduct.doc(
54 "Create Product",
55 reponses={201: "Created", 409: "Key alreay exists", 500: "Unexpected Error"},
56 )
57 @nsProduct.expect(productInsert, validate=True)
58 @nsProduct.marshal_with(product)
59 def post(self):
60 try:
61 data = request.json
62 id = uuid.uuid4().__str__()
63 data["id"] = id
64 data["createdAt"] = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
65 cb.insert(id, data)
66 return data, 201
67 except DocumentExistsException:
68 return "Key already exists", 409
69 except CouchbaseException as e:
70 return f"Unexpected error: {e}", 500
71 except Exception as e:
72 return f"Unexpected error: {e}", 500
73
74 @nsProduct.doc(
75 "Find Products",
76 reponses={200: "found", 500: "Unexpected Error"},
77 params={
78 "status": "Product is ACTIVE/INACTIVE"
79 },
80 )
81 def get(self):
82 try:
83 status = request.args.get("status","ACTIVE")
84 query = f"SELECT p.* FROM {db_info['bucket']}.{db_info['scope']}.{db_info['collection']} p WHERE p
85 result = cb.query(query, status=status)
86 products = [x for x in result]
87 return products, 200
88 except Exception as e:
89 return f"Unexpected error: {e}", 500
90
91 @nsProduct.route("/<productId>")
92 class ProductId(Resource):
93 @nsProduct.doc(
94 "Get Profile",
95 reponses={200: "Document Found", 404: "Document Not Found", 500: "Unexpected Error"},
96 )
97 def get(self, productId):
98 try:
99 query = f"SELECT p.* FROM {db_info['bucket']}.{db_info['scope']}.{db_info['collection']} p WHERE p
100 result = cb.query(query,productId=productId)
101 return list(result)[0] ,200
102 except DocumentNotFoundException:
103 return "Key not found", 404
104 except CouchbaseException as e:
105 return f"Unexpected error: {e}", 500
106
107 db_info = {
108 "host": os.getenv("DB_HOST"),
109 "bucket": os.getenv("BUCKET"),
110 "scope": os.getenv("SCOPE"),
111 "collection": os.getenv("COLLECTION"),
112 "username": os.getenv("USERNAME"),
112 "username": os.getenv("USERNAME"),
113 "password": os.getenv("PASSWORD"),
114 }
115
116 cb = CouchbaseClient(*db_info.values())
117 cb.connect()
118
119 if __name__ == "__main__":
120 app.run(debug=True,port=5002)
1 import os
2 import uuid
3 from datetime import datetime
4 from dotenv import load_dotenv
5 from couchbase.exceptions import DocumentExistsException
6 from src.cb import CouchbaseClient
7 from flask import Flask, request
8 from flask_restx import Api, Resource, fields
9
10 from mailjet_rest import Client
11
12 app = Flask(__name__)
13
14 env_path = "./src/.env"
15 load_dotenv(env_path)
16
17 senderMail = os.getenv("SENDER_MAIL")
18 mailjet = Client(auth=(os.getenv("API_KEY"), os.getenv("API_SECRET")), version='v3.1')
19 api = Api(app)
20 nsProduct = api.namespace("api/v1/email", "Send Email")
21
22 emailInsert = api.model(
23 "emailInsert",
24 {
25 "orderId": fields.String(required=True, description="Order ID"),
26 "name": fields.String(required=True, description="Name of Customer"),
27 "mailId": fields.String(required=True, description="Mail Id of customer"),
28 "totalCost" : fields.Float(required=True, description="Total cost of order"),
29 },
30 )
31
32 emailResponse = api.model(
33 "emailResponse",
34 {
35 "id": fields.String(required=True, description="Audit for email sent"),
36 "orderId": fields.String(required=True, description="Order ID"),
37 "mailId": fields.String(required=True, description="Mail Id of customer"),
38 "status" : fields.String(required=True, description="Email Status"),
39 "deliveredAt" : fields.String(required=True, description="Time email is delivered"),
40 "statusCode" : fields.Integer(required=True, description="Status code of SMTP")
41 },
42 )
43
44
45 @nsProduct.route("/sendMail")
46 class Email(Resource):
47 # tag::post[]
48 @nsProduct.doc(
49 "Send Email",
50 reponses={200: "Success", 500: "Unexpected Error"},
51 )
52 @nsProduct.expect(emailInsert, validate=True)
53 @nsProduct.marshal_with(emailResponse)
54 def post(self):
55 reqData = request.json
56 print(reqData)
57 data = {
58 'Messages': [
59 {
60 "From": {
61 "Email": senderMail,
62 "Name": senderMail
63 },
64 "To": [
65 {
66 "Email": reqData["mailId"],
67 "Name": reqData["name"]
68 }
69 ],
70 "Subject": "Greetings from DealBazaar",
71 "TextPart": f"Your Order {reqData['orderId']} successfully placed !",
72 "HTMLPart": f"<h3>Dear {reqData['name']} , Your order {reqData['orderId']} is successful
73 }
74 ]
75 }
76 response = {}
77 rep = mailjet.send.create(data=data)
78 if rep.status_code==200:
79 response["status"] = "SUCCESS"
80 response["deliveredAt"] = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
81 else:
82 response["status"] = "FAILURE"
83 response["deliveredAt"] = None
84 id = uuid.uuid4().__str__()
85 response["id"] = id
86 response["mailId"] = reqData["mailId"]
87 response["orderId"] = reqData["orderId"]
88 response["statusCode"] = rep.status_code
89 try :
90 cb.insert(id, response)
91 return response, 202
92 except DocumentExistsException:
93 return "Key already exists", 409
94 except Exception as e:
95 return f"Unexpected error: {e}", 500
96
97 db_info = {
98 "host": os.getenv("DB_HOST"),
99 "bucket": os.getenv("BUCKET"),
100 "scope": os.getenv("SCOPE"),
101 "collection": os.getenv("COLLECTION"),
102 "username": os.getenv("USERNAME"),
103 "password": os.getenv("PASSWORD")
104 }
105
106 cb = CouchbaseClient(*db_info.values())
107 cb.connect()
108
109 if __name__ == "__main__":
110 app.run(debug=True,port=5004)
1 import os
2 import uuid
3 import requests
4 from datetime import datetime
5 from dotenv import load_dotenv
6 from couchbase.exceptions import (
7 CouchbaseException,
8 DocumentExistsException,
9 DocumentNotFoundException,
10 )
11 from src.cb import CouchbaseClient
12 from flask import Flask, request
13 from flask_restx import Api, Resource, fields
14
15 app = Flask(__name__)
16
17 env_path = "./src/.env"
18 load_dotenv(env_path)
19
20 api = Api(app)
21 nsOrder = api.namespace("api/v1/orders", "CRUD operations for Orders")
22
23 contact = api.model(
24 "Contact",
25 {
26 "name": fields.String(required=True, description="Customer Name"),
27 "emailId": fields.String(required=True, description="emailId of customer"),
28 "phone": fields.String(required=True, description="Phone number of customer"),
29 "address": fields.String(required=True, description="Address of customer")
30 },
31 )
32
33 product = api.model(
34 "Product",
35 {
36 "productName": fields.String(required=True, description="Product Name"),
37 "productId": fields.String(required=True, description="Product's Unique ID"),
38 "price": fields.Float(required=True, description="Product Price"),
39 "tax": fields.Float(required=True, description="Product tax percentage"),
40 "quantity" : fields.Integer(required=True,description="Item count")
41 },
42 )
43
44 orderInsert = api.model(
45 "orderInsert",
46 {
47 "orderItems": fields.List(fields.Nested(product)),
48 "contact": fields.Nested(contact)
49 },
50 )
51
52 order = api.model(
53 "Order",
54 {
55 "id": fields.String(required=True, description="Product's system generated Id"),
56 "orderId": fields.String(required=True, description="Order Id"),
57 "orderItems": fields.List(fields.Nested(product)),
58 "contact": fields.Nested(contact),
59 "totalCost": fields.Float(required=True, description="Total cost of order"),
59 "totalCost": fields.Float(required=True, description="Total cost of order"),
60 "submittedAt" : fields.String(required=True, description="Time order is submitted"),
61 },
62 )
63
64 @nsOrder.route("/submitOrder")
65 class Orders(Resource):
66
67 @nsOrder.doc(
68 "Create Product",
69 reponses={200: "Success", 409: "Key alreay exists", 500: "Unexpected Error"},
70 )
71 @nsOrder.expect(orderInsert, validate=True)
72 @nsOrder.marshal_with(order)
73 def post(self):
74 try:
75 data = request.json
76 print(data)
77 data["id"] = uuid.uuid4().__str__()
78 data["submittedAt"] = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
79 orderId = cb.get(os.getenv("ORDERID_KEY"))
80 cb.upsert( os.getenv("ORDERID_KEY"), orderId.value+1)
81 orderId = "ORD-"+str(orderId.value)
82 data["orderId"] = orderId
83 totalCost=0
84 for cost in data["orderItems"]:
85 totalCost += (cost["price"] * cost["quantity"]) + (cost["price"] * (cost["tax"])/100) * cost["qu
86 data["totalCost"] = totalCost
87 cb.insert(orderId, data)
88 requestData={}
89 requestData["orderId"] = data["orderId"]
90 requestData["name"] = data["contact"]["name"]
91 requestData["mailId"] = data["contact"]["emailId"]
92 requestData["totalCost"] = data["totalCost"]
93 url = "http://"+os.getenv("NG_BASEURL")+":"+os.getenv("NG_PORT")+os.getenv("NG_URL")
94 response = requests.post(url,json=requestData)
95 return data, 200
96 except DocumentExistsException:
97 return "Key already exists", 409
98 except CouchbaseException as e:
99 return f"Unexpected error: {e}", 500
100 except Exception as e:
101 return f"Unexpected error: {e}", 500
102
103 @nsOrder.route("/<orderId>")
104 class ProductId(Resource):
105 @nsOrder.doc(
106 "Get Profile",
107 reponses={200: "Document Found", 404: "Document Not Found", 500: "Unexpected Error"},
108 )
109 def get(self, orderId):
110 try:
111 result = cb.get(orderId)
112 return result.value ,200
113 except DocumentNotFoundException:
114 return "Key not found", 404
115 except CouchbaseException as e:
116 return f"Unexpected error: {e}", 500
117
118 db_info = {
119 "host": os.getenv("DB_HOST"),
120 "bucket": os.getenv("BUCKET"),
121 "scope": os.getenv("SCOPE"),
Search Write
122 "collection": os.getenv("COLLECTION"),
123 "username": os.getenv("USERNAME"),
124 "password": os.getenv("PASSWORD"),
124 "password": os.getenv("PASSWORD"),
125 }
126
127 cb = CouchbaseClient(*db_info.values())
128 cb.connect()
129 try:
130 cb.get(os.getenv("ORDERID_KEY"))
131 except DocumentNotFoundException:
132 cb.insert(os.getenv("ORDERID_KEY"),int(os.getenv("ORDERID_BASE")))
133
134 if __name__ == "__main__":
135 app.run(debug=True,port=5003)
1 import os
2 from dotenv import load_dotenv
3 import requests
4 from flask import Flask, request, jsonify,render_template
5
6 app = Flask(__name__)
7
8 env_path = "./src/.env"
9 load_dotenv(env_path)
10
11
12 @app.route('/')
13 @app.route('/home')
14 def home():
15 return render_template('home.html')
16
17
18 @app.route('/order',methods=['GET','POST'])
19 def order():
20 url = "http://" + os.getenv("PM_BASEURL") + ":" + os.getenv("PM_PORT") + os.getenv("PM_GETPRODUCT_URL")
21 response = requests.get(url).json()
22 if request.method == 'POST':
23 data={}
24 orderItems = []
25 contact = {}
26 for item in response:
27 orderItem ={}
28 if int(request.form[item['productId']]):
29 orderItem["quantity"] = int(request.form[item['productId']])
30 orderItem["productName"] = item["productName"]
31 orderItem["price"] = item["price"]
32 orderItem["productId"] = item["productId"]
33 orderItem["tax"] = item["tax"]
34 orderItems.append(orderItem)
35
36 contact["name"] = request.form["name"]
37 contact["address"] = request.form["address"]
38 contact["emailId"] = request.form["email"]
39 contact["phone"] = request.form["mobile"]
40 data["orderItems"] = orderItems
41 data["contact"] = contact
42
43 url = "http://" + os.getenv("OM_BASEURL") + ":" + os.getenv("OM_PORT") + os.getenv("OM_SUBMITORDER_URL")
44 response = requests.post(url, json=data)
45 return render_template('orderView.html',data=response.json())
46 return render_template('order.html',data=response)
47
48 @app.route('/viewOrder',methods=['GET','POST'])
49 def viewOrder():
50 if request.method == 'POST':
51 orderId = request.form['orderId']
52 url = "http://" + os.getenv("OM_BASEURL") + ":" + os.getenv("OM_PORT") + os.getenv("OM_GETORDER_URL") + o
53 response = requests.get(url).json()
54 return render_template('orderView.html',data=response)
55 return render_template('view.html')
56
57 if __name__ == "__main__":
58 app.run(debug=True,port=5001)
3. Run
Make sure you are running each service on a different port . In my case —
python app.py
Open a terminal and lets create few products by executing the below curl
1342734683/photo/an-accessory-for-men.jpg?s=612x612&w=0&k=20&c=KRrVxbNC34_dGQZmB7GmihvTB30S]z2oKNs5RLrlsBQ=" •"status":"ACTIVE"}»
{
ohtt:/cathost:502/ap/u/produet://media.stockphoto.com/d/
IN-C02FD6T3ML7H:~ashish.mj$curl-H'Content-Type:application/son'-d'{"productName":"NeutronGenTrack","productId":"DV0001", "price":15000
"id":"9147ea02-2e81-4bb2-8c2d-69961430b2f3",
"productName":"NeutronGenTrack",
"productId":"DV0001",
"price":15000.0,
"tax":18.9,
"description":null,
//d.istockphoto.com/10/1342734683/photo/an-acessory-for-men.g
"createdAt":"27/11/202300:31:56"
6122612020
016 512651500
IN-C02D6T3ML7H:~ashish.mj$curl-H'Content-Type:application/json'-d'{"productName":"ProtonGenTrack","productId":"DV0002",
"id":"467400a0-8095-40c-9ca4-117276ab2c7b*,
"productName":"ProtonGenTrack",
"productid":"DV0002",
"price":10000.0,
"tax":10.0,
"description":nu11,
"status":"ACTIVE",
"url":"https://cdn1.ethoswatches.com/media/catalog/product/cache/5b6ffe97254a86fab5749cb594365e70/h/u/hublot-shaped-821-ox-0180-rx.jpg",
"createdAt":"27/11/202300:33:08"
3. Sanity Test
Submit an order- Click on Order Now button in the home page and select
the products , quantity , fill in the details (name , address , email phone )
and click on submit button
Submitting an order
Results of Order submission
View an order- Click on View Order button in the home page and enter
the orderId and click on search button
View an order
Conclusion
In this story, we have seen the basics of microservices architecture and
learnt how to build microservices using python in simple steps. Hope
everything is clear !
Additional Resource
Flask
Rest API’s
A Software Engineer with a demonstrated history of working in the IT .A keen learner, who
always strives to feed curiosity and learn about new technologies.
194 904 11
381 4 574 3
34 min read · Jan 29, 2024 106 min read · Apr 12, 2024
4.5K 39 1.1K 7
Lists
You’re Decent At Python If You Can Python One Billion Row Challenge
Answer These 7 Questions —From 10 Minutes to 4 Seconds
Correctly
# No cheating pls!! The one billion row challenge is exploding in
popularity. How well does Python stack up?
3.8K 20 1.99K 31
1.2K 15 1.1K 6
Help Status About Careers Blog Privacy Terms Text to speech Teams