diff --git a/kosheev_maksim_lab_3/Dockerfile b/kosheev_maksim_lab_3/Dockerfile new file mode 100644 index 0000000..af7563e --- /dev/null +++ b/kosheev_maksim_lab_3/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.9 + +WORKDIR /app + +COPY . . + +RUN pip install flask requests + +CMD ["python", "subscriptions_service.py"] # Замените на нужный файл при сборке. \ No newline at end of file diff --git a/kosheev_maksim_lab_3/Readmy.md b/kosheev_maksim_lab_3/Readmy.md new file mode 100644 index 0000000..3128055 --- /dev/null +++ b/kosheev_maksim_lab_3/Readmy.md @@ -0,0 +1,46 @@ +# Лабораторная работа №3 - REST API, шлюз и синхронный обмен данными между микросервисами + +## Задание + +### Цель: +Изучение принципов проектирования с использованием паттерна шлюза, организации синхронной передачи данных между микросервисами и применения архитектурного стиля RESTful API. + +### Задачи: +1. Создание двух микросервисов, которые реализуют операции CRUD для связанных сущностей. +2. Реализация механизма синхронного обмена данными между микросервисами. +3. Настройка шлюза на базе Nginx в качестве прозрачного прокси-сервера. + +### Микросервисы: +1. **books_service** — сервис, который управляет информацией о книгах. +2. **subscriptions_service** — сервис, который обрабатывает данные об абонементах. + +### Связь между микросервисами: +- Один абонемент может быть связан с несколькими книгами. Взаимодействие между сервисами осуществляется через синхронные HTTP-запросы. + +## Как запустить проект: + +Для запуска приложения необходимо выполнить команду: + +```bash +docker-compose up --build +``` +После этого проект будет доступен на следующих адресах: + +Books Service: http://localhost:5000/books/ +Subscriptions Service: http://localhost:5001/subscriptions/ +## Описание работы: +Для разработки микросервисов был выбран язык программирования Python, с использованием фреймворка Flask для реализации API. + +## Синхронный обмен данными +Сервис books_service обращается к subscriptions_service для получения информации о подписках, связанных с каждой книгой. +Все запросы между сервисами выполняются синхронно с использованием HTTP-запросов, что позволяет обеспечить актуальность данных при выполнении операций CRUD. +Docker Compose +Конфигурационный файл docker-compose.yml представляет собой многоконтейнерное приложение, которое включает в себя три сервиса: books_service, subscriptions_service и nginx. С помощью Docker Compose можно удобно запустить все сервисы одновременно. + +## Nginx +Конфигурация Nginx настроена так, чтобы обрабатывать входящие HTTP-запросы и перенаправлять их на соответствующие сервисы. Это позволяет использовать один общий адрес для доступа к разным API-сервисам. + +Nginx будет действовать как шлюз, который будет маршрутизировать запросы от пользователей в соответствующие микросервисы. + +### Видео +https://disk.yandex.ru/i/b12BECyaGmbqUQ \ No newline at end of file diff --git a/kosheev_maksim_lab_3/books_service/Dockerfile b/kosheev_maksim_lab_3/books_service/Dockerfile new file mode 100644 index 0000000..e1985cf --- /dev/null +++ b/kosheev_maksim_lab_3/books_service/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.9-slim + +WORKDIR /app +COPY requirements.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt +COPY . . + +CMD ["python", "app.py"] \ No newline at end of file diff --git a/kosheev_maksim_lab_3/books_service/app.py b/kosheev_maksim_lab_3/books_service/app.py new file mode 100644 index 0000000..52b9013 --- /dev/null +++ b/kosheev_maksim_lab_3/books_service/app.py @@ -0,0 +1,82 @@ +from flask import Flask, jsonify, request +import requests +import uuid + +app = Flask(__name__) + +# Данные о книгах (в памяти) +books = [ + { + "uuid": str(uuid.uuid4()), + "author": "J.K.R.", + "subject": "HP and PS", + "year": 1997, + "subscriptionUuid": "8f036445-a5bd-401c-926e-840f9de795cd" + } +] + +SUBSCRIPTIONS_URL = "http://subscriptions_service:5000/subscriptions/" + + +@app.route('/books/', methods=['GET']) +def get_books(): + return jsonify(books), 200 + + +@app.route('/books/', methods=['GET']) +def get_book(book_id): + book = next((b for b in books if b['uuid'] == str(book_id)), None) + if not book: + return jsonify({"error": "Book not found"}), 404 + + # Получение данных об абонементе + response = requests.get(f"{SUBSCRIPTIONS_URL}{book['subscriptionUuid']}") + if response.status_code == 200: + book["subscriptionInfo"] = response.json() + return jsonify(book), 200 + + +@app.route('/books/', methods=['POST']) +def create_book(): + data = request.json + new_book = { + "uuid": str(uuid.uuid4()), + "author": data["author"], + "subject": data["subject"], + "year": data["year"], + "subscriptionUuid": data["subscriptionUuid"] + } + books.append(new_book) + return jsonify(new_book), 201 + + +@app.route('/books/', methods=['PUT']) +def update_book(book_id): + data = request.json + book = next((b for b in books if b['uuid'] == str(book_id)), None) + if not book: + return jsonify({"error": "Book not found"}), 404 + + # Обновляем данные книги + book["author"] = data.get("author", book["author"]) + book["subject"] = data.get("subject", book["subject"]) + book["year"] = data.get("year", book["year"]) + book["subscriptionUuid"] = data.get("subscriptionUuid", book["subscriptionUuid"]) + + return jsonify(book), 200 + + +@app.route('/books/', methods=['DELETE']) +def delete_book(book_id): + global books + book = next((b for b in books if b['uuid'] == str(book_id)), None) + if not book: + return jsonify({"error": "Book not found"}), 404 + + # Удаляем книгу + books = [b for b in books if b['uuid'] != str(book_id)] + return jsonify({"message": "Book deleted successfully"}), 200 + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) \ No newline at end of file diff --git a/kosheev_maksim_lab_3/books_service/requirements.txt b/kosheev_maksim_lab_3/books_service/requirements.txt new file mode 100644 index 0000000..07b9331 --- /dev/null +++ b/kosheev_maksim_lab_3/books_service/requirements.txt @@ -0,0 +1,3 @@ +flask +flask-restful +requests \ No newline at end of file diff --git a/kosheev_maksim_lab_3/docker-compose.yml b/kosheev_maksim_lab_3/docker-compose.yml new file mode 100644 index 0000000..4e2fca5 --- /dev/null +++ b/kosheev_maksim_lab_3/docker-compose.yml @@ -0,0 +1,28 @@ +version: "3.8" + +services: + books_service: + build: + context: ./books_service + dockerfile: Dockerfile + ports: + - "5000:5000" + + + subscriptions_service: + build: + context: ./subscriptions_service + dockerfile: Dockerfile + ports: + - "5001:5001" + + + nginx: + image: nginx:latest + volumes: + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf + ports: + - "80:80" + depends_on: + - books_service + - subscriptions_service \ No newline at end of file diff --git a/kosheev_maksim_lab_3/nginx/nginx.conf b/kosheev_maksim_lab_3/nginx/nginx.conf new file mode 100644 index 0000000..5f89208 --- /dev/null +++ b/kosheev_maksim_lab_3/nginx/nginx.conf @@ -0,0 +1,11 @@ +server { + listen 80; + + location /books/ { + proxy_pass http://books_service:5000/; + } + + location /subscriptions/ { + proxy_pass http://subscriptions_service:5001/; + } +} \ No newline at end of file diff --git a/kosheev_maksim_lab_3/subscriptions_service/Dockerfile b/kosheev_maksim_lab_3/subscriptions_service/Dockerfile new file mode 100644 index 0000000..e1985cf --- /dev/null +++ b/kosheev_maksim_lab_3/subscriptions_service/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.9-slim + +WORKDIR /app +COPY requirements.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt +COPY . . + +CMD ["python", "app.py"] \ No newline at end of file diff --git a/kosheev_maksim_lab_3/subscriptions_service/app.py b/kosheev_maksim_lab_3/subscriptions_service/app.py new file mode 100644 index 0000000..7b2bb10 --- /dev/null +++ b/kosheev_maksim_lab_3/subscriptions_service/app.py @@ -0,0 +1,65 @@ +from flask import Flask, jsonify, request +import uuid + +app = Flask(__name__) + +# Данные об абонементах (в памяти) +subscriptions = [ + { + "uuid": str(uuid.uuid4()), + "number": 135, + "fullName": "Иванов И.И.", + "issued": "2023-10-18T05:41:00Z" + } +] + +@app.route('/subscriptions/', methods=['GET']) +def get_subscriptions(): + return jsonify(subscriptions), 200 + +@app.route('/subscriptions/', methods=['GET']) +def get_subscription(subscription_id): + subscription = next((s for s in subscriptions if s['uuid'] == str(subscription_id)), None) + if subscription: + return jsonify(subscription), 200 + return jsonify({"error": "Subscription not found"}), 404 + +@app.route('/subscriptions/', methods=['POST']) +def create_subscription(): + data = request.json + new_subscription = { + "uuid": str(uuid.uuid4()), + "number": data["number"], + "fullName": data["fullName"], + "issued": data["issued"] + } + subscriptions.append(new_subscription) + return jsonify(new_subscription), 201 + +@app.route('/subscriptions/', methods=['PUT']) +def update_subscription(subscription_id): + data = request.json + subscription = next((s for s in subscriptions if s['uuid'] == str(subscription_id)), None) + if not subscription: + return jsonify({"error": "Subscription not found"}), 404 + + # Обновляем поля, если они переданы + subscription["number"] = data.get("number", subscription["number"]) + subscription["fullName"] = data.get("fullName", subscription["fullName"]) + subscription["issued"] = data.get("issued", subscription["issued"]) + + return jsonify(subscription), 200 + +@app.route('/subscriptions/', methods=['DELETE']) +def delete_subscription(subscription_id): + global subscriptions + subscription = next((s for s in subscriptions if s['uuid'] == str(subscription_id)), None) + if not subscription: + return jsonify({"error": "Subscription not found"}), 404 + + # Удаляем запись + subscriptions = [s for s in subscriptions if s['uuid'] != str(subscription_id)] + return jsonify({"message": "Subscription deleted successfully"}), 200 + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5001) \ No newline at end of file diff --git a/kosheev_maksim_lab_3/subscriptions_service/requirements.txt b/kosheev_maksim_lab_3/subscriptions_service/requirements.txt new file mode 100644 index 0000000..07b9331 --- /dev/null +++ b/kosheev_maksim_lab_3/subscriptions_service/requirements.txt @@ -0,0 +1,3 @@ +flask +flask-restful +requests \ No newline at end of file