Merge pull request 'karamushko_maxim_lab_3' (#385) from karamushko_maxim_lab_3 into main
Reviewed-on: #385
This commit is contained in:
commit
de1aa91832
1
karamushko_maxim_lab_3/.gitignore
vendored
Normal file
1
karamushko_maxim_lab_3/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/.venv
|
35
karamushko_maxim_lab_3/README.md
Normal file
35
karamushko_maxim_lab_3/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами
|
||||
## ПИбд-42 || Карамушко Максим
|
||||
|
||||
### Описание:
|
||||
В данной лабораторной работе реализованы две сущности со связью "одик-ко-многим". Для доступа к кажой сущности реализован отдельный сервис.
|
||||
|
||||
Сервис film: CRUD операции. Помимо этого реализована операция получения фильмов с информацие о жанре, получение фильмов по жанру и получение одного фильма с информацией о жанре
|
||||
|
||||
Сервис genre: CRUD операции. Также реализована функция получения жанра со списком фильмов в этом жанре.
|
||||
|
||||
### Цель лабораторной работы
|
||||
изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API
|
||||
|
||||
### Выбранные сущности:
|
||||
- Жанр. Поля: Uuid, Name
|
||||
- Фильм. Поля: Uuid, Name, Description
|
||||
|
||||
### Инструкция для работы
|
||||
1. Клонирование репозитория:
|
||||
```
|
||||
git clone <ссылка-на-репозиторий>
|
||||
cd <папка репозитория>
|
||||
cd <папка лабораторной работы>
|
||||
```
|
||||
|
||||
2. Запуск контейнеров:
|
||||
```
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
3. Результаты:
|
||||
Можно применять описанные выше операции к сущностям через http запросы.
|
||||
|
||||
### Видео с демонстрацией работы:
|
||||
https://disk.yandex.ru/i/j7UxPcoU3lHx3Q
|
26
karamushko_maxim_lab_3/docker-compose.yaml
Normal file
26
karamushko_maxim_lab_3/docker-compose.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
services:
|
||||
genre:
|
||||
container_name: genre_service
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./genre/Dockerfile
|
||||
expose:
|
||||
- 8001
|
||||
|
||||
film:
|
||||
container_name: film_service
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./film/Dockerfile
|
||||
expose:
|
||||
- 8002
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- genre
|
||||
- film
|
14
karamushko_maxim_lab_3/film/Dockerfile
Normal file
14
karamushko_maxim_lab_3/film/Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
# базовый образ
|
||||
FROM python:3.12
|
||||
|
||||
# рабочая директория
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
# зависимости
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY film/service.py .
|
||||
|
||||
CMD ["python", "service.py"]
|
157
karamushko_maxim_lab_3/film/service.py
Normal file
157
karamushko_maxim_lab_3/film/service.py
Normal file
@ -0,0 +1,157 @@
|
||||
from flask import Flask, jsonify, request
|
||||
import requests
|
||||
from uuid import uuid4
|
||||
import uuid
|
||||
|
||||
|
||||
class Film:
|
||||
def __init__(self, name, description, uuid_: uuid, genre_id: uuid):
|
||||
if uuid_ is None:
|
||||
self.uuid_: uuid = uuid4()
|
||||
else:
|
||||
self.uuid_: uuid = uuid.UUID(uuid_)
|
||||
self.name: str = name
|
||||
self.description: str = description
|
||||
self.genre_id: uuid = uuid.UUID(genre_id)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'genre_id': self.genre_id,
|
||||
'uuid': self.uuid_
|
||||
}
|
||||
|
||||
def to_dict_for_genres(self):
|
||||
return {
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'uuid': self.uuid_
|
||||
}
|
||||
|
||||
def to_dict_with_info(self, genre: dict):
|
||||
return {
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'genre_id': self.genre_id,
|
||||
'genre_info': genre,
|
||||
'uuid': self.uuid_
|
||||
}
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
specialities: list[Film] = [
|
||||
Film(name='Пираты карибского моря', description='фильм про пиратов', uuid_='51e5c5c1-1e6e-4455-b4e1-aec5774a2961',
|
||||
genre_id='450a9c7c-fb6f-4a42-a354-acef65af4c9b'),
|
||||
Film(name='1+1', description='=2', uuid_='660e4da0-abe9-4d33-9070-f3e8f67484f4',
|
||||
genre_id='6f26879d-bdf2-4ccd-bd6f-23275c86a9ac'),
|
||||
Film(name='Во все тяжкие', description='познавательный сериал про химию', uuid_='801d599b-ce70-4755-a2ef-00e1c092811d',
|
||||
genre_id='139a4578-5922-4688-92e2-22e749ff8c47'),
|
||||
]
|
||||
|
||||
genres_url = 'http://genre_service:8001/'
|
||||
|
||||
|
||||
def list_jsonify():
|
||||
return jsonify([speciality.to_dict() for speciality in specialities])
|
||||
|
||||
|
||||
# получение всех фильмов
|
||||
@app.route('/', methods=['GET'])
|
||||
def get_all():
|
||||
return list_jsonify(), 200
|
||||
|
||||
|
||||
# получение всех фильмов с информацие о жанре
|
||||
@app.route('/full', methods=['GET'])
|
||||
def get_all_full():
|
||||
genres: list[dict] = requests.get(genres_url).json()
|
||||
response = []
|
||||
for speciality in specialities:
|
||||
for genre in genres:
|
||||
if speciality.genre_id == uuid.UUID(genre.get('uuid')):
|
||||
response.append(speciality.to_dict_with_info(genre))
|
||||
|
||||
return response, 200
|
||||
|
||||
|
||||
# получение фильмов по uuid жанра
|
||||
@app.route('/by-genre/<uuid:genre_uuid>', methods=['GET'])
|
||||
def get_by_genre_id(genre_uuid):
|
||||
return [speciality.to_dict_for_genres() for speciality in specialities if speciality.genre_id == genre_uuid], 200
|
||||
|
||||
|
||||
# получение фильма по uuid
|
||||
@app.route('/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one(uuid_):
|
||||
for speciality in specialities:
|
||||
if speciality.uuid_ == uuid_:
|
||||
return speciality.to_dict(), 200
|
||||
|
||||
return 'Специальность с указанным uuid не обнаружена', 404
|
||||
|
||||
|
||||
# получение фильма по uuid с информацие о жанре
|
||||
@app.route('/full/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one_full(uuid_):
|
||||
for speciality in specialities:
|
||||
if speciality.uuid_ == uuid_:
|
||||
response = requests.get(genres_url + str(speciality.genre_id))
|
||||
return speciality.to_dict_with_info(response.json()), 200
|
||||
|
||||
return 'Специальность с указанным uuid не обнаружена', 404
|
||||
|
||||
|
||||
# создание фильма
|
||||
@app.route('/', methods=['POST'])
|
||||
def create():
|
||||
data = request.json
|
||||
name = data.get('name', None)
|
||||
description = data.get('description', None)
|
||||
genre_id = data.get('genre_id', None)
|
||||
checking = requests.get(genres_url + f'/check/{genre_id}')
|
||||
print(checking)
|
||||
if checking.status_code == 200:
|
||||
new_speciality = Film(name, description, None, genre_id)
|
||||
specialities.append(new_speciality)
|
||||
return get_one(new_speciality.uuid_)
|
||||
if checking.status_code == 404:
|
||||
return 'Факультета с таким uuid не существует', 404
|
||||
|
||||
return 'Неизвестная ошибка', 500
|
||||
|
||||
|
||||
# изменение фильма по uuid
|
||||
@app.route('/<uuid:uuid_>', methods=['PUT'])
|
||||
def update_by_id(uuid_):
|
||||
data = request.json
|
||||
new_name = data.get('name', None)
|
||||
new_description = data.get('description', None)
|
||||
|
||||
for speciality in specialities:
|
||||
print(speciality.uuid_)
|
||||
|
||||
if speciality.uuid_ == uuid_:
|
||||
if new_name is not None:
|
||||
speciality.name = new_name
|
||||
if new_description is not None:
|
||||
speciality.description = new_description
|
||||
return get_one(speciality.uuid_)
|
||||
|
||||
return 'Специальность с указанным uuid не обнаружена', 404
|
||||
|
||||
|
||||
# удаление фильма по uuid
|
||||
@app.route('/<uuid:uuid_>', methods=['DELETE'])
|
||||
def delete(uuid_):
|
||||
for speciality in specialities:
|
||||
if speciality.uuid_ == uuid_:
|
||||
specialities.remove(speciality)
|
||||
return 'Специальность была удалена', 200
|
||||
|
||||
return 'Специальность с указанным uuid не обнаружена', 404
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8002, debug=True)
|
14
karamushko_maxim_lab_3/genre/Dockerfile
Normal file
14
karamushko_maxim_lab_3/genre/Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
# базовый образ
|
||||
FROM python:3.12
|
||||
|
||||
# рабочая директория
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
# зависимости
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY genre/service.py .
|
||||
|
||||
CMD ["python", "service.py"]
|
125
karamushko_maxim_lab_3/genre/service.py
Normal file
125
karamushko_maxim_lab_3/genre/service.py
Normal file
@ -0,0 +1,125 @@
|
||||
from flask import Flask, jsonify, request
|
||||
from uuid import uuid4
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
|
||||
class Genre:
|
||||
def __init__(self, name, uuid_: uuid):
|
||||
if uuid_ is None:
|
||||
self.uuid_: uuid = uuid4()
|
||||
else:
|
||||
self.uuid_: uuid = uuid.UUID(uuid_)
|
||||
self.name: str = name
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"uuid": self.uuid_,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
def to_dict_with_films(self, films: list):
|
||||
return {
|
||||
"uuid": self.uuid_,
|
||||
"name": self.name,
|
||||
"films": films
|
||||
}
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
genres: list[Genre] = [
|
||||
Genre(name='Комедия', uuid_='450a9c7c-fb6f-4a42-a354-acef65af4c9b'),
|
||||
Genre(name='Драма', uuid_='6f26879d-bdf2-4ccd-bd6f-23275c86a9ac'),
|
||||
Genre(name='Экшен', uuid_='139a4578-5922-4688-92e2-22e749ff8c47'),
|
||||
]
|
||||
|
||||
films_url = 'http://film_service:8002/'
|
||||
|
||||
|
||||
def list_jsonify():
|
||||
return jsonify([genre.to_dict() for genre in genres])
|
||||
|
||||
|
||||
# получение всех жанров
|
||||
@app.route('/', methods=['GET'])
|
||||
def get_all():
|
||||
return list_jsonify(), 200
|
||||
|
||||
|
||||
# получение жанра по uuid
|
||||
@app.route('/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one(uuid_):
|
||||
for genre in genres:
|
||||
if genre.uuid_ == uuid_:
|
||||
return genre.to_dict(), 200
|
||||
|
||||
return 'Факультет с указанным uuid не обнаружен', 404
|
||||
|
||||
|
||||
# получение жанра со списком фильмов в этом жанре
|
||||
@app.route('/with-films/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one_with_films(uuid_):
|
||||
for genre in genres:
|
||||
if genre.uuid_ == uuid_:
|
||||
response = requests.get(films_url + f'by-genre/{uuid_}')
|
||||
print(response.json())
|
||||
return genre.to_dict_with_films(response.json()), 200
|
||||
|
||||
return 'Факультет с указанным uuid не обнаружен', 404
|
||||
|
||||
|
||||
# проверка наличия жанра по uuid
|
||||
@app.route('/check/<uuid:uuid_>', methods=['GET'])
|
||||
def check_exist(uuid_):
|
||||
for genre in genres:
|
||||
if genre.uuid_ == uuid_:
|
||||
return '', 200
|
||||
return '', 404
|
||||
|
||||
|
||||
# создание жанра
|
||||
@app.route('/', methods=['POST'])
|
||||
def create():
|
||||
data = request.json
|
||||
name = data.get('name', None)
|
||||
description = data.get('description', None)
|
||||
if name is None or description is None:
|
||||
return 'Не хватает данных для создания нового факультета', 404
|
||||
|
||||
new_genre = Genre(name, description, None)
|
||||
genres.append(new_genre)
|
||||
return get_one(new_genre.uuid_)
|
||||
|
||||
|
||||
# изменения жанра по uuid
|
||||
@app.route('/<uuid:uuid_>', methods=['PUT'])
|
||||
def update_by_id(uuid_):
|
||||
data = request.json
|
||||
new_name = data.get('name', None)
|
||||
new_description = data.get('description', None)
|
||||
|
||||
for genre in genres:
|
||||
if genre.uuid_ == uuid_:
|
||||
if new_name is not None:
|
||||
genre.name = new_name
|
||||
if new_description is not None:
|
||||
genre.description = new_description
|
||||
return get_one(genre.uuid_)
|
||||
|
||||
return 'Факультет с указанным uuid не обнаружен', 404
|
||||
|
||||
|
||||
# удаление жанра по uuid
|
||||
@app.route('/<uuid:uuid_>', methods=['DELETE'])
|
||||
def delete(uuid_):
|
||||
for genre in genres:
|
||||
if genre.uuid_ == uuid_:
|
||||
genres.remove(genre)
|
||||
return 'Факультет был удален', 200
|
||||
|
||||
return 'Факультет с указанным uuid не обнаружен', 404
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8001, debug=True)
|
25
karamushko_maxim_lab_3/nginx.conf
Normal file
25
karamushko_maxim_lab_3/nginx.conf
Normal file
@ -0,0 +1,25 @@
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location /genre_service/ {
|
||||
proxy_pass http://genre_service:8001/;
|
||||
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 /film_service/ {
|
||||
proxy_pass http://film_service:8002/;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
2
karamushko_maxim_lab_3/requirements.txt
Normal file
2
karamushko_maxim_lab_3/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Flask==3.0.3
|
||||
requests==2.32.3
|
Loading…
Reference in New Issue
Block a user