# Домашнее задание по модулю "Основы Go" Необходимо реализовать сервис для работы с корзиной пользователя Cart ## Основное задание Необходимо имплементировать сервис, отвечающий за работу с корзиной пользователя (сервис Cart). Логика работы методов и их контракты описаны ниже. Требования к решению: 1. Используем HTTP, на основе стандартной библиотеки Go 1.23 2. Для определения существования товара делаем поход в сервис `products` 3. Состояние храним в in-memory, персистентное хранилище на данный момент не требуется 4. Никакого резерва стоков не делаем, логика простейшая ## Дополнительное задание 1. Делаем Middleware, который будет логировать поступающие запросы 2. Делаем валидацию входящих структур на основе любой Open Source библиотеки (можно подсмотреть тут - https://awesome-go.com/validation/) 3. Делаем ретраи в `products` на 420/429 статус в виде Client Middleware. 3 ретрая, потом ошибка ## Спецификация ### Добавить товар в корзину Идентификатором товара является числовой идентификатор SKU. Метод добавляет указанный товар в корзину определенного пользователя. Каждый пользователь имеет числовой идентификатор userID. При добавлении в корзину проверяем, что товар существует в специальном сервисе. Один и тот же товар может быть добавлен в корзину несколько раз, при этом количество экземпляров складывается. | Метод | URI | |-------|-------------------------------------------------| | POST | /user//cart/ | **Параметры запроса:** | Параметр | Тип параметра | Тип данных | Пример | Описание | |----------|---------------|------------|--------|------------------------------------------------------------------| | user_id | query path | int64 | 1007 | Идентификатор пользователя, в корзину которого добавляется товар | | sku_id | query path | int64 | 2008 | Идентификатор товара, добавляемого в корзину | | count | body | uint32 | 12 | Количество товаров, добавляемое в корзину | **Параметры ответа:** отсутствуют **Параметры ошибочных ответов:** | Сценарий | HTTP код ошибки | Описание | |-------------------------------------------------------------------------------------------|-----------------|-------------------------------------------------------------------------| | Добавление валидного SKU для пользователя (user_id) с нулевым или отрицательным значением | 400 | Идентификатор пользователя должен быть натуральным числом (больше нуля) | | Добавление SKU с нулевым или отрицательным значением | 400 | SKU должен быть натуральным числом (больше нуля) | | Добавление SKU с нулевым или отрицательным количеством (count) | 400 | Количество должно быть натуральным числом (больше нуля) | | Добавление несуществующего SKU в корзину | 412 | SKU должен существовать в сервисе `products` | **Диаграмма последовательности:** ![cart-cart-item-add](img/cart-cart-item-add.png) ### Удалить товар из корзины Метод полностью удаляет все количество товара из корзины пользователя. Если у пользователя вовсе нет данной позиции, то возвращается такой же ответ, как будто бы все позиции данного sku были успешно удалены. | Метод | URI | |--------|-------------------------------------------------| | DELETE | /user//cart/ | **Параметры запроса:** | Параметр | Тип параметра | Тип данных | Пример | Описание | |----------|---------------|------------|--------|------------------------------------------------------------------| | user_id | query path | int64 | 1007 | Идентификатор пользователя, в корзину которого добавляется товар | | sku_id | query path | int64 | 2008 | Идентификатор товара, удаляемого из корзины | **Параметры ответа:** отсутствуют **Параметры ошибочных ответов:** | Сценарий | HTTP код ошибки | Описание | |------------------------------------------------------------------|-----------------|-------------------------------------------------------------------------| | Удаление SKU с нулевым или отрицательным значением | 400 | SKU должен быть натуральным числом (больше нуля) | | Удаление SKU с нулевым или отрицательным пользователем (user_id) | 400 | Идентификатор пользователя должен быть натуральным числом (больше нуля) | **Диаграмма последовательности:** ![cart-cart-item-delete](img/cart-cart-item-delete.png) ### Очистить корзину пользователя Метод полностью очищает корзину пользователя. Если у пользователя нет корзины или она пуста, то, как и при успешной очистке корзины, необходимо вернуть код ответа 204 No Content. | Метод | URI | |--------|----------------------| | DELETE | /user//cart | **Параметры запроса:** | Параметр | Тип параметра | Тип данных | Пример | Описание | |----------|---------------|------------|--------|------------------------------------------------------------------| | user_id | query path | int64 | 1007 | Идентификатор пользователя, в корзину которого добавляется товар | **Параметры ответа:** отсутствуют **Параметры ошибочных ответов:** | Сценарий | HTTP код ошибки | Описание | |----------------------------------------------------------------------|-----------------|-------------------------------------------------------------------------| | Удаление корзины с нулевым или отрицательным пользователем (user_id) | 400 | Идентификатор пользователя должен быть натуральным числом (больше нуля) | **Диаграмма последовательности:** ![cart-cart-clear](img/cart-cart-clear.png) ### Получить содержимое корзины Метод возвращает содержимое корзины пользователя на текущий момент. Если корзины у переданного пользователя нет, либо она пуста, следует вернуть 404 код ответа. Товары в корзине упорядочены в порядке возрастания sku. | Метод | URI | |-------|--------------------------| | GET | /user//cart | **Параметры запроса:** | Параметр | Тип параметра | Тип данных | Пример | Описание | |----------|---------------|------------|--------|------------------------------------------------------------------| | user_id | query path | int64 | 1007 | Идентификатор пользователя, в корзину которого добавляется товар | **Параметры ответа:** | Параметр | Тип данных | Пример | Описание | |----------------|------------|--------------------------------------------------|----------------------------------------------| | items[i].sku | int64 | 2008 | Идентификатор товара в корзине пользователя | | items[i].name | string | "Гречка пропаренная, в пакетиках для варки, 400" | Наименование товара | | items[i].count | uint32 | 10 | Количество единиц товара | | items[i].price | uint32 | 16 | Стоимость единицы товара в условных единицах | | total_price | uint32 | 160 | Суммарная стоимость всех товаров в корзине | **Пример ответа:** ```json { "items" : [ { "sku": 2958025, "name": "Roxy Music. Stranded. Remastered Edition", "count": 2, "price": 1028 }, { "sku": 773297411, "name": "Кроссовки Nike JORDAN", "count": 1, "price": 2202 } ], "total_price": 4258 } ``` **Параметры ошибочных ответов:** | Сценарий | HTTP код ошибки | Описание | |--------------------------------------------------------------------|-----------------|-------------------------------------------------------------------------| | Запрос корзины с нулевым или отрицательным пользователем (user_id) | 400 | Идентификатор пользователя должен быть натуральным числом (больше нуля) | **Диаграмма последовательности:** ![cart-cart-list](img/cart-cart-list.png) ## Взаимодействие с Product service Если, вызвав `make run-all`, развернуть деплоймент, swagger этого сервиса можно увидеть локально по адресу: [http://localhost:8082/docs/](http://localhost:8082/docs/) Сервис поддерживает следующие операции: #### GET /product?count=10&start_after_sku=0 Эта операция имеет два необязательных параметра: - `count` — сколько элементов вернуть и - `start_after_sku` — после какого элемента начать вывод. Response: ``` [ { name string price int32 sku int64 }, ] ``` #### GET /product/<sku> Эта операция выводит данные товара с заданным `sku`. Response: ``` { name string price int32 sku int64 } ``` Обратите внимание, сервис `products` отдаёт цены в `int32` формате. В JSON нет беззнаковых целых, поэтому `int32`. Вам же, внутри своих сервисов, для совместимости их между собой следует использовать беззнаковый тип `uint32`. Также следует обратить внимание на то, что он выведен на `localhost:8082` лишь для вашего удобства работы с данными. Ваш сервис `cart`, запущенный докер-контейнером, сможет подключиться к `products` по адресу `products:8082` — заниматься маршрутизацией будет сам докер. Авторизация на запрос выполняется с помощью заголовка `X-API-KEY` и токена `testToken`. ## Makefile В рамках данного задания необходимо имплементировать следующие таргеты: - run-all — запускает сервисы. На данный момент их должно стать два: - `products` — уже реализован - `cart` — разрабатываемый вами в качестве домашнего задания ## Ожидаемый результат - с помощью команды `make run-all` можно запустить приложение - приложение слушает HTTP-запросы на порту 8080 - реализованы API-методы - POST /user//cart/ - DELETE /user//cart/ - DELETE /user//cart - GET /user//cart - методы реализуют заявленную бизнес-логику - методы валидируют запросы и отдают описанные в спецификации коды ошибок - информация о пользователях и их состоянии корзины хранится в памяти приложения - при рестарте приложения состояние системы теряется ## Автоматические проверки Ваше решение должно проходить автоматические проверки: - Компиляция - Линтер - Unit-тесты (если есть) - Автотесты Прохождение автоматических проверок влияет на итоговую оценку за домашнюю работу. ## Сценарии тестирования с примерами запроса (для тьютора) Сценарий тестирования следует описать в [cart.http](./cart.http) ### Дедлайны сдачи и проверки задания: - 24 мая 23:59 (сдача) / 27 мая, 23:59 (проверка)