diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..36737af --- /dev/null +++ b/docs/README.md @@ -0,0 +1,18 @@ +# Домашние задания + +## Введение + + В рамках учебного проекта предстоит реализовать систему, состоящую из нескольких сервисов, которая +будет моделировать работу простого интернет магазина включая такие бизнес процессы как: + +- добавление товаров в корзину и их удаление из нее +- просмотр содержимого корзины +- оформление заказа по текущему составу корзины +- создание заказа +- оплата заказа +- отмена заказа пользователем или по истечению времени ожидания оплаты + +## Содержание + +1. [Основы Go](./homework-1) + diff --git a/docs/homework-1/README.md b/docs/homework-1/README.md new file mode 100644 index 0000000..194c3a1 --- /dev/null +++ b/docs/homework-1/README.md @@ -0,0 +1,274 @@ +# Домашнее задание по модулю "Основы 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 (проверка) diff --git a/docs/homework-1/cart.http b/docs/homework-1/cart.http new file mode 100644 index 0000000..aa5ed20 --- /dev/null +++ b/docs/homework-1/cart.http @@ -0,0 +1,86 @@ +### add 1 sku to cart +POST http://localhost:8080/user/31337/cart/1076963 +Content-Type: application/json + +{ + "count": 1 +} +### expected {} 200 OK; must add 1 item + +### add 5 sku to cart +POST http://localhost:8080/user/31337/cart/1076963 +Content-Type: application/json + +{ + "count": 5 +} +### expected {} 200 OK; must add 5 more item, 1076963 - must be 6 items + +### add unknown sku to cart +POST http://localhost:8080/user/31337/cart/1076963000 +Content-Type: application/json + +{ + "count": 1 +} +### expected {} 412 Precondition Failed; invalid sku + +### add another sku to cart +POST http://localhost:8080/user/31337/cart/1148162 +Content-Type: application/json + +{ + "count": 1 +} +### expected {} 200 OK; must add 1 item + +### invalid user +POST http://localhost:8080/user/0/cart/1148162 +Content-Type: application/json + +{ + "count": 1 +} +### expected {} 400 Bad Request + +### invalid sku +POST http://localhost:8080/user/31337/cart/0 +Content-Type: application/json + +{ + "count": 1 +} +### expected {} 400 Bad Request + +### invalid count +POST http://localhost:8080/user/31337/cart/1148162 +Content-Type: application/json + +{ + "count": 0 +} +### expected {} 400 Bad Request + +# ======================================================================================== + +### delete whole sku from cart +DELETE http://localhost:8080/user/31337/cart/1076963 +Content-Type: application/json +### expected {} 200 OK; must delete item from cart + +### delete whole cart +DELETE http://localhost:8080/user/31337/cart +Content-Type: application/json +### expected {} 204 No Content; must delete cart + +# ======================================================================================== + +### get list of a cart +GET http://localhost:8080/user/31337/cart +Content-Type: application/json +### expected {} 200 OK; must show cart + +### get invalid list of cart +GET http://localhost:8080/user/0/cart +Content-Type: application/json +### 400 bad request diff --git a/docs/homework-1/http-client.env.example.json b/docs/homework-1/http-client.env.example.json new file mode 100644 index 0000000..f3f141c --- /dev/null +++ b/docs/homework-1/http-client.env.example.json @@ -0,0 +1,5 @@ +{ + "dev": { + "port": "8080" + } +} diff --git a/docs/homework-1/img/cart-cart-clear.plantuml b/docs/homework-1/img/cart-cart-clear.plantuml new file mode 100644 index 0000000..ce0f174 --- /dev/null +++ b/docs/homework-1/img/cart-cart-clear.plantuml @@ -0,0 +1,13 @@ +@startuml + +actor User as u +collections Cart as c +database CartStorage as cs + +u -> c : DELETE /user//cart +activate c +c -> cs : cart.DeleteItemsByUserID +c -> u : Response: 204 No Content +deactivate c + +@enduml diff --git a/docs/homework-1/img/cart-cart-clear.png b/docs/homework-1/img/cart-cart-clear.png new file mode 100644 index 0000000..1ef96b0 Binary files /dev/null and b/docs/homework-1/img/cart-cart-clear.png differ diff --git a/docs/homework-1/img/cart-cart-item-add.plantuml b/docs/homework-1/img/cart-cart-item-add.plantuml new file mode 100644 index 0000000..730a769 --- /dev/null +++ b/docs/homework-1/img/cart-cart-item-add.plantuml @@ -0,0 +1,21 @@ +@startuml + +actor User as u +collections Cart as c +database CartStorage as cs +collections ProductService as p + +u -> c : POST /user//cart/\n\t- count +activate c + +c -> p : GET /product/ +activate p + p -> c : \nResponse: 200 OK\n\t- name\n\t- price\n\t- sku +deactivate p +c -> c : validate product exists +c -> cs : cart.AddItem() +c -> u : Response: 200 OK + +deactivate c + +@enduml diff --git a/docs/homework-1/img/cart-cart-item-add.png b/docs/homework-1/img/cart-cart-item-add.png new file mode 100644 index 0000000..8d36e47 Binary files /dev/null and b/docs/homework-1/img/cart-cart-item-add.png differ diff --git a/docs/homework-1/img/cart-cart-item-delete.plantuml b/docs/homework-1/img/cart-cart-item-delete.plantuml new file mode 100644 index 0000000..0e3e590 --- /dev/null +++ b/docs/homework-1/img/cart-cart-item-delete.plantuml @@ -0,0 +1,13 @@ +@startuml + +actor User as u +collections Cart as c +database CartStorage as cs + +u -> c : DELETE /user//cart/ +activate c +c -> cs : cart.DeleteItem(userID, skuID) +c -> u : Response: 204 No Content +deactivate c + +@enduml diff --git a/docs/homework-1/img/cart-cart-item-delete.png b/docs/homework-1/img/cart-cart-item-delete.png new file mode 100644 index 0000000..ff2befc Binary files /dev/null and b/docs/homework-1/img/cart-cart-item-delete.png differ diff --git a/docs/homework-1/img/cart-cart-list.plantuml b/docs/homework-1/img/cart-cart-list.plantuml new file mode 100644 index 0000000..df61339 --- /dev/null +++ b/docs/homework-1/img/cart-cart-list.plantuml @@ -0,0 +1,25 @@ +@startuml + +actor User as u +collections Cart as c +database CartStorage as cs +collections ProductService as p + +u -> c : GET /user//cart +activate c +c -> cs : cart.GetItemsByUserID +alt cart exists + loop for each item in cart + c -> p : GET /product/ + activate p + p -> c : Response: 200 OK\n\t- name\n\t- price\n\t- sku + deactivate p + c -> c : calculate total price + end + c -> u : Response: 200 OK\n\t- []item\n\t- total_price +else + c -> u : Response: 404 Not Found +end +deactivate c + +@enduml diff --git a/docs/homework-1/img/cart-cart-list.png b/docs/homework-1/img/cart-cart-list.png new file mode 100644 index 0000000..b0e3b70 Binary files /dev/null and b/docs/homework-1/img/cart-cart-list.png differ