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:
21
yakovlev_maxim_lab_3/.gitignore
vendored
Normal file
21
yakovlev_maxim_lab_3/.gitignore
vendored
Normal 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
|
||||
57
yakovlev_maxim_lab_3/README.md
Normal file
57
yakovlev_maxim_lab_3/README.md
Normal 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)
|
||||
10
yakovlev_maxim_lab_3/departments-service/Dockerfile
Normal file
10
yakovlev_maxim_lab_3/departments-service/Dockerfile
Normal 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"]
|
||||
79
yakovlev_maxim_lab_3/departments-service/app.py
Normal file
79
yakovlev_maxim_lab_3/departments-service/app.py
Normal 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("")
|
||||
@@ -0,0 +1,4 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
requests==2.31.0
|
||||
pydantic==2.5.0
|
||||
30
yakovlev_maxim_lab_3/docker-compose.yml
Normal file
30
yakovlev_maxim_lab_3/docker-compose.yml
Normal 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
|
||||
10
yakovlev_maxim_lab_3/employees-service/Dockerfile
Normal file
10
yakovlev_maxim_lab_3/employees-service/Dockerfile
Normal 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"]
|
||||
127
yakovlev_maxim_lab_3/employees-service/app.py
Normal file
127
yakovlev_maxim_lab_3/employees-service/app.py
Normal 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("")
|
||||
4
yakovlev_maxim_lab_3/employees-service/requirements.txt
Normal file
4
yakovlev_maxim_lab_3/employees-service/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
requests==2.31.0
|
||||
pydantic==2.5.0
|
||||
41
yakovlev_maxim_lab_3/nginx/nginx.conf
Normal file
41
yakovlev_maxim_lab_3/nginx/nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user