Cart Module
Cart модуль является ключевым компонентом системы Alifshop API, обеспечивающий полноценное управление корзинами покупок, списками желаний и общими корзинами с поддержкой модерируемых товаров.
Обзор модуля
Cart модуль построен по принципам Clean Architecture и Domain-Driven Design (DDD) с использованием CQRS паттерна для разделения команд и запросов.
Основные функции:
- Управление корзинами - добавление, удаление, изменение товаров
- Wishlist функциональность - списки желаний пользователей
- SharedCarts - общие корзины для совместных покупок
- Moderated Items - товары, требующие модерации
- Business Rules - валидация лимитов и ограничений
- Background Jobs - автоматическая очистка и обслуживание
Поддерживаемые типы покупателей:
- Авторизованные пользователи - полный функционал
- Неавторизованные пользователи - гостевые корзины
- MiniApp пользователи - упрощенный интерфейс
Архитектура
Структура проектов
Cart модуль разделен на несколько проектов согласно Clean Architecture:
Alif.ServiceShop.Modules.Cart.Domain/
├── Aggregates/
│ ├── Cart/ # Основной aggregate корзины
│ ├── SharedCarts/ # Общие корзины
│ └── WishlistAggregate/ # Списки желаний
├── BusinessRules/ # Бизнес-правила
├── Events/ # Domain Events
└── Services/ # Domain Services
Alif.ServiceShop.Modules.Cart.Application/
├── Commands/ # CQRS Commands
├── Queries/ # CQRS Queries
├── Handlers/ # Command/Query Handlers
├── ViewModels/ # DTO/ViewModels
└── Services/ # Application Services
Alif.ServiceShop.Modules.Cart.Infrastructure/
├── Repositories/ # Data Access
├── Migrations/ # Database Migrations
├── BackgroundJobs/ # Scheduled Jobs
└── Configuration/ # DI Configuration
Alif.ServiceShop.Modules.Cart.IntegrationEvents/
├── Incoming/ # Внешние события
└── Outgoing/ # Исходящие события
Основные Aggregates
1. Cart Aggregate
Root Entity: Cart.cs
public class Cart : AggregateRoot
{
public CartId Id { get; private set; }
public BuyerId BuyerId { get; private set; }
public List<CartItem> Items { get; private set; }
public List<ModeratedCartItem> ModeratedItems { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime? UpdatedAt { get; private set; }
}
Entities:
CartItem
- обычные товары в корзинеModeratedCartItem
- товары, требующие модерации
Value Objects:
BnplDetails
- детали BNPL (Buy Now Pay Later)LoanCondition
- условия кредитованияPaymentMethod
- способы оплатыPurchaseRestrictions
- ограничения покупки
2. SharedCarts Aggregate
Root Entity: SharedCart.cs
public class SharedCart : AggregateRoot
{
public SharedCartId Id { get; private set; }
public BuyerId CreatorId { get; private set; }
public List<BuyerId> Participants { get; private set; }
public List<Item> Items { get; private set; }
public DateTime ExpiresAt { get; private set; }
}
3. Wishlist Aggregate
Root Entity: Wishlist.cs
public class Wishlist : AggregateRoot
{
public WishlistId Id { get; private set; }
public BuyerId BuyerId { get; private set; }
public List<Offer> Offers { get; private set; }
public List<ModeratedOffer> ModeratedOffers { get; private set; }
}
Domain Layer
Business Rules
Cart модуль содержит набор бизнес-правил для обеспечения корректности операций:
Основные Business Rules:
// Ограничения покупки для авторизованных пользователей
public class AuthorizedBuyerPurchaseLimitRule : IBusinessRule
{
public bool IsBroken() => // Логика проверки лимитов
public string Message => "Превышен лимит покупки для авторизованного пользователя";
}
// Обязательность условий кредита при рассрочке
public class CartItemsMustHaveLoanConditionByInstallmentPurchaseRule : IBusinessRule
{
public bool IsBroken() => // Проверка наличия условий кредита
public string Message => "При покупке в рассрочку необходимы условия кредитования";
}
// Проверка количества на складе
public class QuantityMustNotBeMoreThenInWarehouseRule : IBusinessRule
{
public bool IsBroken() => // Проверка остатков
public string Message => "Количество товара превышает остаток на складе";
}
Domain Services
public interface ICheckOfferPurchaseLimitForBuyer
{
Task<bool> CanBuyOfferAsync(BuyerId buyerId, OfferId offerId, int quantity);
}
public interface IOfferQuantityInWarehouseChecker
{
Task<int> GetAvailableQuantityAsync(OfferId offerId);
}
public interface IOrderCreator
{
Task<OrderId> CreateOrderAsync(CartId cartId, PaymentMethod paymentMethod);
}
Domain Events
public class ItemAddedToCartDomainEvent : DomainEventBase
{
public CartId CartId { get; }
public OfferId OfferId { get; }
public int Quantity { get; }
public DateTime OccurredOn { get; }
}
public class CartCreatedDomainEvent : DomainEventBase
{
public CartId CartId { get; }
public BuyerId BuyerId { get; }
public DateTime OccurredOn { get; }
}
Application Layer
Commands (CQRS)
Основные Cart Commands:
// Добавление товара в корзину
public class AddItemToCartCommand : IRequest<Unit>
{
public int? ClientId { get; set; }
public string SessionId { get; set; }
public int OfferId { get; set; }
public int Quantity { get; set; }
public int? LoanConditionId { get; set; }
}
// Изменение количества товара
public class ChangeItemQuantityCommand : IRequest<Unit>
{
public int? ClientId { get; set; }
public string SessionId { get; set; }
public int OfferId { get; set; }
public int NewQuantity { get; set; }
}
// Создание заказа
public class CreateOrderCommand : IRequest<CreateOrderResult>
{
public int? ClientId { get; set; }
public string SessionId { get; set; }
public int PaymentMethodId { get; set; }
public string DeliveryAddress { get; set; }
}
Moderated Items Commands:
public class AddModeratedItemToCartCommand : IRequest<Unit>
{
public int? ClientId { get; set; }
public string SessionId { get; set; }
public int ModeratedOfferId { get; set; }
public int Quantity { get; set; }
}
Background Commands:
public class AutoDeleteAbandonedCartCommand : IRequest<Unit>
{
public int DaysThreshold { get; set; } = 14;
}
public class AutoDelete60DaysOldWishlistCommand : IRequest<Unit>
{
public int DaysThreshold { get; set; } = 60;
}
Queries
// Получение корзины
public class ViewCartQuery : IRequest<ViewCartVm>
{
public int? ClientId { get; set; }
public string SessionId { get; set; }
}
// Получение списка желаний
public class GetWishlistQuery : IRequest<WishlistVm>
{
public int? ClientId { get; set; }
public string SessionId { get; set; }
}
// Получение общей корзины
public class GetSharedCartQuery : IRequest<SharedCartVm>
{
public Guid SharedCartId { get; set; }
public int RequesterId { get; set; }
}
ViewModels
public class ViewCartVm
{
public List<CartItemVm> Items { get; set; } = new();
public List<ModeratedCartItemVm> ModeratedItems { get; set; } = new();
public decimal TotalAmount { get; set; }
public int TotalItemsCount { get; set; }
public List<PaymentMethodVm> AvailablePaymentMethods { get; set; } = new();
}
public class CartItemVm
{
public int OfferId { get; set; }
public string Title { get; set; }
public decimal Price { get; set; }
public decimal? OldPrice { get; set; }
public int Quantity { get; set; }
public List<ItemImageVm> Images { get; set; } = new();
public BnplDetailsVm BnplDetails { get; set; }
public LoanConditionVm LoanCondition { get; set; }
public PurchaseRestrictionsVm PurchaseRestrictions { get; set; }
}
API Endpoints
Cart Controller
POST /api/cart/items # Добавить товар в корзину
GET /api/cart/items # Получить содержимое корзины
DELETE /api/cart/items/{offerId} # Удалить товар из корзины
PATCH /api/cart/items/quantity # Изменить количество товара
PATCH /api/cart/items/condition # Изменить условия кредитования
DELETE /api/cart/items # Очистить корзину
POST /api/cart/merge-carts # Объединить корзины при авторизации
POST /api/cart/orders # Создать заказ из корзины
POST /api/cart/orders-v2 # Создать заказ с выбранными товарами
DELETE /api/cart/items/bulk # Удалить выбранные товары
MiniApp Endpoints
POST /api/cart/mini-app/items # Добавить товар (MiniApp)
GET /api/cart/mini-app/items # Получить корзину (MiniApp)
DELETE /api/cart/mini-app/items/{offerId} # Удалить товар (MiniApp)
Moderated Items Endpoints
POST /api/cart/moderated-items # Добавить модерируемый товар
DELETE /api/cart/moderated-items/{moderatedOfferId} # Удалить модерируемый товар
PATCH /api/cart/moderated-items/quantity # Изменить количество
PATCH /api/cart/moderated-items/condition # Изменить условия кредитования
SharedCart Controller
POST /api/shared-carts # Создать общую корзину
GET /api/shared-carts/{id} # Получить общую корзину
GET /api/shared-carts/count # Количество общих корзин пользователя
Wishlist Controller
POST /api/wishlist/offers # Добавить в список желаний
GET /api/wishlist # Получить список желаний
DELETE /api/wishlist/offers/{offerId} # Удалить из списка желаний
POST /api/wishlist/merge-wishlists # Объединить списки желаний
Бизнес-правила
Система лимитов покупки
Для авторизованных пользователей:
- Дневные лимиты - ограничения на сумму покупок в день
- Товарные лимиты - ограничения по конкретным товарам
- Кредитные лимиты - ограничения при покупке в рассрочку
Для неавторизованных пользователей:
- Более строгие лимиты на количество и сумму
- Ограниченные способы оплаты
- Обязательная авторизация для крупных покупок
Управление остатками
public class InventoryValidationRule
{
// Проверка доступности товара на складе
public async Task<bool> IsQuantityAvailable(int offerId, int requestedQuantity)
{
var availableQuantity = await _warehouseService.GetAvailableQuantity(offerId);
return requestedQuantity <= availableQuantity;
}
// Резервирование товара в корзине
public async Task ReserveItem(int offerId, int quantity, TimeSpan reservationTime)
{
await _warehouseService.ReserveItem(offerId, quantity, reservationTime);
}
}
Условия кредитования
- Обязательность при рассрочке - товары в рассрочку должны иметь условия кредитования
- Исключение при оплате картой - при оплате картой условия кредитования не применяются
- Проверка кредитоспособности партнера - партнер должен иметь активные кредитные условия
Integration Events
Входящие события (подписки)
Catalog Module Events:
// Активация/деактивация предложений
public class OfferActivatedIntegrationEvent : IntegrationEvent
{
public int OfferId { get; set; }
public DateTime ActivatedAt { get; set; }
}
public class OfferDeactivatedIntegrationEvent : IntegrationEvent
{
public int OfferId { get; set; }
public string Reason { get; set; }
}
// Изменения цен и количества
public class OfferPriceChangedIntegrationEvent : IntegrationEvent
{
public int OfferId { get; set; }
public decimal OldPrice { get; set; }
public decimal NewPrice { get; set; }
}
public class OfferQuantityChangedIntegrationEvent : IntegrationEvent
{
public int OfferId { get; set; }
public int NewQuantity { get; set; }
}
Orders Module Events:
public class OrdersCreatedIntegrationEvent : IntegrationEvent
{
public List<int> OrderIds { get; set; }
public int ClientId { get; set; }
public List<int> ProcessedOfferIds { get; set; }
}
Исходящие события
Cart модуль предоставляет Facade для других модулей:
public interface ICartModuleFacade
{
Task<List<string>> GetSessionIdsByDaysInCart(int days);
Task<List<string>> GetClientIdsByDaysInCartAsync(int days);
Task<CartDetailsForOrderVm> GetCartDetailsForOrderAsync(string cartId);
Task RemoveCartOnOrderCreated(int clientId);
Task RemoveSelectedItemsFromCart(int clientId, List<int> offerIds);
Task<CartDetailsForOrderVm> GetCartByClientIdAsync(int clientId);
}
Background Jobs
Scheduled Jobs
Автоматическая очистка данных:
[DisableConcurrentExecution(60)]
public class AutoDeleteAbandonedCartsJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
// Удаление корзин старше 14 дней
var command = new AutoDeleteAbandonedCartCommand { DaysThreshold = 14 };
await _mediator.Send(command);
}
}
[DisableConcurrentExecution(60)]
public class AutoDelete60DaysOldWishlistsJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
// Удаление списков желаний старше 60 дней
var command = new AutoDelete60DaysOldWishlistCommand { DaysThreshold = 60 };
await _mediator.Send(command);
}
}
Конфигурация Jobs
{
"Quartz": {
"AutoDeleteAbandonedCarts": {
"CronExpression": "0 0 2 * * ?", // Каждый день в 2:00
"Enabled": true
},
"AutoDeleteOldWishlists": {
"CronExpression": "0 0 3 * * ?", // Каждый день в 3:00
"Enabled": true
}
}
}
Database Schema
MongoDB Collections
AuthenticatedBuyerCarts
{
"_id": ObjectId,
"buyerId": NumberInt,
"items": [{
"offerId": NumberInt,
"quantity": NumberInt,
"loanConditionId": NumberInt,
"addedAt": ISODate,
"updatedAt": ISODate
}],
"moderatedItems": [{
"moderatedOfferId": NumberInt,
"quantity": NumberInt,
"loanConditionId": NumberInt,
"addedAt": ISODate
}],
"createdAt": ISODate,
"updatedAt": ISODate
}
UnAuthenticatedBuyerCarts
{
"_id": ObjectId,
"sessionId": "string",
"items": [/* аналогично AuthenticatedBuyerCarts */],
"createdAt": ISODate,
"updatedAt": ISODate,
"expiresAt": ISODate // TTL index для автоудаления
}
SQL Server Tables
DomainEvents
CREATE TABLE [cart].[DomainEvents] (
[Id] uniqueidentifier NOT NULL,
[OccurredOn] datetime2 NOT NULL,
[Type] nvarchar(255) NOT NULL,
[Data] nvarchar(max) NOT NULL,
[ProcessedDate] datetime2 NULL
);
InboxMessages / OutboxMessages
CREATE TABLE [cart].[InboxMessages] (
[Id] uniqueidentifier NOT NULL,
[OccurredOn] datetime2 NOT NULL,
[Type] nvarchar(255) NOT NULL,
[Data] nvarchar(max) NOT NULL,
[ProcessedDate] datetime2 NULL
);
Тестирование
Unit Tests
Domain Tests
public class CartTests
{
[Fact]
public void AddItem_WhenValidItem_ShouldAddSuccessfully()
{
// Arrange
var cart = Cart.Create(BuyerId.Of(1));
var item = CartItem.Create(OfferId.Of(100), 2);
// Act
cart.AddItem(item);
// Assert
Assert.Single(cart.Items);
Assert.Equal(2, cart.Items.First().Quantity);
}
[Fact]
public void AddItem_WhenExceedsLimit_ShouldThrowException()
{
// Arrange & Act & Assert
var cart = Cart.Create(BuyerId.Of(1));
var item = CartItem.Create(OfferId.Of(100), 1000); // Превышает лимит
Assert.Throws<BusinessRuleValidationException>(() => cart.AddItem(item));
}
}
Application Tests
public class AddItemToCartCommandHandlerTests
{
[Fact]
public async Task Handle_WhenValidCommand_ShouldAddItemToCart()
{
// Arrange
var command = new AddItemToCartCommand
{
ClientId = 1,
OfferId = 100,
Quantity = 2
};
// Act
await _handler.Handle(command, CancellationToken.None);
// Assert
_cartRepository.Verify(x => x.SaveAsync(It.IsAny<Cart>()), Times.Once);
}
}
Integration Tests
public class CartControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task AddItemToCart_WhenValidRequest_ShouldReturn200()
{
// Arrange
var request = new AddItemToCartRequest
{
OfferId = 100,
Quantity = 2
};
// Act
var response = await _client.PostAsJsonAsync("/api/cart/items", request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Cart модуль представляет собой сложную систему управления корзинами с поддержкой различных типов пользователей, модерируемых товаров, бизнес-правил и интеграций с другими модулями платформы Alifshop.