Данный проект - задание в рамках дисциплины по backend разработке, которое было следующим:
Требуется реализовать приложение, принимающее на вход список инструкций. Существуют два вида инструкций:
calc
: вычислить арифметическую операцию (умножение, сложение, вычитание) над двумя сущностями и сохранить результат в переменную. Сущность может быть либо литерал типа int64, либо имя переменной. В одну и ту же переменную записывать результат можно только один раз.x = 5
Считаем, что каждая арифметическая операция из инструкций вычисляется долго, например 50ms. Требуется наиболее быстро вычислять результаты списка инструкций, т.е. выводить требуемые переменные.input
[ { "type": "calc", "op": "+", "var": "x", "left": 10, "right": 2 }, { "type": "calc", "op": "*", "var": "y", "left": "x", "right": 5 }, { "type": "calc", "op": "-", "var": "q", "left": "y", "right": 20 }, { "type": "calc", "op": "+", "var": "unusedA", "left": "y", "right": 100 }, { "type": "calc", "op": "*", "var": "unusedB", "left": "unusedA", "right": 2 }, { "type": "print", "var": "q" }, { "type": "calc", "op": "-", "var": "z", "left": "x", "right": 15 }, { "type": "print", "var": "z" }, { "type": "calc", "op": "+", "var": "ignoreC", "left": "z", "right": "y" }, { "type": "print", "var": "x" } ]output
{ "items": [ {"var": "q","value": 40}, {"var": "z","value": -3}, {"var": "x","value": 12} ] }
Приложение реализовано на Go с использованием брокера сообщений RabbitMQ и разделено на два сервиса - Executor
и Arithmetic
.
Так как задание подразумевало, что математические операции выполняются долго, будто это запрос в другой сервис, логика вычислений была на самом деле вынесена в отдельный сервис (для наглядности).
Executor
- основной сервис, на который поступают запросы со списком инструкций от пользователей,
а Arithmetic занимается математическими вычислениями.
Executor
обрабатывает инструкции, отправляет запросы на Arithmetic
(через gRPC), затем ждет ответы в брокере сообщений и обрабатывает эти ответы.
Arithmetic
помимо самих вычислений также отправляет их результаты в брокер сообщений.
Благодаря тому, что логика вычислений вынесена в отдельный сервис, есть возможность использовать брокер сообщений для асинхронной обработки результатов, что упростит логику в основном сервисе, избавив от необходимости каких-либо элементов синхронизации, а также позволит независимо масштабировать арифметический сервис и значительно увеличить количество инструкций в контексте одного запроса на основной сервис.
Архитектура кода обоих сервисов - слоистая. То есть в центре — бизнес-логика со всеми её сущностями, которые занимаются прикладными задачами.
К бизнес-логике относятся use cases
(код, который выполняет какой-либо бизнес-процесс, в качестве действий использует атомарные методы сервисов)
и services
(группа методов, которые группируются в сервисы по смысловой нагрузке и необходимы для изоляции сценария от внешних зависимостей).
Вокруг бизнес-логики - драйверы, которые могут исп
642F
ользоваться как для вызова бизнес-логики (handlers
).
internal
├── arithmetic
│ ├── handlers # HTTP-обработчики
│ ├── services # Слой-посредник между бизнес-логикой и инфраструктурой
│ ├── use_cases # Сценарии использования (application logic)
│ └── entities.go # Бизнес-сущности
├── executor
│ ├── dto # Объекты передачи данных между слоями
│ ├── handlers # HTTP-обработчики
│ ├── services # Слой-посредник между бизнес-логикой и инфраструктурой
│ ├── use_cases # Сценарии использования (application logic)
│ ├── entities.go # Бизнес-сущности
│ └── errors.go # Доменные ошибки
На данный момент реализовано 2 хэндлера для сервиса Executor
- grpc
и rest
.
rest
реализован с помощью фреймворка gin. Хэндлеры занимаются первичной валидацией инструкций и их распределением по типам для дальнейшей передачи в сценарий.
Также хэндлеры кладут id запроса в контекст (так как он выступает именем очереди в брокере) и маппят доменные ошибки к интерфейсным.
В качестве API документации у rest
- Swagger, а у grpc
- его proto файл.
Также бизнес логика обоих сервисов покрыта unit тестами с использованием GoMock.
Для приложения написан docker-compose.yml
, а все параметры конфигурации задаются через config.yaml
.
Для запуска проекта, включая RabbitMQ, можно выполнить одну команду:
docker-compose up -d
С дефолтным config.yaml
Swagger UI доступен по адресу: http://localhost:8080/swagger/index.html#