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