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:
21
razin_andrey_lab_3/README.md
Normal file
21
razin_andrey_lab_3/README.md
Normal 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)
|
||||
12
razin_andrey_lab_3/book-service/Dockerfile
Normal file
12
razin_andrey_lab_3/book-service/Dockerfile
Normal 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"]
|
||||
122
razin_andrey_lab_3/book-service/app.py
Normal file
122
razin_andrey_lab_3/book-service/app.py
Normal 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)
|
||||
3
razin_andrey_lab_3/book-service/requirements.txt
Normal file
3
razin_andrey_lab_3/book-service/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
flask==2.3.3
|
||||
requests==2.31.0
|
||||
uuid
|
||||
32
razin_andrey_lab_3/docker-compose.yml
Normal file
32
razin_andrey_lab_3/docker-compose.yml
Normal 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
|
||||
41
razin_andrey_lab_3/nginx/nginx.conf
Normal file
41
razin_andrey_lab_3/nginx/nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
razin_andrey_lab_3/subscription-service/Dockerfile
Normal file
12
razin_andrey_lab_3/subscription-service/Dockerfile
Normal 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"]
|
||||
78
razin_andrey_lab_3/subscription-service/app.py
Normal file
78
razin_andrey_lab_3/subscription-service/app.py
Normal 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)
|
||||
2
razin_andrey_lab_3/subscription-service/requirements.txt
Normal file
2
razin_andrey_lab_3/subscription-service/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
flask==2.3.3
|
||||
uuid
|
||||
Reference in New Issue
Block a user