kosheev_maksim_lab_3 is ready #230
9
kosheev_maksim_lab_3/Dockerfile
Normal file
9
kosheev_maksim_lab_3/Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.9
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN pip install flask requests
|
||||||
|
|
||||||
|
CMD ["python", "subscriptions_service.py"] # Замените на нужный файл при сборке.
|
46
kosheev_maksim_lab_3/Readmy.md
Normal file
46
kosheev_maksim_lab_3/Readmy.md
Normal 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
|
8
kosheev_maksim_lab_3/books_service/Dockerfile
Normal file
8
kosheev_maksim_lab_3/books_service/Dockerfile
Normal 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"]
|
82
kosheev_maksim_lab_3/books_service/app.py
Normal file
82
kosheev_maksim_lab_3/books_service/app.py
Normal 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)
|
3
kosheev_maksim_lab_3/books_service/requirements.txt
Normal file
3
kosheev_maksim_lab_3/books_service/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
flask
|
||||||
|
flask-restful
|
||||||
|
requests
|
28
kosheev_maksim_lab_3/docker-compose.yml
Normal file
28
kosheev_maksim_lab_3/docker-compose.yml
Normal 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
|
11
kosheev_maksim_lab_3/nginx/nginx.conf
Normal file
11
kosheev_maksim_lab_3/nginx/nginx.conf
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location /books/ {
|
||||||
|
proxy_pass http://books_service:5000/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /subscriptions/ {
|
||||||
|
proxy_pass http://subscriptions_service:5001/;
|
||||||
|
}
|
||||||
|
}
|
8
kosheev_maksim_lab_3/subscriptions_service/Dockerfile
Normal file
8
kosheev_maksim_lab_3/subscriptions_service/Dockerfile
Normal 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"]
|
65
kosheev_maksim_lab_3/subscriptions_service/app.py
Normal file
65
kosheev_maksim_lab_3/subscriptions_service/app.py
Normal 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)
|
@ -0,0 +1,3 @@
|
|||||||
|
flask
|
||||||
|
flask-restful
|
||||||
|
requests
|
Loading…
Reference in New Issue
Block a user