Merge pull request 'vaksman_valeria_lab_3' (#60) from vaksman_valeria_lab_3 into main

Reviewed-on: Alexey/DAS_2024_1#60
This commit is contained in:
Alexey 2024-10-16 16:47:28 +04:00
commit 55e18b6a64
9 changed files with 442 additions and 0 deletions

View File

@ -0,0 +1,64 @@
# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами
## Задание
#### Цель:
Изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
#### Задачи:
* Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
* Реализовать механизм синхронного обмена сообщениями между микросервисами.
* Реализовать шлюз на основе прозрачного прокси-сервера nginx.
### Сервисы:
 1. ```product_server``` - сервис, отвечающий за продукты
 2. ```document_server``` - сервис, отвечающий за документы, которые содержат в себе продукты
#### Связь между сервисами:
```document (single) <- product (many)```
## Как запустить программу:
```
docker compose up
```
## Файловая иерархия
```
Лаба 3/
|-- document_server/
| |-- Dockerfile
| |-- document_server.py
|-- product_server/
| |-- Dockerfile
| |-- product_server.py
|-- nginx.conf
|-- docker-compose.yml
|-- requirements.txt
|-- Test_commands.txt
```
## Описание работы:
Для разработки приложений был выбран язык программирования - ```python```
#### Синхронный обмен сообщениями
`product_server` будет отправлять http-запрос на `document_server` при определенных crud операциях.
### Docker-compose
Конфигурационный файл ```docker-сompose``` представляет собой многоконтейнерное приложение с тремя сервисами: ```product_server```, ```document_server``` и ```nginx```. Обязанности маршрутизатора возложены на сервер Nginx
### Nginx
Этот файл представляет собой конфигурацию для ```Nginx```, который является веб-сервером и обратным прокси.
# ВК
https://vk.com/video256017065_456239873

View File

@ -0,0 +1,24 @@
http://document_server:8008/documents/all/ - get
http://document_server:8008/documents/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0 - get
http://document_server:8008/documents/full/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0 - get
http://document_server:8008/documents/ - post
title content manager
http://document_server:8008/ - /documents/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0 title ПРОВЕРКА manager ПОЛЬЗОВАТЕЛЬ МЕНЕДЖЕР
http://document_server:8008documents/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0
http://product_server:8009/products/all/ - get
http://product_server:8009/products/085a7833-e486-4a55-9289-d253e21fe64e - get
http://product_server:8009/products/full/085a7833-e486-4a55-9289-d253e21fe64e - get
http://product_server:8009/products/list/documents/4a3f30c3-823f-4278-8f98-76177ad083bd - get
http://product_server:8009/products/ - post
name price uuid_document
http://product_server:8009/products/085a7833-e486-4a55-9289-d253e21fe64e - put
http://product_server:8009/products/085a7833-e486-4a55-9289-d253e21fe64e - delete

View File

@ -0,0 +1,27 @@
services:
document_server:
container_name: document_server
build:
context: .
dockerfile: ./document_server/Dockerfile
expose:
- 8008
product_server:
container_name: product_server
build:
context: .
dockerfile: ./product_server/Dockerfile
expose:
- 8009
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- document_server
- product_server

View File

@ -0,0 +1,17 @@
# Использую базовый образ Python
FROM python:3.10-slim
# Устанавливаю рабочую директорию внутри контейнера
WORKDIR /app
# Копирую файл requirements.txt в контейнер
COPY requirements.txt .
# Устанавливаю зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копирую исполняемый файл
COPY document_server/main.py .
# Команда для запуска Python-скрипта
CMD ["python", "main.py"]

View File

@ -0,0 +1,115 @@
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
import requests
app = FastAPI(title="Document service")
product_url = 'http://product_server:8009'
# Добавили автогенерацию уникального идентификатора
class Document(BaseModel):
uuid_: UUID = Field(default_factory=uuid4)
title: str | None
content: str | None
manager: str | None
# Наше хранилище данных
# Пару id указываем явно, остальные генерируются случайно
DATA_LIST: list[Document] = [
Document(uuid_="4a3f30c3-823f-4278-8f98-76177ad083bd", title="Document 1", content="Content 1", manager="Manager 1"),
Document(uuid_="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0", title="Document 2", content="Content 2", manager="Manager 2"),
Document(title="Document 3", content="Content 3", manager="Manager 3"),
Document(title="Document 4", content="Content 4", manager="Manager 4"),
]
@app.get("/")
async def redirect_to_docs():
return RedirectResponse(url="/docs")
@app.get("/documents/all/", tags=["Document"])
def all_documents():
return DATA_LIST
@app.get("/documents/{document_id}", tags=["Document"])
def get_document(document_id: UUID):
document = next((x for x in DATA_LIST if x.uuid_ == document_id), None)
if not document:
return HTTPException(status_code=404, detail="Документ не найден")
return document
@app.get("/documents/full/{document_id}", tags=["Document"])
def get_full_document(document_id: UUID):
document = next((x for x in DATA_LIST if x.uuid_ == document_id), None)
if not document:
return HTTPException(status_code=404, detail="Продукт не найден")
result_dict = document.model_dump()
if type(result_dict['uuid_']) is UUID:
products = requests.get(f"{product_url}/products/list/documents/{document_id}")
if not products:
return HTTPException(status_code=404, detail="Продукты не найдены")
result_dict["products"] = products.json()
return result_dict
@app.post("/documents/", tags=["Document"])
def create_document(title: str, content: str, manager: str):
document = Document(title=title, content=content, manager=manager)
try:
DATA_LIST.append(document)
return JSONResponse(content={"message": "Успешное создание документа"}, status_code=200)
except Exception as e:
return HTTPException(status_code=404, detail={"Неудачная попытка добавления документа": str(e)})
@app.put("/documents/{document_id}", tags=["Document"])
def update_document(document_id: UUID, title: str = None, content: str = None, manager: str = None):
document = get_document(document_id)
if type(document) is Document:
index = DATA_LIST.index(document)
if title:
DATA_LIST[index].title = title
if content:
DATA_LIST[index].content = content
if manager:
DATA_LIST[index].manager = manager
return JSONResponse(content={"message": "Успешное обновление документа"}, status_code=201)
else:
return HTTPException(status_code=404, detail=document)
@app.delete("/documents/{document_id}", tags=["Document"])
def delete_document(document_id: UUID):
document = get_document(document_id)
if type(document) is Document:
index = DATA_LIST.index(document)
print(index)
del DATA_LIST[index]
return JSONResponse(content={"message": "Успешное удаление документа"}, status_code=201)
return HTTPException(status_code=404, detail=document)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8008)

View File

@ -0,0 +1,25 @@
events { worker_connections 1024; }
http {
server {
listen 80;
listen [::]:80;
server_name localhost;
location /document_server/ {
proxy_pass http://document_server:8008/;
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;
}
location /product_server/ {
proxy_pass http://product_server:8009/;
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;
}
}
}

View File

@ -0,0 +1,17 @@
# Использую базовый образ Python
FROM python:3.10-slim
# Устанавливаю рабочую директорию внутри контейнера
WORKDIR /app
# Копирую файл requirements.txt в контейнер
COPY requirements.txt .
# Устанавливаю зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копирую исполняемый файл
COPY product_server/main.py .
# Команда для запуска Python-скрипта
CMD ["python", "main.py"]

View File

@ -0,0 +1,136 @@
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
import requests
app = FastAPI(title="Product service")
document_url = 'http://document_server:8008'
# Добавили автогенерацию уникального идентификатора
class Product(BaseModel):
uuid_: UUID = Field(default_factory=uuid4)
name: str | None
price: int | None
uuid_document: UUID | None
# Наше хранилище данных
DATA_LIST: list[Product] = [
Product(uuid_="085a7833-e486-4a55-9289-d253e21fe64e",name="Товар 1", price=100, uuid_document="4a3f30c3-823f-4278-8f98-76177ad083bd"),
Product(name="Товар 2", price=200, uuid_document="4a3f30c3-823f-4278-8f98-76177ad083bd"),
Product(name="Товар 3", price=300, uuid_document="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0"),
Product(name="Товар 4", price=400, uuid_document="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0"),
Product(name="Товар 5", price=500, uuid_document="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0"),
]
@app.get("/")
async def redirect_to_docs():
return RedirectResponse(url="/docs")
@app.get("/products/all/", tags=["Product"])
def all_products():
return DATA_LIST
@app.get("/products/{product_id}", tags=["Product"])
def get_product(product_id: UUID):
product = next((x for x in DATA_LIST if x.uuid_ == product_id), None)
if not product:
return HTTPException(status_code=404, detail="Продукт не найден")
return product
@app.get("/products/full/{product_id}", tags=["Product"])
def get_full_product(product_id: UUID):
product = next((x for x in DATA_LIST if x.uuid_ == product_id), None)
if not product:
return HTTPException(status_code=404, detail="Продукт не найден")
result_dict = product.model_dump()
if type(result_dict['uuid_document']) is UUID:
document = requests.get(f"{document_url}/documents/{result_dict['uuid_document']}")
if not document:
return HTTPException(status_code=404, detail="Документ не найден")
result_dict["document"] = document.json()
del result_dict['uuid_document']
return result_dict
@app.get("/products/list/documents/{document_id}", tags=["Product"])
def get_list_product_document(document_id: UUID):
products = (x for x in DATA_LIST if x.uuid_document == document_id)
if not products:
return HTTPException(status_code=404, detail="Продукты не найдены")
return products
@app.get("/products/{product_id}", tags=["Product"])
def get_product(product_id: UUID):
product = next((x for x in DATA_LIST if x.uuid_ == product_id), None)
if not product:
return HTTPException(status_code=404, detail="Продукт не найден")
return product
@app.post("/products/", tags=["Product"])
def create_product(name: str, price: int, uuid_document: UUID):
product = Product(name=name, price=price, uuid_document=uuid_document)
try:
DATA_LIST.append(product)
return JSONResponse(content={"message": "Успешное создание продукта"}, status_code=200)
except Exception as e:
return HTTPException(status_code=404, detail={"Неудачная попытка добавления продукта": str(e)})
@app.put("/products/{product_id}", tags=["Product"])
def update_product(product_id: UUID, name: str = None, price: int = None, uuid_document: UUID = None):
product = get_product(product_id)
if type(product) is Product:
index = DATA_LIST.index(product)
if name:
DATA_LIST[index].name = name
if price:
DATA_LIST[index].price = price
if uuid_document:
DATA_LIST[index].uuid_document = uuid_document
return JSONResponse(content={"message": "Успешное обновление продукта"}, status_code=201)
else:
return HTTPException(status_code=404, detail=product)
@app.delete("/products/{product_id}", tags=["Product"])
def delete_product(product_id: UUID):
product = get_product(product_id)
if type(product) is Product:
index = DATA_LIST.index(product)
print(index)
del DATA_LIST[index]
return JSONResponse(content={"message": "Успешное удаление продукта"}, status_code=201)
return HTTPException(status_code=404, detail=product)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8009)

View File

@ -0,0 +1,17 @@
annotated-types==0.7.0
anyio==4.6.0
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
exceptiongroup==1.2.2
fastapi==0.115.0
h11==0.14.0
idna==3.10
pydantic==2.9.2
pydantic_core==2.23.4
requests==2.32.3
sniffio==1.3.1
starlette==0.38.6
typing_extensions==4.12.2
urllib3==2.2.3
uvicorn==0.31.0