Skip to content

Commit

Permalink
Merge pull request confluentinc#245 from confluentinc/pytexas
Browse files Browse the repository at this point in the history
Add Python Microservices project
  • Loading branch information
daveklein authored Mar 26, 2022
2 parents 99f88d8 + 2493aa2 commit 0ad08ab
Show file tree
Hide file tree
Showing 24 changed files with 897 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ You may well need to allocate Docker 8GB when running these. Avoid allocating al
- [Micronaut & AWS Lambda on Confluent Cloud](micronaut-lambda)
- [Bridge to Cloud (and back!) with Confluent and MongoDB Atlas](mongodb-demo)
- [Random Pizza Generator with Micronaut on Confluent Cloud](micronaut-pizza-gen)
- [Random Pizza Generator with Flask and Python on Confluent Cloud](python-microservices)

### Confluent Platform

Expand Down
48 changes: 48 additions & 0 deletions python-microservices/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Random Pizza Generator
an event-driven microservices demo with Python and Kafka

## Description

This demo consists of the following projects:

- *pizza-service* a Flask app with Kafka support.
- *sauce-service* a basic Python app with Kafka.
- *cheese-service* a basic Python app with Kafka.
- *meat-service* a basic Python app with Kafka.
- *veggie-service* a basic Python app with Kafka.


The demo was built using Confluent Cloud, the Cloud-native service for Apache Kafka. If you don't have an account on Confluent Cloud, you can set that up [here](https://www.confluent.io/confluent-cloud).

## Running the demo

Before running this demo, you will need to update the `config.properties` file in each project. Replace the fields marked with `< >` using values from your Confluent Cloud cluster. Also, you will need to create 5 topics in Confluent Cloud. They can each have 1 partion (or more if you so desire): `pizza`, `pizza-with-sauce`, `pizza-with-cheese`, `pizza-with-meat`, and `pizza-with-veggies`.

Once all five services are up and running, you can issue the following `curl` command to send an order for 3 random pizzas.
`curl -X POST -d http://localhost:5000/order/5`

This will trigger a series of events which will result in a completed pizza order with the five pizzas, and it will return a UUID of that pizza order. Of course you can alter that last value in the URL to get a different number of pizzas. (I was a bit hungry when I wrote this.)

To see your pizzas in all their hot, delicious glory, run the following `curl` command using the UUID returned from the first call.

curl http://localhost:5000/order/{{pizza-order-UUID}} | jq

Note: `jq` is optional, but very helpful. More info at https://stedolan.github.io/jq/


*When you are done working with this demo project, you can delete these topics to avoid additional charges.*


## Related Documentation that might be helpful

### Confluent Cloud

[Quik-start Guide](https://docs.confluent.io/cloud/current/get-started/index.html)

### Python and Kafka

- [Quick-start Guide](https://developer.confluent.io/get-started/python)
- [Python Kafka Client](https://docs.confluent.io/clients-confluent-kafka-python/current/overview.html)
- [Python Kafka Articles](https://www.confluent.io/blog/tag/python/)

---
9 changes: 9 additions & 0 deletions python-microservices/cheese-service/config.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[kafka_client]
bootstrap.servers=<Kafka server or Confluent Cloud endpoint>
security.protocol=SASL_SSL
sasl.mechanisms=PLAIN
sasl.username=<Confluent Cloud API Key>
sasl.password=<Confluent Cloud API Secret>
auto.offset.reset=earliest
group.id=cheeses

41 changes: 41 additions & 0 deletions python-microservices/cheese-service/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from configparser import ConfigParser
from confluent_kafka import Producer, Consumer
import json
import random

config_parser = ConfigParser(interpolation=None)
config_file = open('config.properties', 'r')
config_parser.read_file(config_file)
client_config = dict(config_parser['kafka_client'])

cheese_producer = Producer(client_config)

sauce_consumer = Consumer(client_config)
sauce_consumer.subscribe(['pizza-with-sauce'])


def start_service():
while True:
msg = sauce_consumer.poll(0.1)
if msg is None:
pass
elif msg.error():
pass
else:
pizza = json.loads(msg.value())
add_cheese(msg.key(), pizza)


def add_cheese(order_id, pizza):
pizza['cheese'] = calc_cheese()
cheese_producer.produce('pizza-with-cheese', key=order_id, value=json.dumps(pizza))


def calc_cheese():
i = random.randint(0, 6)
cheeses = ['extra', 'none', 'three cheese', 'goat cheese', 'extra', 'three cheese', 'goat cheese']
return cheeses[i]


if __name__ == '__main__':
start_service()
57 changes: 57 additions & 0 deletions python-microservices/cheese-service/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions python-microservices/cheese-service/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[tool.poetry]
name = "cheese-service"
version = "0.1.0"
description = ""
authors = ["Dave Klein <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.9"
confluent-kafka = "^1.8.2"
configparser = "^5.2.0"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
9 changes: 9 additions & 0 deletions python-microservices/meat-service/config.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[kafka_client]
bootstrap.servers=<Kafka server or Confluent Cloud endpoint>
security.protocol=SASL_SSL
sasl.mechanisms=PLAIN
sasl.username=<Confluent Cloud API Key>
sasl.password=<Confluent Cloud API Secret>
auto.offset.reset=earliest
group.id=meats

47 changes: 47 additions & 0 deletions python-microservices/meat-service/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from configparser import ConfigParser
from confluent_kafka import Producer, Consumer
import json
import random

config_parser = ConfigParser(interpolation=None)
config_file = open('config.properties', 'r')
config_parser.read_file(config_file)
client_config = dict(config_parser['kafka_client'])

meats_producer = Producer(client_config)

cheese_consumer = Consumer(client_config)
cheese_consumer.subscribe(['pizza-with-cheese'])


def start_service():
while True:
msg = cheese_consumer.poll(0.1)
if msg is None:
pass
elif msg.error():
pass
else:
pizza = json.loads(msg.value())
add_meats(msg.key(), pizza)


def add_meats(order_id, pizza):
pizza['meats'] = calc_meats()
meats_producer.produce('pizza-with-meats', key=order_id, value=json.dumps(pizza))


def calc_meats():
i = random.randint(0, 4)
meats = ['pepperoni', 'sausage', 'ham', 'anchovies', 'salami', 'bacon', 'pepperoni', 'sausage', 'ham', 'anchovies', 'salami', 'bacon']
selection = []
if i == 0:
return 'none'
else:
for n in range(i):
selection.append(meats[random.randint(0, 11)])
return ' & '.join(set(selection))


if __name__ == '__main__':
start_service()
57 changes: 57 additions & 0 deletions python-microservices/meat-service/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions python-microservices/meat-service/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[tool.poetry]
name = "meat-service"
version = "0.1.0"
description = ""
authors = ["Dave Klein <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.9"
confluent-kafka = "^1.8.2"
configparser = "^5.2.0"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
19 changes: 19 additions & 0 deletions python-microservices/pizza-service/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask import Flask
import pizza_service

app = Flask(__name__)


@app.route('/order/<count>', methods=['POST'])
def order_pizzas(count):
order_num = pizza_service.order_pizzas(int(count))
return '{"order_id":"' + order_num + '"}'


@app.route('/order/<order_id>', methods=['GET'])
def get_order(order_id):
return pizza_service.get_order(order_id)


if __name__ == '__main__':
app.run()
13 changes: 13 additions & 0 deletions python-microservices/pizza-service/config.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[kafka_client]
bootstrap.servers=<Kafka server or Confluent Cloud endpoint>
security.protocol=SASL_SSL
sasl.mechanisms=PLAIN
sasl.username=<Confluent Cloud API Key>
sasl.password=<Confluent Cloud API Secret>

[consumer]
auto.offset.reset=earliest
group.id=pizza_shop
enable.auto.commit=true
max.poll.interval.ms=3000000

37 changes: 37 additions & 0 deletions python-microservices/pizza-service/pizza.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import json
import uuid

class Pizza:
def __init__(self):
self.order_id = ''
self.sauce = ''
self.cheese = ''
self.meats = ''
self.veggies = ''

def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=False, indent=4)
def __str__(self):
return json.dumps(self.__dict__)



class PizzaOrder:
def __init__(self, count):
self.id = str(uuid.uuid4().int)
self.count = count
self.pizzas = []

def add_pizza(self, pizza):
self.pizzas.append(pizza)

def get_pizzas(self):
return self.pizzas

def __str__(self):
return json.dumps(self.__dict__)

def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=False, indent=4)
Loading

0 comments on commit 0ad08ab

Please sign in to comment.