Микросервис carts представляет собой автономный модуль в рамках микросервисной архитектуры интернет-магазина, предназначенный для управления корзинами покупателей. Сервис позволяет пользователям добавлять товары в корзину, изменять их количество, удалять товары из корзины и просматривать текущее состояние корзины, а также обрабатывает логику применения промокодов и купонов.
carts/
└── src/ # Исходный код приложения
├── app/ # Основная директория приложения
│ │
│ ├── api/ # Точки входа - Primary adapters (API)
│ │ ├── cli/ # Точки входа для интерфейса командной строки
│ │ ├── events/ # Точка входа для обработки событий из очереди сообщений
│ │ └── rest/ # Точка входа для RESTful API
│ │
│ ├── app_layer/ # Use cases - Application
│ │ ├── interfaces/ # Интерфейсы application уровня
│ │ └── use_cases/ # Конкретные реализации use cases
│ │
│ ├── domain/ # Бизнес-сущности - Domain
│ │ ├── cart_config/ # Сущности и бизнес-логика конфигурации корзины
│ │ ├── cart_coupons/ # Сущности и бизнес-логика промокода в корзине
│ │ ├── cart_items/ # Сущности и бизнес-логика товара в корзине
│ │ ├── cart_notifications/ # Сущности и бизнес-логика оповещения корзины
│ │ ├── carts/ # Сущности и бизнес-логика корзины
│ │ └── interfaces/ # Интерфейсы domain уровня
│ │
│ ├── infra/ # Компоненты инфраструктуры - Secondary adapters (Infra)
│ │ ├── events/ # Консьюмеры очереди сообщений, продьюсеры задач и тд.
│ │ ├── http/ # Компоненеты для взаимодействия по HTTP
│ │ ├── repositories/ # Компоненты для формирования SQL запросов
│ │ └── unit_of_work/ # Компонент для работы с данными в одном транзакционном контексте
│ │
│ ├── config.py # Файл конфигурации
│ └── containers.py # Контейнер для инъекции зависимостей
│
└── tests/ # Тесты
├── environment/ # Компоненты тестового окружения
├── functional/ # Функциональные тесты
└── unit/ # Юнит-тесты
- Управление товарами: Пользователи могут добавлять товары в корзину, изменять количество товаров и удалять их из корзины.
- Валидация: Система проверяет:
- максимальное количество товаров в корзине
- минимальную стоимость корзины перед оформлением заказа
- ограничение количества покупки определенных товаров, чтобы избежать дефицита для других клиентов
- Оформление заказов: Корзины могут быть преобразованы в заказы. Для этого в системе используется статусная модель корзины. Корзина считается преобразованной в заказ тогда, когда переходит в статус COMPLETED. Бизнес-логика заказа в данном микросервисе не описана.
- Обработка промокодов: Микросервис поддерживает валидацию и применение промокодов, предоставляя пользователю соответствующие скидки.
- Управление состоянием корзины:
Корзины могут находиться в различных состояниях, таких как:
- OPENED - открыта для редактирования пользователем
- LOCKED - закрыта для редактирования, происходит формирование заказа из данной корзины
- COMPLETED - микросервис заказов успешно создал заказ из этой корзины
- DEACTIVATED - корзина удалена и не может редактироваться В один момент времени конкретная корзина может изменять свое состояние только одним процессом. Это необходимое требование, т.к. должна быть гарантия того, что корзина не изменит состояния тогда, когда микросервис заказов формирует заказ из этой корзины.
- Идентификация забытых корзин: Сервис отслеживает корзины, которые не обновлялись в течение определенного времени, и может отправлять уведомления пользователям для возвращения и завершения покупок.
В основе архитектуры микросервиса лежат принципы Clean Architecture и Hexagonal Architecture, обеспечивая четкое разделение логики и инфраструктуры. На диаграмме показаны уровни, на которые разделено приложение:
- Domain layer: Содержит бизнес-правила и сущности, такие как
Cart
,CartItem
,CartCoupon
,CartNotification
иCartConfig
. - Application layer: Оркестрирует логику для достижения бизнес-цели. Он вызывает бизнес-логику, находящуюся в бизнес сущностях и взаимодействуюет с другими компонентами системы через интерфейсы.
- Primary adapters layer (API): Точки входа в приложение. Принимают входные данные от пользователя и упаковывают их в форму, удобную для application layer, затем возвращают данные в форме, удобной для их отображения пользователю (HTTP, HTML, JSON, CLI и т.д.).
- Secondary adapters layer (Infra): Содержит технические инструменты (такие как репозитории, доступ к внешним API/сервисам, брокеры сообщений, платформы и т.д.) и адаптирует ввод/вывод к интерфейсу, который соответствует потребностям application layer.
- Frameworks: Фреймворки, инструменты, технологии, библиотеки и т.д.
Создание системы, обладающей следующими характеристиками:
- Независимость от фреймворков. Архитектура не зависит от наличия какой-либо библиотеки. Это позволяет рассматривать фреймворки как инструменты, вместо того чтобы стараться втиснуть систему в их рамки.
- Простота тестирования. Бизнес-правила можно тестировать без пользовательского интерфейса, базы данных, веб-сервера и любых других внешних элементов.
- Независимость от пользовательского интерфейса. Пользовательский интерфейс можно легко изменять, не затрагивая остальную систему. Например, веб-интерфейс можно заменить консольным интерфейсом, не изменяя бизнес-правил.
- Независимость от базы данных. Можно поменять Oracle или SQL Server на Mongo, BigTable, CouchDB или что-то еще. Бизнес-логика не привязана к базе данных.
- Независимость от любых внешних агентов. Бизнес-логика ничего не знает об интерфейсах, ведущих во внешний мир.
Главным правилом, приводящим эту архитектуру в действие, является правило зависимостей: Зависимости в исходном коде должны быть направлены внутрь, в сторону высокоуровневых политик.
У микросервиса есть 2 точки взаимодействия с внешним миром:
- Primary adapters (API): адаптеры, которые управляют приложением реализуют взаимодействие с системой через REST, очередь сообщений и CLI. Клиенты таких адаптеров могут быть конечные пользователи, другие микросервисы, консьюмеры очередей сообщений, технические пользователи, крон джобы и т.д.
- Secondary adapters (Infra): адаптеры, управляемые приложением реализуют взаимодействие системы с внешними зависимостями - БД, cистема распределенной блокировки, система аутентификации и авторизации, очередь сообщений, внешние интеграции по HTTP.
Диаграмма ниже показывает взаимодействие компонентов системы в сценарии добавления пользователем товара в корзину:
Для обработки этого сценария система использует:
- веб-фреймворк FastAPI для принятия HTTP запроса и формирования HTTP ответа
- база данных Redis для установления блокировки на изменение состояние корзины
- систему аутентификации и авторизации на основе JWT для идентификации пользователя
- набор компонентов для доступа к данным БД PostgreSQL:
- Sqlalchemy - ORM
- UnitOfWork - абстракция для работы с данными в одном транзакционном контексте
- Repository - компоненты, в которых инкапсулирована логика формирования sql запросов и получение бизнес-сущностей из данных в БД
- бизнес-сущности
Cart
,CartConfig
,CarItemt
, в которых инкапсулирована бизнес-логика микросервиса - набор компонентов для взаимодействия с внешней интеграцией products (микросервис каталога):
- aiohttp - инструмент для HTTP запросов
- клиенты, в которых инкапсулирована логика формирования HTTP запросов к внешним интеграциям
- транспорт - абстракция, которая формирует HTTP запрос через aiohttp
- система ретраев - обеспечивает повторы запросов в случае кратковременной неработоспособности внешней интеграции
На диаграмме показан примерный поток управления для данного сценария:
- Веб-фреймворк FastAPI:
- принимает HTTP запрос от пользователя
- формирует HTTP ответ пользователю
- Контроллер
app.api.rest.public.v1.cart_items.controllers.add_item
:- принимает исходные данные HTTP запроса
- принимает объект
AddCartItemUseCase
через механизм dependency injection - упаковывает данные в
AddItemToCartInputDTO
- вызывает метод объекта
AddCartItemUseCase.execute
и передает в негоAddItemToCartInputDTO
- обрабатывает результат работы метода и передает его в
CartViewModel
или выбрасывает HTTP исключение в случае ошибок, полученных из метода
- Use case
AddCartItemUseCase
:- принимает
AddItemToCartInputDTO
- с помощью
RedisLockSystem
устанавливает блокировку на изменение корзины - обрабатывает авторизационный токен через
JWTAuthSystem
и получает данные о пользователе - через транзакционный контекст
SqlAlchemyUnitOfWork
и репозиторийSqlAlchemyCartsRepository
получает бизнес-объектCart
- Вызывает бизнес-логику
Cart
:- Проверяет есть ли у пользователя права на редактирование корзины. Если нет, то выкидывает исключение, иначе
- Если товар уже есть в корзине, то прибавляет его кол-во к текущему и возвращает
CartOutputDTO
, иначе
- Отправляет HTTP запрос во внешнюю интеграцию products и получает оттуда данные
- Создает из полученных данных бизнес-объект
CartItem
- Вызывает бизнес-логику
Cart
для добавления товара в корзину - Сохраняет товар в БД через транзакционный контекст
SqlAlchemyUnitOfWork
и репозиторийSqlAlchemyItemsRepository
- Возвращает
CartOutputDTO
- принимает
CartViewModel
применяет логику отображения, согласно контракту между бэкендом и фронтендом.
- FastAPI: Веб-фреймворк для построения REST API
- ARQ: Менеджер очередей
- Typer: Инструмент для взаимодействия с системой через CLI
- Dependency-injector: Фреймворк для внедрения зависимостей
- SQLAlchemy: ORM для взаимодействия с реляционной базой данных
- PostgreSQL: База данных для хранения состояния корзин и товаров
- Redis: Механизм для распределенной блокировки и брокер для очередей
- Aiohttp: Фреймворк для взаимодействия с внешними интеграциями по HTTP
- Pydantic: Инструмент формирования DTO, инкапсуляции логики отображения и хранения настроек сервиса
- Backoff: Механизм для повторной отправки неудавшихся HTTP запросов
- Pytest: Фреймворк для тестирования
Микросервис поддерживает контейнеризацию и может быть развернут в любой современной облачной среде или на собственных серверах.
- Создать файл
.env
в корне проекта. Для локального запуска достаточно будет скопировать все переменные из.env.defaults
- В качестве source root необходимо указать путь до директории src
- Собрать и запустить текущий проект:
make start
- Перейти к документации API: http://0.0.0.0:8000/docs/
Собрать тестовое окружение и запустить тесты:
make test
Чтобы запустить конкретный модуль с тестами или отдельный тест надо добавить аргумент target
make test target=tests/unit
В проекте поддерживаются строгие стандарты кодирования, которые обеспечиваются с помощью линтеров и форматировщиков.
Запустить линтеры, форматтеры и тесты одной командой:
make check