From a0209b612eeb3d050a91e1411a55a736d7c735e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D1=8C=D1=84=D0=B8=D1=8F=20=D0=A2=D1=83=D0=BA?= =?UTF-8?q?=D0=B0=D0=B5=D0=B2=D0=B0?= Date: Fri, 11 Oct 2024 01:01:25 +0400 Subject: [PATCH] tukaeva_alfiya_lab_3 is ready --- tukaeva_alfiya_lab_3/.gitignore | 2 + tukaeva_alfiya_lab_3/README.md | 52 +++++++ tukaeva_alfiya_lab_3/book_service/Dockerfile | 16 +++ .../book_service/book_service.py | 134 ++++++++++++++++++ tukaeva_alfiya_lab_3/docker-compose.yml | 29 ++++ tukaeva_alfiya_lab_3/lab_3.1.png | Bin 0 -> 28769 bytes tukaeva_alfiya_lab_3/nginx.conf | 26 ++++ tukaeva_alfiya_lab_3/requirements.txt | 3 + tukaeva_alfiya_lab_3/theme_service/Dockerfile | 16 +++ .../theme_service/theme_service.py | 118 +++++++++++++++ 10 files changed, 396 insertions(+) create mode 100644 tukaeva_alfiya_lab_3/.gitignore create mode 100644 tukaeva_alfiya_lab_3/README.md create mode 100644 tukaeva_alfiya_lab_3/book_service/Dockerfile create mode 100644 tukaeva_alfiya_lab_3/book_service/book_service.py create mode 100644 tukaeva_alfiya_lab_3/docker-compose.yml create mode 100644 tukaeva_alfiya_lab_3/lab_3.1.png create mode 100644 tukaeva_alfiya_lab_3/nginx.conf create mode 100644 tukaeva_alfiya_lab_3/requirements.txt create mode 100644 tukaeva_alfiya_lab_3/theme_service/Dockerfile create mode 100644 tukaeva_alfiya_lab_3/theme_service/theme_service.py 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 0000000000000000000000000000000000000000..088273f66ef52b4b8b64c8eccaaa148a29ff157c GIT binary patch literal 28769 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

_7*STQEY^yyL7ai1I=X7rWi{o!kf!02TC#f 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 literal 0 HcmV?d00001 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