diff --git a/tukaeva_alfiya_lab_3/.gitignore b/tukaeva_alfiya_lab_3/.gitignore new file mode 100644 index 0000000..0196882 --- /dev/null +++ b/tukaeva_alfiya_lab_3/.gitignore @@ -0,0 +1,2 @@ +/.venv +/.idea diff --git a/tukaeva_alfiya_lab_3/README.md b/tukaeva_alfiya_lab_3/README.md new file mode 100644 index 0000000..d863862 --- /dev/null +++ b/tukaeva_alfiya_lab_3/README.md @@ -0,0 +1,52 @@ +# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами + +## Задание + +* Создать 2 микросервиса, реализующих CRUD на связанных сущностях. +* Реализовать механизм синхронного обмена сообщениями между микросервисами. +* Реализовать шлюз на основе прозрачного прокси-сервера nginx. + +## Предметная область: + +В качестве предметной области использовался пример 19-го варианта. Имеются 2 сущности: одна сущность - книга в библиотеке, вторая - тематика. +Поля тематики: УИД (уникальный идентификатор) тематики, номер, название. Поля книги: УИД книги, Автор, Название, Год издания, УИД тематики, к которой принадлежит книга. + + +## Запуск ЛР: + +Введем в терминале команду: +``` +docker compose up --build + +``` +Эта команда сначала выполнит сборку, а затем запустит контейнеры. + +Также нужно убедиться в том,что 80 порт свободен и тогда вся система запустится. + +Запускаем Postman и запускаем следующие запросы: + +![](lab_3.1.png "") + + +Для каждой сущности были реализованы CRUD-операции. + +Theme сервис: + +GET /themes — получить все темы +POST /themes — создать новую тему +GET /themes/{id} — получить тему по ID +GET /themes/with-books/{themeId} — получить все книги, которые относятся к themeId +PUT /themes/{id} — обновить тему по ID +DELETE /themes/{id} — удалить тему по ID + +Book сервис: + +GET /books — получить все книги +POST /books — создать новую книгу +GET /books/{id} — получить книгу по ID +PUT /books/{id} — обновить книгу по ID +DELETE /books/{id} — удалить книгу по ID + + +# Видео +https://vk.com/video230744264_456239105?list=ln-xiFZDAJoFyAsG9Dg2l diff --git a/tukaeva_alfiya_lab_3/book_service/Dockerfile b/tukaeva_alfiya_lab_3/book_service/Dockerfile new file mode 100644 index 0000000..ebe62b9 --- /dev/null +++ b/tukaeva_alfiya_lab_3/book_service/Dockerfile @@ -0,0 +1,16 @@ +FROM python:latest + +# Устанавливаю рабочую директорию внутри контейнера +WORKDIR /app + +# Копирую файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаю зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копирую все файлы в контейнер +COPY book_service/book_service.py . + +# Команда для запуска Python-скрипта +CMD ["python", "book_service.py"] diff --git a/tukaeva_alfiya_lab_3/book_service/book_service.py b/tukaeva_alfiya_lab_3/book_service/book_service.py new file mode 100644 index 0000000..70141aa --- /dev/null +++ b/tukaeva_alfiya_lab_3/book_service/book_service.py @@ -0,0 +1,134 @@ +from flask import Flask, jsonify, request +import requests +from uuid import uuid4 +import uuid + + +class Book: + def __init__(self, title, year, author, uuid_: uuid, theme_id: uuid): + if uuid_ is None: + self.uuid_: uuid = uuid4() + else: + self.uuid_: uuid = uuid.UUID(uuid_) + self.title: str = title + self.year: int = year + self.author: str = author + self.theme_id: uuid = uuid.UUID(theme_id) + + def to_dict(self): + return { + 'title': self.title, + 'year': self.year, + 'author': self.author, + 'theme_id': self.theme_id, + 'uuid': self.uuid_ + } + + def to_dict_for_themes(self): + return { + 'title': self.title, + 'year': self.year, + 'author': self.author, + 'uuid': self.uuid_ + } + + + +app = Flask(__name__) + +books: list[Book] = [ + Book(title='Mac', year=1977, author= 'Jane', uuid_='89fa1e7a-7e88-445e-a4d8-6d4497ea8f19', + theme_id='997aa4c5-ebb2-4794-ba81-e742f9f1fa30'), + Book(title='Portrait', year=1989, uuid_='0351ee11-f11b-4d83-b2c8-1075b0c357dc', author= 'Mark', + theme_id='694827e4-0f93-45a5-8f75-bad7ef2d21fe'), + Book(title='Chamomile', uuid_='dfc17619-7690-47aa-ae8e-6a5068f8ddec', year=1977, author= 'Laura', + theme_id='997aa4c5-ebb2-4794-ba81-e742f9f1fa30') +] + +themes_url = 'http://theme_service:20001/' + + +def list_jsonify(): + return jsonify([book.to_dict() for book in books]) + + +# получить список книг +@app.route('/', methods=['GET']) +def get_all(): + return list_jsonify(), 200 + + +# получение списка книг по идентификатору темы (для theme_service) +@app.route('/by-theme/', methods=['GET']) +def get_by_theme_id(theme_uuid): + return [book.to_dict_for_themes() for book in books if book.theme_id == theme_uuid], 200 + + +# получение книги по идентификатору +@app.route('/', methods=['GET']) +def get_one(uuid_): + for book in books: + if book.uuid_ == uuid_: + return book.to_dict(), 200 + + return 'Книга с таким uuid не была найдена', 404 + + + + +# создание новой книги +@app.route('/', methods=['POST']) +def create(): + data = request.json + title = data.get('title', None) + year = data.get('year', None) + author = data.get('author', None) + theme_id = data.get('theme_id', None) + checking = requests.get(themes_url + f'/check/{theme_id}') + print(checking) + if checking.status_code == 200: + new_book = Book(title, year,author, None, theme_id) + books.append(new_book) + return get_one(new_book.uuid_) + if checking.status_code == 404: + return 'Тема с таким uuid не существует', 404 + + return 'Неизвестная ошибка', 500 + + +# изменение книги по идентификатору +@app.route('/', methods=['PUT']) +def update_by_id(uuid_): + data = request.json + new_title = data.get('title', None) + new_year = data.get('year', None) + new_author = data.get('author', None) + + for book in books: + print(book.uuid_) + + if book.uuid_ == uuid_: + if new_title is not None: + book.title = new_title + if new_year is not None: + book.year = new_year + if new_author is not None: + book.author= new_author + return get_one(book.uuid_) + + return 'Книга с таким uuid не была найдена', 404 + + +# удаление книги по идентификатору +@app.route('/', methods=['DELETE']) +def delete(uuid_): + for book in books: + if book.uuid_ == uuid_: + books.remove(book) + return 'Книга успешно удалена', 200 + + return 'Книга с таким uuid не была найдена', 404 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=20002, debug=True) diff --git a/tukaeva_alfiya_lab_3/docker-compose.yml b/tukaeva_alfiya_lab_3/docker-compose.yml new file mode 100644 index 0000000..2cb7d0c --- /dev/null +++ b/tukaeva_alfiya_lab_3/docker-compose.yml @@ -0,0 +1,29 @@ + +services: + + book_service: + container_name: book_service + build: + context: . + dockerfile: ./book_service/Dockerfile + expose: + - 20001 + + theme_service: + container_name: theme_service + build: + context: . + dockerfile: ./theme_service/Dockerfile + expose: + - 20002 + + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - book_service + - theme_service + diff --git a/tukaeva_alfiya_lab_3/lab_3.1.png b/tukaeva_alfiya_lab_3/lab_3.1.png new file mode 100644 index 0000000..088273f Binary files /dev/null and b/tukaeva_alfiya_lab_3/lab_3.1.png differ diff --git a/tukaeva_alfiya_lab_3/nginx.conf b/tukaeva_alfiya_lab_3/nginx.conf new file mode 100644 index 0000000..eb9f6fb --- /dev/null +++ b/tukaeva_alfiya_lab_3/nginx.conf @@ -0,0 +1,26 @@ + +events { worker_connections 1024; } + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /theme_service/ { + proxy_pass http://theme_service:20001/; + 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 /book_service/ { + proxy_pass http://book_service:20002/; + 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; + } + } +} diff --git a/tukaeva_alfiya_lab_3/requirements.txt b/tukaeva_alfiya_lab_3/requirements.txt new file mode 100644 index 0000000..2c6edec --- /dev/null +++ b/tukaeva_alfiya_lab_3/requirements.txt @@ -0,0 +1,3 @@ + Flask==3.0.3 + requests==2.32.3 + diff --git a/tukaeva_alfiya_lab_3/theme_service/Dockerfile b/tukaeva_alfiya_lab_3/theme_service/Dockerfile new file mode 100644 index 0000000..c20c499 --- /dev/null +++ b/tukaeva_alfiya_lab_3/theme_service/Dockerfile @@ -0,0 +1,16 @@ +FROM python:latest + +# Устанавливаю рабочую директорию внутри контейнера +WORKDIR /app + +# Копирую файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаю зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копирую все файлы в контейнер +COPY theme_service/theme_service.py . + +# Команда для запуска Python-скрипта +CMD ["python", "theme_service.py"] diff --git a/tukaeva_alfiya_lab_3/theme_service/theme_service.py b/tukaeva_alfiya_lab_3/theme_service/theme_service.py new file mode 100644 index 0000000..ee64757 --- /dev/null +++ b/tukaeva_alfiya_lab_3/theme_service/theme_service.py @@ -0,0 +1,118 @@ +from flask import Flask, jsonify, request +from uuid import uuid4 +import uuid +import requests + + +class Theme: + def __init__(self, number, name, uuid_: uuid): + if uuid_ is None: + self.uuid_: uuid = uuid4() + else: + self.uuid_: uuid = uuid.UUID(uuid_) + self.number: str = number + self.name: str = name + + def to_dict(self): + return { + "uuid": self.uuid_, + "number": self.number, + "name": self.name + } + + def to_dict_with_books(self, books: list): + return { + "uuid": self.uuid_, + "number": self.number, + "name": self.name, + "books": books + } + + +app = Flask(__name__) + +themes: list[Theme] = [ + Theme(name='Flowers', number='1', uuid_='997aa4c5-ebb2-4794-ba81-e742f9f1fa30'), + Theme(name='Art', number='2', uuid_='694827e4-0f93-45a5-8f75-bad7ef2d21fe') +] + +books_url = 'http://book_service:20002/' + + +def list_jsonify(): + return jsonify([theme.to_dict() for theme in themes]) + + +# получение списка всех тем +@app.route('/', methods=['GET']) +def get_all(): + return list_jsonify(), 200 + + +# получение темы по идентификатору +@app.route('/', methods=['GET']) +def get_one(uuid_): + for theme in themes: + if theme.uuid_ == uuid_: + return theme.to_dict(), 200 + + return 'Тема с таким uuid не была найдена', 404 + + +# получение темы со списком книг по ней +@app.route('/with-books/', methods=['GET']) +def get_one_with_books(uuid_): + for theme in themes: + if theme.uuid_ == uuid_: + response = requests.get(books_url + f'by-theme/{uuid_}') + print(response.json()) + return theme.to_dict_with_books(response.json()), 200 + + return 'Тема с таким uuid не была найдена', 404 + + +# создание темы +@app.route('/', methods=['POST']) +def create(): + data = request.json + number = data.get('number', None) + name = data.get('name', None) + if name is None or number is None: + return 'Недостаточно полей для создания новой темы', 404 + + new_theme = Theme(number, name, None) + themes.append(new_theme) + return get_one(new_theme.uuid_) + + +# изменение темы по идентификатору +@app.route('/', methods=['PUT']) +def update_by_id(uuid_): + data = request.json + new_number = data.get('number', None) + new_name = data.get('name', None) + + for theme in themes: + if theme.uuid_ == uuid_: + if new_number is not None: + theme.number = new_number + if new_name is not None: + theme.name = new_name + return get_one(theme.uuid_) + + return 'Тема с таким uuid не была найдена', 404 + + +# удаление темы по идентификатору +@app.route('/', methods=['DELETE']) +def delete(uuid_): + for theme in themes: + if theme.uuid_ == uuid_: + themes.remove(theme) + return 'Тема успешно удалена', 200 + + return 'Тема с таким uuid не была найдена', 404 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=20001, debug=True) \ No newline at end of file