Merge pull request 'yakovlev_maxim_lab_3_is_ready' (#416) from yakovlev_maxim_lab_3 into main

Reviewed-on: #416
This commit was merged in pull request #416.
This commit is contained in:
2025-12-08 22:56:53 +04:00
10 changed files with 383 additions and 0 deletions

21
yakovlev_maxim_lab_3/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Virtual environment
venv/
venv*/
env/
env*/
.venv/
# Python cache
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,57 @@
# Лабораторная работа №3
### Цель
Изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
### Задачи:
1. Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
2. Реализовать механизм синхронного обмена сообщениями между микросервисами.
3. Реализовать шлюз на основе прозрачного прокси-сервера nginx.
### Используемые технологии
- Python 3.10 - язык программирования
- FastAPI - фреймворк для создания API
- Docker - контейнеризация приложений
- Docker Compose - оркестрация контейнеров
- Nginx - API Gateway
- Uvicorn - ASGI сервер
### Архитектура проекта
Проект реализует систему учета сотрудников компании. Система состоит из трех основных компонентов:
- Department Service - микросервис для управления отделами
- Employee Service - микросервис для управления сотрудниками
- Nginx Gateway - API для маршрутизации запросов
### Сущности
Department (Отдел)
- uuid
- name - название отдела
- manager - ФИО руководителя
Employee (Сотрудник)
- uuid
- fullName - ФИО
- position - Должность
- hireDate - дата найма
- departmentUuid - ссылка на отдел
## Запуск проекта
1. Запуск сервисов командой: `docker-compose up --build`
2. После запуска, в поисковой строке введите:
- http://localhost/departments/docs - для работы с отделами
- http://localhost/employees/docs - для работы с пользователями
## Описание работы
Для работы с сущностью переходим по нужной нам ссылке приведенной выше.
### Просмотр:
Отделы - http://localhost/departments/departments
Сотрудники - http://localhost/employees/employees
Сотрудники с информацией по отделу - http://localhost/employees/employees/{department_uuid} (department_uuid - заменить на uuid существующего отдела)
### Создание, обновление, удаление:
Отделы - http://localhost/departments/docs
Сотрудники - http://localhost/employees/docs
В зависимости от нужно сущночти выбираем ссылку и используем нужный post, put, delete метод c uuid нужного объекта
[Ссылка на видео](https://vkvideo.ru/video-233039857_456239019?list=ln-2855tWzgpcmvyuoQET)

View File

@@ -0,0 +1,10 @@
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,79 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
import uuid
from datetime import datetime
from enum import Enum
app = FastAPI(
title="Departments Service",
version="1.0.0",
root_path="/departments"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
departments_db = {}
class DepartmentCreate(BaseModel):
name : str
manager : str
class DepartmentResponse(BaseModel):
uuid: str
name: str
manager : str
@app.get("/departments", response_model=List[DepartmentResponse])
def get_departments():
return list(departments_db.values())
@app.get("/departments/{department_uuid}", response_model=DepartmentResponse)
def get_department(department_uuid: str):
if department_uuid not in departments_db:
raise HTTPException(status_code=404, detail="Department not found")
return departments_db[department_uuid]
@app.post("/departments", response_model=DepartmentResponse)
def create_department(department: DepartmentCreate):
department_uuid = str(uuid.uuid4())
department_data = DepartmentResponse(
uuid=department_uuid,
name=department.name,
manager=department.manager
)
departments_db[department_uuid] = department_data.dict()
return department_data
@app.put("/departments/{department_uuid}", response_model=DepartmentResponse)
def update_department(department_uuid: str, department: DepartmentCreate):
if department_uuid not in departments_db:
raise HTTPException(status_code=404, detail="Department not found")
departments_db[department_uuid].update({
"name": department.name,
"manager": department.manager
})
return departments_db[department_uuid]
@app.delete("departments/{department_uuid}")
def delete_department(department_uuid: str):
if department_uuid not in departments_db:
raise HTTPException(status_code=404, detail="Department not found")
del departments_db[department_uuid]
return {"message": "Department deleted successfully"}
@app.on_event("startup")
async def startup_event():
print("Department Service started!")
print("Swagger UI: http://localhost/courses/docs")
print("JSON API: http://localhost/courses/courses")
print("")

View File

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

View File

@@ -0,0 +1,30 @@
services:
departments-service:
build: ./departments-service
ports:
- "8000:8000"
networks:
- app-network
employees-service:
build: ./employees-service
ports:
- "8001:8000"
networks:
- app-network
nginx-gateway:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- departments-service
- employees-service
networks:
- app-network
networks:
app-network:
driver: bridge

View File

@@ -0,0 +1,10 @@
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,127 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
import uuid
import requests
from datetime import datetime
from enum import Enum
app = FastAPI(
title="Employees Service",
version="1.0.0",
root_path="/employees" # для nginx
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class Position(str, Enum):
PROGRAMMER = "programmer"
MANAGER = "manager"
DESIGNER = "designer"
DIRECTOR = "director"
employees_db = {}
class EmployeeCreate(BaseModel):
fullName: str
position: Position
department_uuid: str
class EmployeeResponse(BaseModel):
uuid: str
fullName: str
position: Position
department_uuid: str
hireDate: datetime
class EmployeeDetailResponse(EmployeeResponse):
department_info: Optional[Dict[str, Any]] = None
def get_department_info(department_uuid: str):
try:
response = requests.get(f"http://departments-service:8000/departments/{department_uuid}")
if response.status_code == 200:
return response.json()
return None
except Exception as e:
print(f"Error fetching department info: {e}")
return None
@app.get("/employees", response_model=List[EmployeeResponse])
def get_employees():
return list(employees_db.values())
@app.get("/employees/{employee_uuid}", response_model=EmployeeDetailResponse)
def get_employee(employee_uuid: str):
if employee_uuid not in employees_db:
raise HTTPException(status_code=404, detail="Employee not found")
employee = employees_db[employee_uuid].copy()
department_info = get_department_info(employee["department_uuid"])
employee["department_info"] = department_info
return employee
@app.post("/employees", response_model=EmployeeResponse)
def create_employee(employee: EmployeeCreate):
department_info = get_department_info(employee.department_uuid)
if not department_info:
raise HTTPException(status_code=400, detail="Department not found")
employee_uuid = str(uuid.uuid4())
employee_data = EmployeeResponse(
uuid=employee_uuid,
fullName=employee.fullName,
position=employee.position,
department_uuid=employee.department_uuid,
hireDate=datetime.now()
)
employees_db[employee_uuid] = employee_data.dict()
return employee_data
@app.put("/employees/{employee_uuid}", response_model=EmployeeResponse)
def update_employee(employee_uuid: str, employee: EmployeeCreate):
if employee_uuid not in employees_db:
raise HTTPException(status_code=404, detail="Employee not found")
department_info = get_department_info(employee.department_uuid)
if not department_info:
raise HTTPException(status_code=400, detail="Department not found")
employees_db[employee_uuid].update({
"fullname": employee.fullName,
"position": employee.position,
"department_uuid": employee.department_uuid
})
return employees_db[employee_uuid]
@app.delete("/employees/{employee_uuid}")
def delete_employee(employee_uuid: str):
if employee_uuid not in employees_db:
raise HTTPException(status_code=404, detail="Employee not found")
del employees_db[employee_uuid]
return {"message": "Employee deleted successfully"}
@app.get("/departments/{department_uuid}/employees", response_model=List[EmployeeResponse])
def get_department_employees(department_uuid: str):
department_employees = [employee for employee in employees_db.values() if employee["department_uuid"] == department_uuid]
return department_employees
@app.on_event("startup")
async def startup_event():
print("Tasks Service started!")
print("Swagger UI: http://localhost/tasks/docs")
print("JSON API: http://localhost/tasks/tasks")
print("")

View File

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

View File

@@ -0,0 +1,41 @@
events {
worker_connections 1024;
}
http {
upstream departments_service {
server departments-service:8000;
}
upstream employees_service {
server employees-service:8000;
}
server {
listen 80;
server_name localhost;
location /departments/ {
proxy_pass http://departments_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;
proxy_set_header X-Forwarded-Prefix /departments;
}
location /employees/ {
proxy_pass http://employees_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;
proxy_set_header X-Forwarded-Prefix /employees;
}
# Root redirect to courses docs
location / {
return 302 /departments/docs;
}
}
}