kosheev_maksim_lab_3 is ready #230

Open
1maksim1 wants to merge 1 commits from kosheev_maksim_lab_3 into main
10 changed files with 263 additions and 0 deletions

View File

@ -0,0 +1,9 @@
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install flask requests
CMD ["python", "subscriptions_service.py"] # Замените на нужный файл при сборке.

View File

@ -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

View File

@ -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"]

View File

@ -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/<uuid:book_id>', 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/<uuid:book_id>', 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/<uuid:book_id>', 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)

View File

@ -0,0 +1,3 @@
flask
flask-restful
requests

View File

@ -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

View File

@ -0,0 +1,11 @@
server {
listen 80;
location /books/ {
proxy_pass http://books_service:5000/;
}
location /subscriptions/ {
proxy_pass http://subscriptions_service:5001/;
}
}

View File

@ -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"]

View File

@ -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/<uuid:subscription_id>', 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/<uuid:subscription_id>', 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/<uuid:subscription_id>', 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)

View File

@ -0,0 +1,3 @@
flask
flask-restful
requests