forked from Alexey/DAS_2024_1
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:
commit
55e18b6a64
64
vaksman_valeria_lab_3/README.md
Normal file
64
vaksman_valeria_lab_3/README.md
Normal 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
|
24
vaksman_valeria_lab_3/Test_commands.txt
Normal file
24
vaksman_valeria_lab_3/Test_commands.txt
Normal 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
|
27
vaksman_valeria_lab_3/docker-compose.yaml
Normal file
27
vaksman_valeria_lab_3/docker-compose.yaml
Normal 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
|
17
vaksman_valeria_lab_3/document_server/Dockerfile
Normal file
17
vaksman_valeria_lab_3/document_server/Dockerfile
Normal 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"]
|
115
vaksman_valeria_lab_3/document_server/main.py
Normal file
115
vaksman_valeria_lab_3/document_server/main.py
Normal 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)
|
25
vaksman_valeria_lab_3/nginx.conf
Normal file
25
vaksman_valeria_lab_3/nginx.conf
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
vaksman_valeria_lab_3/product_server/Dockerfile
Normal file
17
vaksman_valeria_lab_3/product_server/Dockerfile
Normal 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"]
|
136
vaksman_valeria_lab_3/product_server/main.py
Normal file
136
vaksman_valeria_lab_3/product_server/main.py
Normal 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)
|
17
vaksman_valeria_lab_3/requirements.txt
Normal file
17
vaksman_valeria_lab_3/requirements.txt
Normal 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
|
Loading…
Reference in New Issue
Block a user