Skip to content

Akim112/online-store

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gozon — Микросервисная система интернет-магазина

Система интернет-магазина "Gozon", построенная на микросервисной архитектуре с асинхронным межсервисным взаимодействием через RabbitMQ. Реализует паттерны Transactional Outbox и Transactional Inbox для гарантированной доставки сообщений и exactly-once семантики для финансовых операций.

Архитектура

Система состоит из следующих компонентов:

Микросервисы

  • API Gateway (ApiGateway) — единая точка входа для всех клиентских запросов, маршрутизация на соответствующие сервисы
  • Orders Service (OrdersService) — управление заказами: создание, просмотр списка, получение статуса
  • Payments Service (PaymentsService) — управление платежами: создание счёта, пополнение баланса, просмотр баланса

Инфраструктура

  • RabbitMQ — брокер сообщений для асинхронной коммуникации между сервисами
  • PostgreSQL — реляционная БД (отдельная база для каждого микросервиса)
  • Frontend — веб-интерфейс на HTML/CSS/JavaScript

План взаимодействия микросервисов

Диаграмма архитектуры системы

Поток данных

Frontend → API Gateway → Orders Service
                              ↓ (async)
                         RabbitMQ (payment.requests)
                              ↓
                        Payments Service
                              ↓ (async)
                         RabbitMQ (payment.results)
                              ↓
                        Orders Service (обновление статуса)

Требования

  • Docker и Docker Compose
  • .NET 9.0 SDK

Быстрый старт

Запуск всей системы

docker compose up --build

Система автоматически:

  • Создаст и инициализирует все базы данных
  • Настроит очереди RabbitMQ
  • Запустит все микросервисы с правильными зависимостями

Доступные сервисы

После запуска доступны:

Остановка

docker compose down

Для полной очистки данных:

docker compose down -v

API Endpoints

Все запросы к API должны содержать заголовок X-User-Id с идентификатором пользователя (строка).

Payments Service

Создание счёта

POST /api/payments/account
X-User-Id: user-1

Ответ:

{
  "balanceRub": 0.00
}

Пополнение баланса

POST /api/payments/account/topup
X-User-Id: user-1
Content-Type: application/json

{
  "amountRub": 1000.00
}

Ответ:

{
  "balanceRub": 1000.00
}

Получение баланса

GET /api/payments/account/balance
X-User-Id: user-1

Ответ:

{
  "balanceRub": 1000.00
}

Orders Service

Создание заказа

POST /api/orders
X-User-Id: user-1
Content-Type: application/json

{
  "amountRub": 250.00,
  "description": "Заказ товаров"
}

Ответ:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "amountRub": 250.00,
  "description": "Заказ товаров",
  "status": "NEW",
  "createdAt": "2025-12-15T10:30:00Z"
}

Примечание: Создание заказа асинхронно запускает процесс оплаты через RabbitMQ. Статус заказа обновится после обработки платежа.

Список заказов

GET /api/orders
X-User-Id: user-1

Ответ:

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "amountRub": 250.00,
    "description": "Заказ товаров",
    "status": "FINISHED",
    "createdAt": "2025-12-15T10:30:00Z"
  }
]

Получение заказа по ID

GET /api/orders/{orderId}
X-User-Id: user-1

Ответ:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "amountRub": 250.00,
  "description": "Заказ товаров",
  "status": "FINISHED",
  "createdAt": "2025-12-15T10:30:00Z"
}

Статусы заказа

  • NEW — заказ создан, ожидает оплаты
  • FINISHED — оплата успешно выполнена
  • CANCELLED — оплата отклонена (недостаточно средств или другая ошибка)

Гарантии доставки и обработки сообщений

At-Least-Once доставка

RabbitMQ обеспечивает доставку сообщений минимум один раз. Consumer подтверждает сообщение (ack) только после успешной обработки в БД. При ошибке выполняется nack с requeue=true для повторной обработки.

Transactional Outbox (Orders Service)

При создании заказа в одной транзакции:

  1. Запись заказа в таблицу orders
  2. Запись сообщения в таблицу outbox_messages

Фоновый воркер (OrdersOutboxPublisherHostedService):

  • Периодически опрашивает outbox_messages на наличие неопубликованных сообщений
  • Публикует их в RabbitMQ (payment.requests)
  • Помечает как опубликованные (published_at) только после подтверждения от RabbitMQ (publisher-confirm)

Это гарантирует, что сообщение будет доставлено даже при сбое после коммита транзакции.

Transactional Inbox + Outbox (Payments Service)

Inbox для идемпотентности

При получении сообщения из RabbitMQ:

  1. Проверяется наличие message_id в таблице inbox_messages
  2. Если сообщение уже обработано — пропускается (no-op)
  3. Если новое — записывается в inbox_messages и обрабатывается

Это обеспечивает идемпотентность обработки дубликатов сообщений.

Exactly-Once списание денег

Для гарантии exactly-once списания используется комбинация:

  1. Таблица processed_orders — хранит уникальные order_id обработанных заказов
  2. Атомарное обновление баланса:
    UPDATE accounts 
    SET balance_cents = balance_cents - @AmountCents 
    WHERE user_id = @UserId 
      AND balance_cents >= @AmountCents
  3. Проверка уникальности: попытка вставить order_id в processed_orders с проверкой конфликта

Если заказ уже обработан, повторная обработка не приводит к повторному списанию.

Outbox для результатов

Результат оплаты записывается в outbox_messages и публикуется воркером в очередь payment.results.

Итоговые гарантии

  • At-least-once доставка: каждое сообщение доставлено минимум один раз
  • Exactly-once обработка: каждое сообщение обработано ровно один раз (через Inbox)
  • Exactly-once списание: каждый заказ списан ровно один раз (через processed_orders + атомарное обновление)

Структура проекта

OnlineStore/
├── src/
│   ├── ApiGateway/          # API Gateway сервис
│   ├── OrdersService/        # Сервис заказов
│   │   ├── Background/       # Фоновые задачи (Outbox, Consumer)
│   │   ├── Infrastructure/   # БД, RabbitMQ подключения
│   │   └── OpenApi/          # Swagger конфигурация
│   ├── PaymentsService/      # Сервис платежей
│   │   ├── Background/       # Фоновые задачи (Inbox, Outbox, Consumer)
│   │   ├── Infrastructure/   # БД, RabbitMQ подключения
│   │   └── OpenApi/          # Swagger конфигурация
│   └── Shared/               # Общие контракты и утилиты
│       ├── Messaging/        # Контракты сообщений
│       └── Money.cs          # Конвертация денег
├── frontend/                 # Веб-интерфейс
│   ├── index.html
│   ├── app.js
│   ├── nginx.conf
│   └── Dockerfile
├── docs/
├── postman/                  
├── docker-compose.yml        
└── README.md

Технические детали

Денежные суммы

  • API: суммы в рублях (decimal) с точностью до 2 знаков после запятой
  • Хранение: суммы в копейках (long) для предотвращения ошибок округления
  • Конвертация: утилиты Money.RubToCents() и Money.CentsToRub() в проекте Shared

Базы данных

  • Orders DB: таблицы orders, outbox_messages
  • Payments DB: таблицы accounts, inbox_messages, processed_orders, payment_results, outbox_messages

Все таблицы создаются автоматически при первом запуске через HostedService.

Сообщения RabbitMQ

Формат сообщения

{
  "messageId": "uuid",
  "messageType": "PaymentRequested",
  "createdAt": "2025-12-15T10:30:00Z",
  "correlationId": "order-id",
  "payload": {
    "orderId": "uuid",
    "userId": "user-1",
    "amountCents": 25000
  }
}

Очереди

  • payment.requests — запросы на оплату (Orders → Payments)
  • payment.results — результаты оплаты (Payments → Orders)

Идентификация пользователя

Все запросы требуют заголовок X-User-Id (строка). Сервисы извлекают его из HTTP заголовков и используют для фильтрации данных.

Postman коллекция

Готовая коллекция для тестирования API находится в postman/OnlineStore.postman_collection.json.

About

Микросервисная система интернет-магазина Gozon с асинхронной коммуникацией через RabbitMQ. Реализует Transactional Outbox/Inbox паттерны и exactly-once семантику для финансовых операций.

Topics

Resources

License

Stars

Watchers

Forks

Contributors