diff --git a/basharin_sevastyan_lab_3/README.md b/basharin_sevastyan_lab_3/README.md new file mode 100644 index 0000000..f8c71ba --- /dev/null +++ b/basharin_sevastyan_lab_3/README.md @@ -0,0 +1,167 @@ +# Лабораторная работа 3. Вариант 5. + +### Как запустить +В директории с файлом `docker-compose.yaml` выполнить команду: +``` +docker-compose up -d +``` +Это запустит `docker-compose`, который создаст контейнер и развернет в нем 3 контейнера. + +### Файловая иерархия +``` +basharin_sevastyan_lab_3/ +|-- user_service/ +| |-- Dockerfile +| |-- requirements.txt +| |-- user_service.py +|-- order_service/ +| |-- Dockerfile +| |-- requirements.txt +| |-- order_service.py +|-- nginx/ +| |-- Dockerfile +| |-- nginx.conf +|-- docker-compose.yml +``` + +### Описание работы +#### Описание предметной области +Объекты микросервисов буду связаны следующим образом: +``` +user (single) <- order (many) +``` +#### Синхронный обмен сообщениями +`order_service` будет отправлять http-запрос на `user_service` при определенных crud операциях. Например, при создании +экземпляра `order`, будет отправлен запрос на добавление `order` к определенному `user`: +```python +# CREATE +@app.route('/add_order/_', methods=['POST']) +def create_order(user_id, product): + order = {'id': len(orders) + 1, + 'user_id': user_id, + 'product': product, + 'action': 'create'} + orders.append(order) + # Отправка сообщения о создании заказа + requests.post("http://user-service:5001/event", json=order) + + return jsonify(order) +``` +В `user_service` этот запрос обрабатывается следующим образом: +```python +@app.route('/event', methods=['POST']) +def event(): + print('получено сообщение') + data = request.get_json() + print(data) + user_id = data.get('user_id') + + if data['action'] == 'create': + # Создание заказа у пользователя + user = next((user for user in users if user['id'] == user_id), None) + if user: + order_id = len(user.get('orders', [])) + 1 + order = {'id': order_id, 'product': data['product']} + user.setdefault('orders', []).append(order) + print(f"Order created for user {user_id}: {order}") + else: + print(f"User not found for order creation: {user_id}") + + elif data['action'] == 'update': + # Обновление заказа у пользователя + user = next((user for user in users if user['id'] == user_id), None) + if user: + order_id = data.get('id') + order = next((order for order in user['orders'] if order['id'] == order_id), None) + if order: + order['product'] = data['product'] + print(f"Order updated for user {user_id}: {order}") + else: + print(f"Order not found for update: {order_id}") + else: + print(f"User not found for order update: {user_id}") + + elif data['action'] == 'delete': + # Удаление заказа у пользователя + user = next((user for user in users if user['id'] == user_id), None) + if user: + order_id = user.get('id') + user['orders'] = [order for order in user.get('orders', []) if order['id'] != order_id] + print(f"Order deleted for user {user_id}: {order_id}") + else: + print(f"User not found for order deletion: {user_id}") + return jsonify({'result': True}) +``` + +### Описание docker-compose +```yaml +version: '3.8' + +services: + user-service: + build: + context: ./user_service + ports: + - "5001:5001" + networks: + - my_network + restart: always + + order-service: + build: + context: ./order_service + ports: + - "5002:5002" + networks: + - my_network + restart: always + + nginx: + image: nginx:latest + ports: + - "80:80" + depends_on: + - user-service + - order-service + networks: + - my_network + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + restart: always + +networks: + my_network: + driver: bridge +``` + +### Dockerfile микросервисов +```dockerfile +# user_service/Dockerfile +FROM python:3.11 +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . /app +CMD ["gunicorn", "--bind", "0.0.0.0:5001", "user_service:app"] +``` +```dockerfile +# order_service/Dockerfile +FROM python:3.11 +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . /app +CMD ["gunicorn", "--bind", "0.0.0.0:5002", "order_service:app"] +``` + +### Dockerfile nginx +```dockerfile +# nginx/Dockerfile +FROM nginx +COPY nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +### Видео +https://youtu.be/3-iF9xBHvCU \ No newline at end of file diff --git a/basharin_sevastyan_lab_3/docker-compose.yaml b/basharin_sevastyan_lab_3/docker-compose.yaml new file mode 100644 index 0000000..b4cc2e5 --- /dev/null +++ b/basharin_sevastyan_lab_3/docker-compose.yaml @@ -0,0 +1,37 @@ +version: '3.8' + +services: + user-service: + build: + context: ./user_service + ports: + - "5001:5001" + networks: + - my_network + restart: always + + order-service: + build: + context: ./order_service + ports: + - "5002:5002" + networks: + - my_network + restart: always + + nginx: + image: nginx:latest + ports: + - "80:80" + depends_on: + - user-service + - order-service + networks: + - my_network + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + restart: always + +networks: + my_network: + driver: bridge \ No newline at end of file diff --git a/basharin_sevastyan_lab_3/nginx/Dockerfile b/basharin_sevastyan_lab_3/nginx/Dockerfile new file mode 100644 index 0000000..1434a93 --- /dev/null +++ b/basharin_sevastyan_lab_3/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx +COPY nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/basharin_sevastyan_lab_3/nginx/nginx.conf b/basharin_sevastyan_lab_3/nginx/nginx.conf new file mode 100644 index 0000000..9bb0979 --- /dev/null +++ b/basharin_sevastyan_lab_3/nginx/nginx.conf @@ -0,0 +1,27 @@ +events { + worker_connections 1024; +} + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /user-service/ { + proxy_pass http://user-service:5001/; + 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; + } + + location /order-service/ { + proxy_pass http://order-service:5002/; + 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; + } + } +} \ No newline at end of file diff --git a/basharin_sevastyan_lab_3/order_service/Dockerfile b/basharin_sevastyan_lab_3/order_service/Dockerfile new file mode 100644 index 0000000..3e8508e --- /dev/null +++ b/basharin_sevastyan_lab_3/order_service/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11 +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . /app +CMD ["gunicorn", "--bind", "0.0.0.0:5002", "order_service:app"] \ No newline at end of file diff --git a/basharin_sevastyan_lab_3/order_service/order_service.py b/basharin_sevastyan_lab_3/order_service/order_service.py new file mode 100644 index 0000000..81d5ac2 --- /dev/null +++ b/basharin_sevastyan_lab_3/order_service/order_service.py @@ -0,0 +1,82 @@ +# order_service.py +from flask import Flask, request, jsonify +import requests + +app = Flask(__name__) + +# Конфигурация RabbitMQ +# time.sleep(20) +# connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq')) +# if connection: +# print(f'Connect to RabbitMQ') +# channel = connection.channel() +# channel.queue_declare(queue='order_service', durable=True) +orders = [] + + +# READ ALL +@app.route('/orders', methods=['GET']) +def show_orders(): + return orders + + +# CREATE +@app.route('/add_order/_', methods=['POST']) +def create_order(user_id, product): + order = {'id': len(orders) + 1, + 'user_id': user_id, + 'product': product, + 'action': 'create'} + orders.append(order) + # Отправка сообщения о создании заказа + requests.post("http://user-service:5001/event", json=order) + #channel.basic_publish(exchange='', routing_key='order_service', body=json.dumps(order).encode('utf-8'),properties=pika.BasicProperties(delivery_mode=2, # make message persistent)) + + return jsonify(order) + + +# READ +@app.route('/order/get_', methods=['GET']) +def get_order(order_id): + order = next((order for order in orders if order['id'] == order_id), None) + if order: + read_order = {'id': order['id'], + 'user_id': order['user_id'], + 'product': order['product']} + return jsonify(read_order) + else: + return jsonify({'error': 'Order not found'}) + + +# UPDATE +@app.route('/order//', methods=['PUT']) +def update_order(order_id, product): + order = next((order for order in orders if order['id'] == order_id), None) + if order: + order['product'] = product + order['action'] = 'update' + # Отправка сообщения об обновлении заказа + requests.post("http://user-service:5001/event", json=order) + #channel.basic_publish(exchange='', routing_key='order_service', body=json.dumps(order).encode('utf-8'), properties=pika.BasicProperties(delivery_mode=2, # make message persistent)) + return jsonify(order) + else: + return jsonify({'error': 'Order not found'}) + + +# DELETE +@app.route('/order/del_', methods=['DELETE']) +def delete_order(order_id): + global orders + del_order = next((order for order in orders if order['id'] == order_id), None) + orders = [order for order in orders if order['id'] != order_id] + del_order['action'] = 'delete' + + # Отправка сообщения об удалении заказа + requests.post("http://user-service:5001/event", json=del_order) + #channel.basic_publish(exchange='', routing_key='order_service', body=json.dumps(order).encode('utf-8'), properties=pika.BasicProperties(delivery_mode=2, # make message persistent )) + + return jsonify({'result': True}) + + +if __name__ == '__main__': + app.run(port=5002) diff --git a/basharin_sevastyan_lab_3/order_service/requirements.txt b/basharin_sevastyan_lab_3/order_service/requirements.txt new file mode 100644 index 0000000..3d70f32 --- /dev/null +++ b/basharin_sevastyan_lab_3/order_service/requirements.txt @@ -0,0 +1,3 @@ +Flask==3.0.0 +requests==2.31.0 +gunicorn==21.2.0 \ No newline at end of file diff --git a/basharin_sevastyan_lab_3/user_service/Dockerfile b/basharin_sevastyan_lab_3/user_service/Dockerfile new file mode 100644 index 0000000..f868a6a --- /dev/null +++ b/basharin_sevastyan_lab_3/user_service/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11 +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . /app +CMD ["gunicorn", "--bind", "0.0.0.0:5001", "user_service:app"] \ No newline at end of file diff --git a/basharin_sevastyan_lab_3/user_service/requirements.txt b/basharin_sevastyan_lab_3/user_service/requirements.txt new file mode 100644 index 0000000..3d70f32 --- /dev/null +++ b/basharin_sevastyan_lab_3/user_service/requirements.txt @@ -0,0 +1,3 @@ +Flask==3.0.0 +requests==2.31.0 +gunicorn==21.2.0 \ No newline at end of file diff --git a/basharin_sevastyan_lab_3/user_service/user_service.py b/basharin_sevastyan_lab_3/user_service/user_service.py new file mode 100644 index 0000000..245f2ee --- /dev/null +++ b/basharin_sevastyan_lab_3/user_service/user_service.py @@ -0,0 +1,170 @@ +# user_service.py +import json +import time +from flask import Flask, request, jsonify + +app = Flask(__name__) +users = [] + + +# Обработка сообщений о заказах +# def process_order_message(ch, method, properties, body: bytes): +# data = json.loads(body.decode('utf-8')) +# user_id = data.get('user_id') +# +# if data['action'] == 'create': +# # Создание заказа у пользователя +# user = next((user for user in users if user['id'] == user_id), None) +# if user: +# order_id = len(user.get('orders', [])) + 1 +# order = {'id': order_id, 'product': data['product']} +# user.setdefault('orders', []).append(order) +# print(f"Order created for user {user_id}: {order}") +# else: +# print(f"User not found for order creation: {user_id}") +# +# elif data['action'] == 'update': +# # Обновление заказа у пользователя +# user = next((user for user in users if user['id'] == user_id), None) +# if user: +# order_id = data.get('order_id') +# order = next((order for order in user['orders'] if order['id'] == order_id), None) +# if order: +# order['product'] = data['product'] +# print(f"Order updated for user {user_id}: {order}") +# else: +# print(f"Order not found for update: {order_id}") +# else: +# print(f"User not found for order update: {user_id}") +# +# elif data['action'] == 'delete': +# # Удаление заказа у пользователя +# user = next((user for user in users if user['id'] == user_id), None) +# if user: +# order_id = data.get('order_id') +# user['orders'] = [order for order in user.get('orders', []) if order['id'] != order_id] +# print(f"Order deleted for user {user_id}: {order_id}") +# else: +# print(f"User not found for order deletion: {user_id}") +# ch.basic_ack(delivery_tag=method.delivery_tag) + + +# first_con = True +# # Конфигурация RabbitMQ +# def connect_to_rabbitmq(): +# global first_con +# if first_con: +# time.sleep(15) +# first_con = False +# credentials = pika.PlainCredentials(username="guest", password="guest") +# connection = pika.BlockingConnection(pika.ConnectionParameters(host='rabbitmq', credentials=credentials)) +# channel = connection.channel() +# channel.queue_declare(queue='order_service', durable=True) +# channel.basic_qos(prefetch_count=1) +# +# def callback(ch, method, properties, body): +# process_order_message(ch, method, properties, body) +# ch.close() +# +# channel.basic_consume(queue='order_service', on_message_callback=callback, auto_ack=False) +# print('Waiting for messages. To exit press CTRL+C') +# while True: +# connection.process_data_events(time_limit=1) + + +@app.route('/event', methods=['POST']) +def event(): + print('получено сообщение') + data = request.get_json() + print(data) + user_id = data.get('user_id') + + if data['action'] == 'create': + # Создание заказа у пользователя + user = next((user for user in users if user['id'] == user_id), None) + if user: + order_id = len(user.get('orders', [])) + 1 + order = {'id': order_id, 'product': data['product']} + user.setdefault('orders', []).append(order) + print(f"Order created for user {user_id}: {order}") + else: + print(f"User not found for order creation: {user_id}") + + elif data['action'] == 'update': + # Обновление заказа у пользователя + user = next((user for user in users if user['id'] == user_id), None) + if user: + order_id = data.get('id') + order = next((order for order in user['orders'] if order['id'] == order_id), None) + if order: + order['product'] = data['product'] + print(f"Order updated for user {user_id}: {order}") + else: + print(f"Order not found for update: {order_id}") + else: + print(f"User not found for order update: {user_id}") + + elif data['action'] == 'delete': + # Удаление заказа у пользователя + user = next((user for user in users if user['id'] == user_id), None) + if user: + order_id = user.get('id') + user['orders'] = [order for order in user.get('orders', []) if order['id'] != order_id] + print(f"Order deleted for user {user_id}: {order_id}") + else: + print(f"User not found for order deletion: {user_id}") + return jsonify({'result': True}) + + +# CRUD операции для пользователей +# READ ALL +@app.route('/users', methods=['GET']) +def show_users(): + return users + + +# CREATE +@app.route('/add_user/', methods=['POST']) +def create_user(name): + if request.method == 'POST': + if len(users) == 0: + id = 1 + else: + id = users[-1]['id'] + 1 + user = {'id': id, 'name': name} + users.append(user) + return jsonify(user) + + +# READ +@app.route('/users/get_', methods=['GET']) +def get_user(user_id): + user = next((user for user in users if user['id'] == user_id), None) + if user: + return jsonify(user) + else: + return jsonify({'error': 'User not found'}) + + +# UPDATE +@app.route('/users/upd__', methods=['PUT']) +def update_user(user_id, name): + user = next((user for user in users if user['id'] == user_id), None) + if user: + user['name'] = name + return jsonify(user) + else: + return jsonify({'error': 'User not found'}) + + +# DELETE +@app.route('/users/del_', methods=['DELETE']) +def delete_user(user_id): + global users + users = [user for user in users if user['id'] != user_id] + return jsonify({'result': True}) + + +if __name__ == '__main__': + #connect_to_rabbitmq() + app.run(port=5001)