2.1. Установка Docker
2.3. Запуск проекта
2.4. Возможные ошибки
2.6. Выключение проекта
3.1. Запросы в curl
3.2. Работа в GUI
Пользователь хочет считать арифметические выражения. Он вводит строку 2 + 2 * 2 и хочет получить в ответ 6. Но наши операции сложения и умножения (также деления и вычитания) выполняются "очень-очень" долго. Поэтому вариант, при котором пользователь делает http-запрос и получает в качестве ответа результат, невозможна. Более того: вычисление каждой такой операции в нашей "альтернативной реальности" занимает "гигантские" вычислительные мощности. Соответственно, каждое действие мы должны уметь выполнять отдельно и масштабировать эту систему можем добавлением вычислительных мощностей в нашу систему в виде новых "машин". Поэтому пользователь, присылая выражение, получает в ответ идентификатор выражения и может с какой-то периодичностью уточнять у сервера "не посчиталось ли выражение"? Если выражение наконец будет вычислено - то он получит результат. Помните, что некоторые части арифметического выражения можно вычислять параллельно.
Проект ставится в несколько шагов:
Для работы с сервисом необходимо установить на компьютер Docker
Версии для Mac, Windows, Linux доступны по следующему адресу: https://docs.docker.com/get-docker/
Клонируем себе на компьютер репозиторий. Набираем в терминале:
git clone https://github.com/iliazaraysky/distributed-expression-calculator.git
Переходим в папку с проектом на компьютере
cd distributed-expression-calculator
Для создания образов и запуска контейнеров набираем:
docker-compose up --build
Первый раз проект запускается долго. Нужно дождаться, чтобы скачались необходимые образы (я постарался прописать везде alpine образы с минимальным набором компонентов, но получилось все равно много). Если вы запускаете проект без флага -d
, сигналом к началу проверки приложения, могут послужить сообщения в терминале, что воркеры ждут заданий из очереди:
worker1 | 2024/02/18 10:08:35 No messages in the queue, waiting
worker2 | 2024/02/18 10:08:37 No messages in the queue, waiting
worker1 | 2024/02/18 10:08:40 No messages in the queue, waiting
worker2 | 2024/02/18 10:08:42 No messages in the queue, waiting
worker1 | 2024/02/18 10:08:45 No messages in the queue, waiting
Я попробовал запускать проект на разных стендах, на 1 из 3 вылезла ошибка:
ERROR: for postgres Cannot create container for service postgres: Conflict.
The container name "/postgres" is already in use by container "ac58e56714bbf18014776cfa3e47ae31d597db721cf00d0c0b36c0621dffd10a".
You have to remove (or rename) that container to be able to reuse that name.
Из текста сообщения понятно, в системе уже есть контейнер с названием postgres
. Я исправил это тем, что удалил контейнер с этим названием командой:
docker rm postgres
Также один раз вылезла ошибка сборки frontend, только у меня и только после того как я зачистил буквально весь Docker от всех Containers, Images, Volumes.
Если встретите подобное, огромная просьба попробовать еще раз пересобрать проект.
Тестирование проводилось на следующих стендах:
Знаю, в проекте заявлено три воркера. В моей базе данных также предварительно записываются три воркера и в списке на таймауты можно выбрать третий.
Однако я решил его не добавлять, так как код в первых двух максимально схож, а вот ресурсы расходуются знатно. Я очень хочу, чтобы проект запустился и каждый кто будет проверять, не испытывал с этим трудности.
Заодно можно увидеть статус "Offline" в таблице монитонга воркеров. Это не заглушка, frontend честно пытается его пинговать
Набрать из консоли, находясь в той же директории откуда запускался проект
docker-compose down
В проекте реализован GUI. Удобнее всего посмотреть работу из него. Но первым пунктом описания сделаны примеры запросов, через curl
.
Во второй части проекты мы добавляли регистрацию и авторизацию. Поэтому в качестве примера, одна из ранее открытых областей, стала закрытой. Теперь нельзя получить доступ к "Списку операций" без токена в заголовке.
Страница отвечает на GET-запрос, приветствием. Пример запроса:
curl -X GET -H "Content-Type: application/json" http://127.0.0.1:8080/registration
Отправляем POST-запрос по адресу:
http://localhost:8080/registration
Тело запроса в формате JSON, структура:
{
"login": "user",
"password": "password123"
}
Пример запроса:
curl -X POST -H "Content-Type: application/json" -d "{\"login\": \"user1\", \"password\":\"password123\"}" http://127.0.0.1:8080/registration
Страница отвечает на GET-запрос, приветствием. Пример запроса:
curl -X GET -H "Content-Type: application/json" http://127.0.0.1:8080/login
Отправляем POST-запрос по адресу:
http://localhost:8080/login
Пример запроса:
curl -X POST -H "Content-Type: application/json" -d "{\"login\": \"user1\", \"password\":\"password123\"}" http://127.0.0.1:8080/login
В ответ сервер присылает Token. Пример токена:
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTMyNzYwNzcsImlhdCI6MTcxMzI3NTQ3NywibG9naW4iOiJ1c2VyMSIsIm5iZiI6MTcxMzI3NTQ3N30.zseZouXKkOlNL7X7mmOrVIFtu-Ekp2nb0lZ7Wnw_SVE"}
Отправляем POST-запрос по адресу:
http://localhost:8080/add-expression
Тело запроса в формате JSON, структура:
{"text": "2+2+2"}
Пример запроса:
curl -X POST -H "Content-Type: application/json" -d "{\"text\": \"3 + 1\"}" http://127.0.0.1:8080/add-expression
Отправляем GET-запрос по адресу:
http://localhost:8080/get-operations
Ответ получаемый от сервера в формате JSON, структура:
{
"data": [
{
"unique_id": "abb1f26f-2014-43f6-9f88-22fd6bf7aedc",
"query_text": "2 + 1 + 3 + 1",
"creation_time": {
"Time": "2024-02-11T21:17:24.764203Z",
"Valid": true
},
"completion_time": {
"Time": "2024-02-11T21:17:36.870463Z",
"Valid": true
},
"execution_time": "00:00:12.10626",
"server_name": {
"String": "worker3",
"Valid": true
},
"result": {
"String": "7",
"Valid": true
},
"status": "Done"
}
],
"total_items": 4,
"total_pages": 1,
"current_page": 1,
"items_per_page": 5
}
Ответ содержит информацию о пагинации, которую мы высчитываем на стороне backend и возвращаем для frontend.
Данные берутся из БД Postgresql - таблица requests
Так как во второй части задания у нас появилась авторизация. Простой GET-запрос, будет возвращать Unauthorized
Пример запроса без авторизации:
curl -X GET -H "Content-Type: application/json" http://127.0.0.1:8080/get-operations
Пример запроса с авторизацией
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTMyNzY1MTEsImlhdCI6MTcxMzI3NTkxMSwibG9naW4iOiJ1c2VyMSIsIm5iZiI6MTcxMzI3NTkxMX0.GL1bbHRijfDo1BortkJMCMrxQwRxexHP8sHEX98bDaA" http://localhost:8080/get-operations
Так как у этого запроса есть пагинация, следует указать как получать следующие страницы:
http://localhost:8080/get-operations?page=2
Информацию о каждой операции также можно получить по GET-запросу по адресу:
http://localhost:8080/get-request-by-id/{uuid}
Ответ получаемый от сервера в формате JSON, структура:
{
"unique_id":"abb1f26f-2014-43f6-9f88-22fd6bf7aedc",
"query_text":"2 + 1 + 3 + 1",
"server_name":"worker3",
"result":"7",
"status":"Done"
}
Пример запроса:
curl -X GET -H "Content-Type: application/json" http://localhost:8080/get-request-by-id/abb1f26f-2014-43f6-9f88-22fd6bf7aedc
У нас в задании говорится о разделении времени выполнения на операции (это я понял, когда уже было поздно), поэтому тут только запрос на установление таймаута для каждого воркера. В принципе, функционал о том же самом, и можно сделать отдельную таблицу в БД, написать отдельный запрос, и все это будет работать по ТЗ. Но в виде самого наипростейшего решения, сойдет и мой вариант... Ведь он работает =)
Данный эндпойнт умеет обрабатывать как GET так и POST запросы. Если мы хотим узнать настройки воркеров, делаем GET-запрос по адресу:
http://localhost:8080/setup-workers
Ответ получаемый от сервера в формате JSON, структура:
[
{
"worker_name": "worker2",
"last_task": "70d9df9f-84ec-426a-b408-31fe7fe8380f",
"status": "ready",
"last_timeout_setup": "2024-02-17T21:10:21.870463Z",
"current_timeout": 11
},
{
"worker_name": "worker3",
"last_task": "abb1f26f-2014-43f6-9f88-22fd6bf7aedc",
"status": "ready",
"last_timeout_setup": "2024-02-17T21:11:37.870463Z",
"current_timeout": 12
},
{
"worker_name": "worker1",
"last_task": "f3715129-63f5-44a6-ac42-adbfb186ba60",
"status": "ready",
"last_timeout_setup": "2024-02-17T21:16:31.870463Z",
"current_timeout": 10
}
]
Пример GET - запроса
curl -X GET -H "Content-Type: application/json" http://localhost:8080/setup-workers
Если мы хотим поменять настройку у воркера, делаем POST-запрос по адресу:
http://localhost:8080/setup-workers
Тело запроса в формате JSON, структура:
{
"worker_name":"worker1",
"timeout_data":10
}
Пример POST - запроса
curl -X POST -H "Content-Type: application/json" -d "{\"worker_name\": \"worker1\", \"timeout_data\": 19}" http://localhost:8080/setup-workers
После запуска проекта он доступен по адресу:
http://localhost:3000/
На главной странице находится строка отправки выражения в которую мы вбиваем данные для вычисления и нажимаем кнопку "Отправить". Сигналом о том, что выражение отправлено, является всплывающее окно.
Также на главной странице располагается таблица состояния воркеров. Принцип действия этой таблицы заключается в следующем. Frontend делает регулярные запросы на главные страницы воркеров, если страница доступна, в таблице отображается "Online" и время последнего запроса. Иначе отображается Offline.
Ссылки на работу воркеров, активные, ведут на их главные страницы
На этой вкладке отображаются все выражения, которые были посчитаны воркерами в упрощенном виде. Здесь мы можем получить уникальный идентификатор и запрос с результатом
На этой вкладке показаны расширенные таблицы всех операций:
UUID запроса, Запрос, Время создания, Время завершения, Время выполнения, Сервер, Результат, Статус
Тут же реализована пагинация, и возможность перейти на конкретную операцию (по uuid)
Состоит трех блоков. Смена таймаута на воркерах, статус воркеров и получение сведений о текущем состоянии воркеров.
Поменять таймаут очень просто, выбираем в выпадающем списке нужного нам воркера, вводим ниже число, нажимаем кнопку "Отправить".
Текущие настройки воркеров, это постоянный GET запрос на backend, curl -X GET -H "Content-Type: application/json" http://localhost:8080/setup-workers
, увидеть изменения в настройках воркеров можно довольно быстро. Не уверен, что так делают в реальных проектах, делал исключительно для демонстрации возможностей
Маленькое пояснение для проверяющих. Я не реализовал gRPC. Не успел. Конечно я могу доделать сейчас, но это нарушает правила. Не успел. Не страшно. Остальное на месте. =)
Для начала стоит указать полный список изменений внесенных во второй части проекта:
/registration
/login
/get-operation-by-user-id
. Нужен для того, чтобы сортировать данные по конкретному пользователюauthMiddleware
, который проверяет наличие токена в заголовке Authorization
corsHandler
, так как теперь у нас есть дополнительный заголовок Authorization
requests
, добавлен столбец username
, в котором теперь записывается пользователь отправивший заданиеusers
, в которой хранятся login
и password
, необходимые для получения токенаdatabase_schema
для docker, с учетом появившейся таблицы users
, и дополнительной колонки username
в requests
Регистрация
Логин
Регистрация
, меняется на его Логин
, который в свою очередь ведет на все операции пользователяUserRequestDetails
Register
Login
Список операций
, теперь доступна только авторизированным пользователямApp.js
ведется проверка токена, контролируется время когда он истекаетApp.js
из токена сохраняется username
и передается на главную страницу, для последующего добавление при инициализации операции Файл с тестами лежит в директории Backend
. Запускать лучше всего после запуска сервиса.
Полное описание тестов лежит в отдельном файле. TESTS_DESCRIPTION.md
Скачиваем репозиторий
git clone https://github.com/iliazaraysky/distributed-expression-calculator.git
Переходим в скаченную директорию distributed-expression-calculator
Запускаем проект
docker-compose up -d --build
После запуска проект доступен по адресу:
http://localhost:3000
Перейдя на главную страницу видим информацию о том, что отправка выражений доступна только после авторизации, также нет доступа к списку операций. Тут следует пояснить:
5.1. На главной странице блок отправки выражений не показывается пока нет токена, это прописано на Frontend
5.2. На странице Список операций
, блокировка идет на уровне Backend, средствами Middleware authMiddleware
Обратим внимание на правый верхний угол Регистрация -> Вводим Логин
и Пароль
-> Создать. Так вы создадите пользователя, от имени которого можно будет совершать операции
6.1. Примечание. Когда заполнены поля и нажата кнопка Создать
, React делает редирект на страницу авторизации, но почему-то не всегда =). Frontend - это сложно, я с ним намучался
Переходим на страницу Вход в кабинет
-> вводим регистрационные данные -> Вход
После авторизации, в правом верхнем углу будет оторажаться Login
и Выход
.
8.1. Выход
удаляет токен из локального хранилища, и обновляет страницу
8.2. Login
является ссылкой к списку операций пользователя
На странице Список операций
, теперь доступны к просмотру сделанные ранее операции
На Главной
странице теперь доступно поле ввода выражений
Вводим -> Отправляем -> нажимаем на Логин
-> Видим появившуюся операцию
Нажимаем выход -> идем на главную страницу -> строка отправки недоступна -> идем в Список операций
-> снова недоступно
Набираем в терминале
docker-compose down
Снова заходим под своей учетной записью, пользователь на месте, данные сохранены
Можно завершить работу docker-compose
с флагом -v
, тогда данные не сохранятся, а новый запуск будет с "чистым" сервисом