Merge pull request 'razin_andrey_lab_3' (#431) from razin_andrey_lab_3 into main

Reviewed-on: #431
This commit was merged in pull request #431.
This commit is contained in:
2025-12-08 23:10:48 +04:00
9 changed files with 323 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
## REST API, Gateway и синхронный обмен между микросервисами
## Цель работы
Изучение шаблона проектирования Gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API на примере системы управления библиотекой.
## Задачи
- [x] Создать 2 микросервиса, реализующих CRUD на связанных сущностях
- [x] Реализовать механизм синхронного обмена сообщениями между микросервисами
- [x] Реализовать шлюз на основе прозрачного прокси-сервера nginx
- [x] Организовать единую точку входа для всех микросервисов
### подробнее о порядке действий:
1. запускаем docker-compose
2. идем в Postman для тестирования работоспособности микросервисов
3. создаем абонемент (POST)
4. получаем список абонементов (GET)
5. тестируем метод PUT для абонемента по uuid
6. копируем UUID созданного абонемента
7. создаем книгу с использованием полученного UUID абонемента
8. проверяем, что книга создалась (GET)
9. тестируем метод PUT для книги по uuid
10. тестируем удаление книг и абонементов по uuid
[видео](https://vkvideo.ru/video-234156294_456239021)

View File

@@ -0,0 +1,12 @@
FROM python:3.9-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 5002
CMD ["python", "app.py"]

View File

@@ -0,0 +1,122 @@
from flask import Flask, request, jsonify
import uuid
import requests
app = Flask(__name__)
books = {}
def get_subscription_info(subscription_uuid):
try:
response = requests.get(f'http://subscription-service:5001/{subscription_uuid}')
if response.status_code == 200:
return response.json()
return None
except requests.exceptions.RequestException:
return None
def serialize_book_for_list(book):
return {
"uuid": book["uuid"],
"author": book["author"],
"subject": book["subject"],
"year": book["year"],
"subscriptionUuid": book["subscriptionUuid"]
}
def serialize_book_for_details(book):
subscription_info = get_subscription_info(book["subscriptionUuid"])
book_data = {
"uuid": book["uuid"],
"author": book["author"],
"subject": book["subject"],
"year": book["year"],
"subscriptionUuid": book["subscriptionUuid"]
}
if subscription_info:
book_data["subscriptionInfo"] = {
"number": subscription_info["number"],
"fullName": subscription_info["fullName"],
"issued": subscription_info["issued"]
}
return book_data
# GET /
@app.route('/', methods=['GET'])
def get_books():
return jsonify([serialize_book_for_list(book) for book in books.values()])
# GET /{uuid}
@app.route('/<string:book_uuid>', methods=['GET'])
def get_book(book_uuid):
book = books.get(book_uuid)
if not book:
return jsonify({"error": "Book not found"}), 404
return jsonify(serialize_book_for_details(book))
# POST /
@app.route('/', methods=['POST'])
def create_book():
data = request.get_json()
if not data or 'author' not in data or 'subject' not in data or 'year' not in data:
return jsonify({"error": "Missing required fields"}), 400
# Check if subscription exists
if 'subscriptionUuid' in data:
subscription_info = get_subscription_info(data['subscriptionUuid'])
if not subscription_info:
return jsonify({"error": "Subscription not found"}), 400
book_uuid = str(uuid.uuid4())
book = {
"uuid": book_uuid,
"author": data['author'],
"subject": data['subject'],
"year": data['year'],
"subscriptionUuid": data.get('subscriptionUuid')
}
books[book_uuid] = book
return jsonify(serialize_book_for_details(book)), 201
# PUT /{uuid}
@app.route('/<string:book_uuid>', methods=['PUT'])
def update_book(book_uuid):
if book_uuid not in books:
return jsonify({"error": "Book not found"}), 404
data = request.get_json()
book = books[book_uuid]
if 'subscriptionUuid' in data:
subscription_info = get_subscription_info(data['subscriptionUuid'])
if not subscription_info:
return jsonify({"error": "Subscription not found"}), 400
if 'author' in data:
book['author'] = data['author']
if 'subject' in data:
book['subject'] = data['subject']
if 'year' in data:
book['year'] = data['year']
if 'subscriptionUuid' in data:
book['subscriptionUuid'] = data['subscriptionUuid']
return jsonify(serialize_book_for_details(book))
# DELETE /{uuid}
@app.route('/<string:book_uuid>', methods=['DELETE'])
def delete_book(book_uuid):
if book_uuid not in books:
return jsonify({"error": "Book not found"}), 404
del books[book_uuid]
return '', 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5002, debug=True)

View File

@@ -0,0 +1,3 @@
flask==2.3.3
requests==2.31.0
uuid

View File

@@ -0,0 +1,32 @@
version: '3.8'
services:
gateway:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- subscription-service
- book-service
networks:
- lab3-network
subscription-service:
build: ./subscription-service
ports:
- "5001"
networks:
- lab3-network
book-service:
build: ./book-service
ports:
- "5002"
networks:
- lab3-network
networks:
lab3-network:
driver: bridge

View File

@@ -0,0 +1,41 @@
events {
worker_connections 1024;
}
http {
upstream subscription_service {
server subscription-service:5001;
}
upstream book_service {
server book-service:5002;
}
server {
listen 80;
# Subscription service routes
location /subscriptions/ {
proxy_pass http://subscription_service/;
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;
}
# Book service routes
location /books/ {
proxy_pass http://book_service/;
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;
}
# Root redirect
location / {
return 200 'Gateway is working! Use /subscriptions/ or /books/';
add_header Content-Type text/plain;
}
}
}

View File

@@ -0,0 +1,12 @@
FROM python:3.9-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 5001
CMD ["python", "app.py"]

View File

@@ -0,0 +1,78 @@
from flask import Flask, request, jsonify
import uuid
from datetime import datetime
app = Flask(__name__)
subscriptions = {}
def serialize_subscription(sub):
return {
"uuid": sub["uuid"],
"number": sub["number"],
"fullName": sub["fullName"],
"issued": sub["issued"]
}
# GET /
@app.route('/', methods=['GET'])
def get_subscriptions():
return jsonify([serialize_subscription(sub) for sub in subscriptions.values()])
# GET /{uuid}
@app.route('/<string:subscription_uuid>', methods=['GET'])
def get_subscription(subscription_uuid):
subscription = subscriptions.get(subscription_uuid)
if not subscription:
return jsonify({"error": "Subscription not found"}), 404
return jsonify(serialize_subscription(subscription))
# POST /
@app.route('/', methods=['POST'])
def create_subscription():
data = request.get_json()
if not data or 'number' not in data or 'fullName' not in data:
return jsonify({"error": "Missing required fields"}), 400
subscription_uuid = str(uuid.uuid4())
subscription = {
"uuid": subscription_uuid,
"number": data['number'],
"fullName": data['fullName'],
"issued": data.get('issued', datetime.utcnow().isoformat() + 'Z')
}
subscriptions[subscription_uuid] = subscription
return jsonify(serialize_subscription(subscription)), 201
# PUT /{uuid}
@app.route('/<string:subscription_uuid>', methods=['PUT'])
def update_subscription(subscription_uuid):
if subscription_uuid not in subscriptions:
return jsonify({"error": "Subscription not found"}), 404
data = request.get_json()
subscription = subscriptions[subscription_uuid]
if 'number' in data:
subscription['number'] = data['number']
if 'fullName' in data:
subscription['fullName'] = data['fullName']
if 'issued' in data:
subscription['issued'] = data['issued']
return jsonify(serialize_subscription(subscription))
# DELETE /{uuid}
@app.route('/<string:subscription_uuid>', methods=['DELETE'])
def delete_subscription(subscription_uuid):
if subscription_uuid not in subscriptions:
return jsonify({"error": "Subscription not found"}), 404
del subscriptions[subscription_uuid]
return '', 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=True)

View File

@@ -0,0 +1,2 @@
flask==2.3.3
uuid