# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами ## Задание #### Цель: Изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API. #### Задачи: * Создать 2 микросервиса, реализующих CRUD на связанных сущностях. * Реализовать механизм синхронного обмена сообщениями между микросервисами. * Реализовать шлюз на основе прозрачного прокси-сервера nginx. ### Сервисы:  1. ```product_service``` - сервис, отвечающий за продукты  2. ```service_service``` - сервис, отвечающий за услуги, которые содержат в себе продукты #### Связь между сервисами: ```service (single) <- product (many)``` ## Как запустить программу: ``` docker compose up ``` ## Файловая иерархия ``` Лаба 3/ |-- service_service/ | |-- Dockerfile | |-- requirements.txt | |-- service_service.py |-- product_service/ | |-- Dockerfile | |-- requirements.txt | |-- product_service.py |-- nginx/ | |-- Dockerfile | |-- nginx.conf |-- docker-compose.yml ``` ## Описание работы: Для разработки приложений был выбран язык программирования - ```python``` #### Синхронный обмен сообщениями `product_service` будет отправлять http-запрос на `service_service` при определенных crud операциях. #### Операции CRUD для услуги Просмотр списка имеющихся услуг: ``` @app.route('/all', methods=['GET']) def show_service_ids(): return service_ids ``` Добавление услуги: ``` @app.route('/add/', methods=['POST']) def create_service_id(name): if request.method == 'POST': if len(service_ids) == 0: id = 1 else: id = service_ids[-1]['id'] + 1 service_id = {'id': id, 'name': name} service_ids.append(service_id) return jsonify(service_id) ``` Поиск услуги по ID: ``` @app.route('/get/', methods=['GET']) def get_service_id(id): found_service = next((service_id for service_id in service_ids if service_id['id'] == id), None) if found_service: return jsonify(found_service) else: return jsonify({'error': 'service_id not found'}) ``` Обновление услуги: ``` @app.route('/upd/_', methods=['PUT']) def update_service_id(id, name): service = next((service_id for service_id in service_ids if service_id['id'] == id), None) if service: service['name'] = name return jsonify(service) else: return jsonify({'error': 'service_id not found'}) ``` Удаление услуги: ``` @app.route('/del/', methods=['DELETE']) def delete_service_id(id): global service_ids service_ids = [service_id for service_id in service_ids if service_id['id'] != id] return jsonify({'result': True}) if __name__ == '__main__': app.run(port=5001) ``` #### Операции CRUD для продукта Просмотр списка имеющихся продуктов: ``` @app.route('/all', methods=['GET']) def show_products(): return products ``` Добавление продукта: ``` @app.route('/add/_', methods=['POST']) def create_product(service_id, product): product = {'id': len(products) + 1, 'service_id': service_id, 'product': product, 'action': 'create'} products.append(product) requests.post("http://service-service:5001/event", json=product) return jsonify(product) ``` Поиск продукта по ID: ``` @app.route('/get/', methods=['GET']) def get_product(product_id): found_product = next((product for product in products if product['id'] == product_id), None) if found_product: read_product = {'id': found_product['id'], 'service_id': found_product['service_id'], 'product': found_product['product']} return jsonify(read_product) else: return jsonify({'error': 'product not found'}) ``` Обновление продукта: ``` @app.route('/upd//', methods=['PUT']) def update_product(product_id, new_product): product = next((prod for prod in products if prod['id'] == product_id), None) if product: product['product'] = new_product product['action'] = 'update' requests.post("http://service-service:5001/event", json=product) return jsonify({'message': 'Product updated successfully'}) else: return jsonify({'error': 'Product not found'}) ``` Удаление продукта: ``` @app.route('/product/del_', methods=['DELETE']) def delete_product(product_id): global products deleted_product = next((product for product in products if product['id'] == product_id), None) products = [product for product in products if product['id'] != product_id] deleted_product['action'] = 'delete' requests.post("http://service-service:5001/event", json=deleted_product) return jsonify({'result': True}) ``` ### Docker-compose Конфигурационный файл ```docker-сompose``` представляет собой многоконтейнерное приложение с тремя сервисами: ```service-service```, ```product-service``` и ```nginx```. Эти сервисы подключены к собственной сети типа "```bridge```" с именем ```my_network``` ``` version: '3.8' services: service-service: build: context: ./service_service ports: - "5001:5001" networks: - my_network restart: always product-service: build: context: ./product_service ports: - "5002:5002" networks: - my_network restart: always nginx: image: nginx:latest ports: - "80:80" depends_on: - service-service - product-service networks: - my_network volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf restart: always networks: my_network: driver: bridge ``` ### Nginx Этот файл представляет собой конфигурацию для ```Nginx```, который является веб-сервером и обратным прокси: ``` events { worker_connections 1024; } http { server { listen 80; listen [::]:80; server_name localhost; location /service-service/ { proxy_pass http://service-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 /product-service/ { proxy_pass http://product-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; } } } ``` ### Dockerfile микросервисов 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", "service_service:app"] ``` 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", "product_service:app"] ``` # Youtube https://www.youtube.com/watch?v=Sf0CkkcuveY