zinovev_vladimir_lab_3 id ready

This commit is contained in:
vladimir_zinovev
2025-12-13 10:56:07 +04:00
parent 312b184706
commit 3e1d7e8d6e
8 changed files with 539 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
# Лабораторная работа 3
**Цель:** изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
**Задачи:**
1. Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
2. Реализовать механизм синхронного обмена сообщениями между микросервисами.
3. Реализовать шлюз на основе прозрачного прокси-сервера nginx.
## Как запустить лабораторную работу
1. Установить и запустить Docker Desktop
2. Открыть папку с проектом и запустить в ней терминал
3. Запустить сервисы командой `docker-compose up --build`.
4. Перейти по локальным адресам для работы в Swagger:
* localhost:8080/glamping_service/docs/ - **Сервис для управления размещениями в глэмпингах**
* localhost:8080/reservation_service/docs/ - **Сервис для управления бронированиями глэмпингов**
5. Выполнить CRUD-операции.
## Какие технологии использовали
1. Docker - контейнеризация приложений.
2. Docker Compose - оркестратор для сбора контейнеров.
3. Dockerfile - стандартизированная сборка образов.
4. Python - язык программирования для реализации логики сервисов.
5. Nginx - API Gateway и прокси-сервер.
6. Swagger UI - развёртывание REST API.
## Особенности реализации
* При запросе `GET /reservation_service/full/{uuid}` сервис бронирований выполняет синхронный HTTP-запрос к сервису глэмпингов для получения дополнительной информации о размещении.
* Nginx выполняет роль API Gateway и проксирует запросы к соответствующим сервисам:
-Запросы `/glamping_service/` → glamping_service:8080
-Запросы `/reservation_service/` → reservation_service:8081
* Для упрощения демонстрации данные хранятся в оперативной памяти в списках.
## API Endpoints
### Glamping Service
| Метод | Endpoint | Описание |
|--------|-------------------------|-----------------------------------------------------------|
|GET | /glamping_service/ | Получение списка глэмпингов |
|GET | /glamping_service/{uuid}| Получение глэмпинга по UUID |
|GET | /glamping_service/full | Получение всех глэмпингов с информацией о бронированиях |
|GET | ..service/full/{uuid} | Получение глэмпинга с информацией о бронированиях по UUID |
|GET | ../by-reservation/{uuid}| Получение глэмпинга по UUID бронирования |
|GET | ..service/check/{uuid} | Проверка существования глэмпинга по UUID |
|POST | /glamping_service/ | Создание нового глэмпинга |
|PUT | /glamping_service/{uuid}| Обновление глэмпинга по UUID |
|DELETE | /glamping_service/{uuid}| Удаление глэмпинга по UUID |
### Reservation Service
| Метод | Endpoint | Описание |
|--------|-------------------------|-----------------------------------------------------------|
|GET | /reservation_service/ | Получение списка бронирований |
|GET | ..service/{uuid} | Получение бронирования по UUID |
|GET | ..service/full | Получение всех бронирований с информацией о глэмпингах |
|GET | ..service/full/{uuid} | Получение бронирования с информацией о глэмпинге по UUID |
|GET | ../by-glamping/{uuid} | Получение бронирований по размещению в глэмпинге |
|POST | /reservation_service/ | Создание нового бронирования |
|PUT | ..service/{uuid} | Обновление бронирования по UUID |
|DELETE | /..service/{uuid} | Удаление бронирования по UUID |
**Пример POST-Запроса** для глэмпинга по адресу http://localhost/glamping_service/
```
{
"title": "Романтическая юрта",
"description": "Уютная юрта для пар с видом на горы",
"type": "Юрта",
"price_per_night": 200.00,
"capacity": 2,
"amenities": ["Камин", "Джакузи", "Завтрак"],
"location": "Алтай, горный район"
}
```
## Тесты
**Видео:**
[Ссылка](https://vk.com/video271933856_456242103?list=ln-Si7duYrJUv89MlSJIt)

View File

@@ -0,0 +1,28 @@
version: '3.8'
services:
glamping_service:
container_name: glamping_service
build:
context: .
dockerfile: ./glamping_service/Dockerfile
expose:
- 8080
reservation_service:
container_name: reservation_service
build:
context: .
dockerfile: ./reservation_service/Dockerfile
expose:
- 8081
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- glamping_service
- reservation_service

View File

@@ -0,0 +1,12 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY glamping_service/glamping_service.py .
EXPOSE 8080
CMD ["python", "glamping_service.py"]

View File

@@ -0,0 +1,155 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from uuid import UUID, uuid4
from datetime import datetime
import requests
import uvicorn
app = FastAPI(
root_path="/glamping_service",
title="Сервис Глэмпингов",
description="Сервис для управления размещениями в глэмпингах",
version="1.0.0"
)
reservations_url = "http://reservation_service:8081/"
class Glamping(BaseModel):
uuid: UUID
title: str
description: str
type: str
price_per_night: float
capacity: int
amenities: List[str]
location: str
created_at: datetime
class GlampingCreate(BaseModel):
title: str
description: str
type: str
price_per_night: float
capacity: int
amenities: List[str] = []
location: str
glamping_accommodations: List[Glamping] = [
Glamping(
uuid=UUID("89fa1e7a-7e88-445e-a4d8-6d4497ea8f19"),
title="Звездный купол",
description="Панорамный купол с видом на звезды",
type="Геодом",
price_per_night=250.00,
capacity=2,
amenities=["Джакузи", "Кондиционер", "Мини-бар"],
location="Карелия, берег озера",
created_at=datetime(2023, 10, 18, 5, 41, 0)
),
Glamping(
uuid=UUID("0351ee11-f11b-4d83-b2c8-1075b0c357dc"),
title="Лесной люкс",
description="Домик на дереве с камином",
type="Домик на дереве",
price_per_night=180.00,
capacity=4,
amenities=["Камин", "Терраса", "Гидромассажная ванна"],
location="Сибирь, тайга",
created_at=datetime(2023, 9, 12, 10, 30, 0)
),
Glamping(
uuid=UUID("dfc17619-7690-47aa-ae8e-6a5068f8ddec"),
title="Сафари-палатка",
description="Роскошная палатка в стиле сафари",
type="Палатка",
price_per_night=150.00,
capacity=3,
amenities=["Собственный бассейн", "Терраса", "Барбекю"],
location="Краснодарский край",
created_at=datetime(2023, 8, 25, 14, 15, 0)
),
]
@app.get("/", response_model=List[Glamping], summary="Получить все домики", description="Получить список всех размещений.")
def get_all_glampings():
return glamping_accommodations
@app.get("/full", summary="Получить все глэмпинги с информацией о бронированиях", description="Получить все размещения в глэмпингах с дополнительной информацией о бронированиях.")
def get_all_glampings_with_reservations():
reservations = requests.get(reservations_url).json()
response = []
for glamping in glamping_accommodations:
glamping_reservations = [r for r in reservations if str(r["glamping_uuid"]) == str(glamping.uuid)]
if glamping_reservations:
response.append({**glamping.dict(), "reservations_info": glamping_reservations})
return response
@app.get("/by-reservation/{reservation_uuid}", response_model=Optional[Glamping], summary="Получить глэмпинг по бронированию", description="Получить размещение в глэмпинге для заданного бронирования.")
def get_glamping_by_reservation(reservation_uuid: UUID):
try:
reservation = requests.get(f"{reservations_url}{reservation_uuid}").json()
if reservation:
glamping_uuid = UUID(reservation["glamping_uuid"])
glamping = next((g for g in glamping_accommodations if g.uuid == glamping_uuid), None)
return glamping
except:
return None
return None
@app.get("/{glamping_uuid}", response_model=Glamping, summary="Получить глэмпинг по ID", description="Получить одно размещение в глэмпинге по его ID.")
def get_glamping_by_id(glamping_uuid: UUID):
glamping = next((glamping for glamping in glamping_accommodations if glamping.uuid == glamping_uuid), None)
if not glamping:
raise HTTPException(status_code=404, detail="Размещение не найдено")
return glamping
@app.get("/full/{glamping_uuid}", summary="Получить глэмпинг с информацией о бронированиях", description="Получить размещение в глэмпинге с дополнительной информацией о бронированиях.")
def get_glamping_with_reservation_info(glamping_uuid: UUID):
glamping = next((glamping for glamping in glamping_accommodations if glamping.uuid == glamping_uuid), None)
if not glamping:
raise HTTPException(status_code=404, detail="Размещение не найдено")
glamping_reservations = requests.get(f"{reservations_url}by-glamping/{glamping_uuid}").json()
return {**glamping.dict(), "reservations_info": glamping_reservations}
@app.post("/", response_model=Glamping, summary="Создать новый глэмпинг", description="Добавить новое размещение в глэмпинге в бд.")
def create_glamping(glamping: GlampingCreate):
new_glamping = Glamping(
uuid=uuid4(),
created_at=datetime.now(),
**glamping.dict()
)
glamping_accommodations.append(new_glamping)
return new_glamping
@app.put("/{glamping_uuid}", response_model=Glamping, summary="Обновить глэмпинг", description="Обновить существующее размещение по его ID.")
def update_glamping(glamping_uuid: UUID, glamping_update: GlampingCreate):
glamping = next((glamping for glamping in glamping_accommodations if glamping.uuid == glamping_uuid), None)
if not glamping:
raise HTTPException(status_code=404, detail="Размещение не найдено")
glamping.title = glamping_update.title
glamping.description = glamping_update.description
glamping.type = glamping_update.type
glamping.price_per_night = glamping_update.price_per_night
glamping.capacity = glamping_update.capacity
glamping.amenities = glamping_update.amenities
glamping.location = glamping_update.location
return glamping
@app.delete("/{glamping_uuid}", summary="Удалить глэмпинг", description="Удалить размещение в глэмпинге по его ID.")
def delete_glamping(glamping_uuid: UUID):
global glamping_accommodations
glamping_accommodations = [glamping for glamping in glamping_accommodations if glamping.uuid != glamping_uuid]
return {"detail": "Размещение успешно удалено"}
@app.get("/check/{glamping_uuid}", summary="Проверить существование глэмпинга", description="Проверить существование размещения в глэмпинге.")
def check_glamping_exists(glamping_uuid: UUID):
glamping = next((glamping for glamping in glamping_accommodations if glamping.uuid == glamping_uuid), None)
if not glamping:
raise HTTPException(status_code=404, detail="Глэмпинг не существует")
return {"detail": "Глэмпинг существует"}
if __name__ == "__main__":
uvicorn.run("glamping_service:app", host="0.0.0.0", port=8080, reload=True)

View File

@@ -0,0 +1,54 @@
events {
worker_connections 1024;
}
http {
upstream glamping_service {
server glamping_service:8080;
}
upstream reservation_service {
server reservation_service:8081;
}
server {
listen 80;
server_name localhost;
# Glamping service routes
location /glamping_service/ {
proxy_pass http://glamping_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;
}
# Reservation service routes
location /reservation_service/ {
proxy_pass http://reservation_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;
}
# Documentation routes
location /glamping_service/docs {
proxy_pass http://glamping_service/docs;
}
location /reservation_service/docs {
proxy_pass http://reservation_service/docs;
}
# OpenAPI routes
location /glamping_service/openapi.json {
proxy_pass http://glamping_service/openapi.json;
}
location /reservation_service/openapi.json {
proxy_pass http://reservation_service/openapi.json;
}
}
}

View File

@@ -0,0 +1,4 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
requests==2.31.0

View File

@@ -0,0 +1,12 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY reservation_service/reservation_service.py .
EXPOSE 8081
CMD ["python", "reservation_service.py"]

View File

@@ -0,0 +1,196 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from uuid import UUID, uuid4
from datetime import datetime, date
import requests
import uvicorn
app = FastAPI(
root_path="/reservation_service",
title="Сервис Бронирований",
description="Сервис для управления бронированиями глэмпингов",
version="1.0.0"
)
# URL для доступа к сервису глэмпингов
glampings_url = "http://glamping_service:8080/"
class Reservation(BaseModel):
uuid: UUID
client_full_name: str
client_email: str
client_phone: str
check_in_date: date
check_out_date: date
guests_count: int
status: str
total_price: float
glamping_uuid: UUID
created_at: datetime
class ReservationCreate(BaseModel):
client_full_name: str
client_email: str
client_phone: str
check_in_date: date
check_out_date: date
guests_count: int
glamping_uuid: UUID
class GlampingInfo(BaseModel):
title: str
type: str
price_per_night: float
capacity: int
location: str
reservations: List[Reservation] = [
Reservation(
uuid=UUID("8f036445-a5bd-401c-926e-840f9de795cd"),
client_full_name="Иванов Иван Иванович",
client_email="ivanov@example.com",
client_phone="+79161234567",
check_in_date=date(2024, 7, 15),
check_out_date=date(2024, 7, 20),
guests_count=2,
status="подтверждено",
total_price=1250.00,
glamping_uuid=UUID("89fa1e7a-7e88-445e-a4d8-6d4497ea8f19"),
created_at=datetime(2024, 6, 1, 10, 0, 0)
),
Reservation(
uuid=UUID("8740d660-b251-4272-8535-be7ec3748d4b"),
client_full_name="Петрова Мария Сергеевна",
client_email="petrova@example.com",
client_phone="+79169876543",
check_in_date=date(2024, 8, 1),
check_out_date=date(2024, 8, 7),
guests_count=4,
status="ожидает подтверждения",
total_price=1080.00,
glamping_uuid=UUID("0351ee11-f11b-4d83-b2c8-1075b0c357dc"),
created_at=datetime(2024, 6, 5, 14, 30, 0)
),
Reservation(
uuid=UUID("a1b2c3d4-e29b-41d4-a716-446655440001"),
client_full_name="Сидоров Алексей Петрович",
client_email="sidorov@example.com",
client_phone="+79165554433",
check_in_date=date(2024, 9, 10),
check_out_date=date(2024, 9, 15),
guests_count=3,
status="подтверждено",
total_price=750.00,
glamping_uuid=UUID("dfc17619-7690-47aa-ae8e-6a5068f8ddec"),
created_at=datetime(2024, 7, 20, 9, 15, 0)
),
]
@app.get("/", response_model=List[Reservation], summary="Получить все бронирования", description="Получить список всех бронирований.")
def get_all_reservations():
return reservations
@app.get("/full", summary="Получить все бронирования с информацией о глэмпингах", description="Получить все бронирования с дополнительной информацией о глэмпингах.")
def get_all_reservations_with_glamping_info():
glampings = requests.get(glampings_url).json()
response = []
for reservation in reservations:
glamping_info = next((g for g in glampings if g["uuid"] == str(reservation.glamping_uuid)), None)
if glamping_info:
response.append({
**reservation.dict(),
"glamping_info": {
"title": glamping_info["title"],
"type": glamping_info["type"],
"price_per_night": glamping_info["price_per_night"],
"capacity": glamping_info["capacity"],
"location": glamping_info["location"]
}
})
return response
@app.get("/by-glamping/{glamping_uuid}", response_model=List[Reservation], summary="Получить бронирования по глэмпингу", description="Получить все бронирования для указанного глэмпинга.")
def get_reservations_by_glamping(glamping_uuid: UUID):
return [reservation for reservation in reservations if reservation.glamping_uuid == glamping_uuid]
@app.get("/{reservation_uuid}", response_model=Reservation, summary="Получить бронирование по ID", description="Получить одно бронирование по его ID.")
def get_reservation_by_id(reservation_uuid: UUID):
reservation = next((reservation for reservation in reservations if reservation.uuid == reservation_uuid), None)
if not reservation:
raise HTTPException(status_code=404, detail="Бронирование не найдено")
return reservation
@app.get("/full/{reservation_uuid}", summary="Получить бронирование с информацией о глэмпинге", description="Получить бронирование с дополнительной информацией о глэмпинге.")
def get_reservation_with_glamping_info(reservation_uuid: UUID):
reservation = next((reservation for reservation in reservations if reservation.uuid == reservation_uuid), None)
if not reservation:
raise HTTPException(status_code=404, detail="Бронирование не найдено")
# Синхронный обмен с glamping-service
glamping_info = requests.get(f"{glampings_url}{reservation.glamping_uuid}").json()
return {
**reservation.dict(),
"glamping_info": {
"title": glamping_info["title"],
"type": glamping_info["type"],
"price_per_night": glamping_info["price_per_night"],
"capacity": glamping_info["capacity"],
"location": glamping_info["location"]
}
}
@app.post("/", response_model=Reservation, summary="Создать новое бронирование", description="Добавить новое бронирование в базу данных.")
def create_reservation(reservation: ReservationCreate):
# Проверяем существование глэмпинга
response = requests.get(f"{glampings_url}check/{reservation.glamping_uuid}")
if response.status_code == 404:
raise HTTPException(status_code=404, detail="Размещение в глэмпинге не найдено")
# Получаем информацию о глэмпинге для расчета цены
glamping_info = requests.get(f"{glampings_url}{reservation.glamping_uuid}").json()
# Рассчитываем общую стоимость
nights = (reservation.check_out_date - reservation.check_in_date).days
total_price = glamping_info["price_per_night"] * nights
# Создаем новое бронирование
new_reservation = Reservation(
uuid=uuid4(),
status="ожидает подтверждения",
total_price=total_price,
created_at=datetime.now(),
**reservation.dict()
)
reservations.append(new_reservation)
return new_reservation
@app.put("/{reservation_uuid}", response_model=Reservation, summary="Обновить бронирование", description="Обновить существующее бронирование по его ID.")
def update_reservation(reservation_uuid: UUID, reservation_update: ReservationCreate):
reservation = next((reservation for reservation in reservations if reservation.uuid == reservation_uuid), None)
if not reservation:
raise HTTPException(status_code=404, detail="Бронирование не найдено")
reservation.client_full_name = reservation_update.client_full_name
reservation.client_email = reservation_update.client_email
reservation.client_phone = reservation_update.client_phone
reservation.check_in_date = reservation_update.check_in_date
reservation.check_out_date = reservation_update.check_out_date
reservation.guests_count = reservation_update.guests_count
reservation.glamping_uuid = reservation_update.glamping_uuid
# Пересчитываем цену
glamping_info = requests.get(f"{glampings_url}{reservation.glamping_uuid}").json()
nights = (reservation.check_out_date - reservation.check_in_date).days
reservation.total_price = glamping_info["price_per_night"] * nights
return reservation
@app.delete("/{reservation_uuid}", summary="Удалить бронирование", description="Удалить бронирование по его ID.")
def delete_reservation(reservation_uuid: UUID):
global reservations
reservations = [reservation for reservation in reservations if reservation.uuid != reservation_uuid]
return {"detail": "Бронирование успешно удалено"}
if __name__ == "__main__":
uvicorn.run("reservation_service:app", host="0.0.0.0", port=8081, reload=True)