tabeev_alexander_lab_3 is ready
This commit is contained in:
33
tabeev_alexander_lab_3/docker-compose.yml
Normal file
33
tabeev_alexander_lab_3/docker-compose.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
researchers:
|
||||
build: ./researchers-service
|
||||
container_name: researchers_service
|
||||
ports:
|
||||
- "5001:5000"
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
publications:
|
||||
build: ./publications-service
|
||||
container_name: publications_service
|
||||
ports:
|
||||
- "5002:5000"
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
gateway:
|
||||
image: nginx:alpine
|
||||
container_name: api_gateway
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- researchers
|
||||
- publications
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
34
tabeev_alexander_lab_3/nginx/nginx.conf
Normal file
34
tabeev_alexander_lab_3/nginx/nginx.conf
Normal file
@@ -0,0 +1,34 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
upstream researchers {
|
||||
server researchers:5000;
|
||||
}
|
||||
|
||||
upstream publications {
|
||||
server publications:5000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location /researchers {
|
||||
rewrite ^/researchers/(.*)$ /$1 break;
|
||||
proxy_pass http://researchers;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /publications {
|
||||
rewrite ^/publications/(.*)$ /$1 break;
|
||||
proxy_pass http://publications;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 200 "API Gateway\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
tabeev_alexander_lab_3/publications-service/Dockerfile
Normal file
10
tabeev_alexander_lab_3/publications-service/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM python:3.9-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", "5000"]
|
||||
91
tabeev_alexander_lab_3/publications-service/app.py
Normal file
91
tabeev_alexander_lab_3/publications-service/app.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
app = FastAPI(title="Publications Service")
|
||||
|
||||
publications_db = []
|
||||
|
||||
class PublicationCreate(BaseModel):
|
||||
title: str
|
||||
type: str
|
||||
year: int
|
||||
journal: Optional[str] = None
|
||||
researcherUuid: str
|
||||
|
||||
class ResearcherInfo(BaseModel):
|
||||
fullName: str
|
||||
position: str
|
||||
department: str
|
||||
hIndex: int
|
||||
|
||||
class PublicationResponse(BaseModel):
|
||||
uuid: str
|
||||
title: str
|
||||
type: str
|
||||
year: int
|
||||
journal: Optional[str] = None
|
||||
researcherUuid: str
|
||||
|
||||
class PublicationDetailResponse(PublicationResponse):
|
||||
researcherInfo: Optional[ResearcherInfo] = None
|
||||
|
||||
def get_researcher_info(researcher_uuid: str) -> Optional[ResearcherInfo]:
|
||||
try:
|
||||
response = requests.get(f"http://researchers:5000/researchers/{researcher_uuid}")
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
return None
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
|
||||
@app.get("/publications/", response_model=List[PublicationResponse])
|
||||
def get_publications():
|
||||
return publications_db
|
||||
|
||||
@app.get("/publications/{publication_uuid}", response_model=PublicationDetailResponse)
|
||||
def get_publication(publication_uuid: str):
|
||||
for publication in publications_db:
|
||||
if publication["uuid"] == publication_uuid:
|
||||
researcher_info = get_researcher_info(publication["researcherUuid"])
|
||||
publication_detail = publication.copy()
|
||||
publication_detail["researcherInfo"] = researcher_info
|
||||
return publication_detail
|
||||
raise HTTPException(status_code=404, detail="Publication not found")
|
||||
|
||||
@app.post("/publications/", response_model=PublicationResponse)
|
||||
def create_publication(publication: PublicationCreate):
|
||||
researcher_info = get_researcher_info(publication.researcherUuid)
|
||||
if not researcher_info:
|
||||
raise HTTPException(status_code=400, detail="Researcher not found")
|
||||
|
||||
publication_data = publication.dict()
|
||||
publication_data["uuid"] = str(uuid.uuid4())
|
||||
publications_db.append(publication_data)
|
||||
return publication_data
|
||||
|
||||
@app.put("/publications/{publication_uuid}", response_model=PublicationResponse)
|
||||
def update_publication(publication_uuid: str, publication_update: PublicationCreate):
|
||||
researcher_info = get_researcher_info(publication_update.researcherUuid)
|
||||
if not researcher_info:
|
||||
raise HTTPException(status_code=400, detail="Researcher not found")
|
||||
|
||||
for publication in publications_db:
|
||||
if publication["uuid"] == publication_uuid:
|
||||
publication.update(publication_update.dict())
|
||||
return publication
|
||||
raise HTTPException(status_code=404, detail="Publication not found")
|
||||
|
||||
@app.delete("/publications/{publication_uuid}")
|
||||
def delete_publication(publication_uuid: str):
|
||||
for i, publication in enumerate(publications_db):
|
||||
if publication["uuid"] == publication_uuid:
|
||||
publications_db.pop(i)
|
||||
return {"message": "Publication deleted successfully"}
|
||||
raise HTTPException(status_code=404, detail="Publication not found")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
||||
@@ -0,0 +1,4 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
pydantic==2.5.0
|
||||
requests==2.31.0
|
||||
45
tabeev_alexander_lab_3/readme.md
Normal file
45
tabeev_alexander_lab_3/readme.md
Normal file
@@ -0,0 +1,45 @@
|
||||
## Цель ЛР: изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
|
||||
|
||||
## Задачи:
|
||||
|
||||
1. Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
|
||||
2. Реализовать механизм синхронного обмена сообщениями между микросервисами.
|
||||
3. Реализовать шлюз на основе прозрачного прокси-сервера nginx.
|
||||
|
||||
## Предметная область
|
||||
Тема диплома звучит так: Сбор и аналитика по научной активности : формирование анкеты по сотрудникам, подбор конференций, журналов, соавторов, грантов.
|
||||
Исходя из темы добавил две сущности: учёные и научные публикации; связь у них один ко многим: у одного ученого может быть много публикаций и каждая публикация принадлежит только одному ученому(не знаю есть ли у ученых какая-то совместная публикация, но у меня вот так)
|
||||
|
||||
## Как запустить ЛР
|
||||
1. Предварительно должны быть установлены Docker и Docker Compose и свободные порты
|
||||
2. Перейти в директорию с файлом docker-compose.yml
|
||||
3. Запустить сервисы с помощью команды docker-compose up --build / down - для отключения
|
||||
4. Убедиться что в докере везде статус Running
|
||||
5. Перейтии по адресу http://localhost:5001/docs#/ (ученые) и http://localhost:5002/docs#/ (научные публикации)
|
||||
|
||||
## Основные технологии
|
||||
1. FastAPI - веб-фреймворк для создания REST API на Python
|
||||
2. Docker - контейнеризация микросервисов
|
||||
3. Docker Compose - сбор контейнеров
|
||||
4. NGINX - веб-сервер и API Gateway
|
||||
5. Python 3.9 - ЯП для реализации логики
|
||||
|
||||
### Что делает ЛР
|
||||
Есть два микросервиса:
|
||||
1. микросервис Researchers Service - сущность ученые с методами 2 get(один для получения всего списка ученых, а второй для получения конкретного ученого по uuid), post, put, delete
|
||||
2. микросервис Publications Service - суущность научные публикации с такими же метордами как у ученых, но есть привязка публикации к ученому, указав его uuid
|
||||
|
||||
## Сценарий
|
||||
1. Запрашиваем информацию о научной публикации, указав uuid публикации
|
||||
2. Publications Service делает http-запрос к Reserchers Service
|
||||
3. Получает информацию об авторе публикации
|
||||
4. Объединяет данные и возвращает полный ответ
|
||||
|
||||
## Вывод
|
||||
В ходе ЛР успешно разработана микросервисная архитектура, состоящая из двух взаимосвязанных сервисов. Реализовано:
|
||||
1. Полный crud для ученых и научных публикаций
|
||||
2. Синхронный http-обмен между сервисаами
|
||||
3. API Gateway на основе nginx для централизованного управдления запросами
|
||||
4. Контейнеризация с помощью докера
|
||||
|
||||
## Ссылка на видео: https://vkvideo.ru/video-234192775_456239019?list=ln-epmNwqwISb1eF2lbtc
|
||||
10
tabeev_alexander_lab_3/researchers-service/Dockerfile
Normal file
10
tabeev_alexander_lab_3/researchers-service/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM python:3.9-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", "5000"]
|
||||
60
tabeev_alexander_lab_3/researchers-service/app.py
Normal file
60
tabeev_alexander_lab_3/researchers-service/app.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
app = FastAPI(title="Researchers Service")
|
||||
|
||||
researchers_db = []
|
||||
|
||||
class ResearcherCreate(BaseModel):
|
||||
fullName: str
|
||||
position: str
|
||||
department: str
|
||||
hIndex: int
|
||||
|
||||
class ResearcherResponse(BaseModel):
|
||||
uuid: str
|
||||
fullName: str
|
||||
position: str
|
||||
department: str
|
||||
hIndex: int
|
||||
|
||||
@app.get("/researchers/", response_model=List[ResearcherResponse])
|
||||
def get_researchers():
|
||||
return researchers_db
|
||||
|
||||
@app.get("/researchers/{researcher_uuid}", response_model=ResearcherResponse)
|
||||
def get_researcher(researcher_uuid: str):
|
||||
for researcher in researchers_db:
|
||||
if researcher["uuid"] == researcher_uuid:
|
||||
return researcher
|
||||
raise HTTPException(status_code=404, detail="Researcher not found")
|
||||
|
||||
@app.post("/researchers/", response_model=ResearcherResponse)
|
||||
def create_researcher(researcher: ResearcherCreate):
|
||||
researcher_data = researcher.dict()
|
||||
researcher_data["uuid"] = str(uuid.uuid4())
|
||||
researchers_db.append(researcher_data)
|
||||
return researcher_data
|
||||
|
||||
@app.put("/researchers/{researcher_uuid}", response_model=ResearcherResponse)
|
||||
def update_researcher(researcher_uuid: str, researcher_update: ResearcherCreate):
|
||||
for researcher in researchers_db:
|
||||
if researcher["uuid"] == researcher_uuid:
|
||||
researcher.update(researcher_update.dict())
|
||||
return researcher
|
||||
raise HTTPException(status_code=404, detail="Researcher not found")
|
||||
|
||||
@app.delete("/researchers/{researcher_uuid}")
|
||||
def delete_researcher(researcher_uuid: str):
|
||||
for i, researcher in enumerate(researchers_db):
|
||||
if researcher["uuid"] == researcher_uuid:
|
||||
researchers_db.pop(i)
|
||||
return {"message": "Researcher deleted successfully"}
|
||||
raise HTTPException(status_code=404, detail="Researcher not found")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
||||
@@ -0,0 +1,4 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
pydantic==2.5.0
|
||||
requests==2.31.0
|
||||
Reference in New Issue
Block a user