Модульная монолитная архитектура
Alifshop API реализует модульную монолитную архитектуру - подход, объединяющий преимущества монолитной и микросервисной архитектур.
Структура модулей
Система состоит из 6 основных модулей, каждый из которых является отдельным вертикальным срезом:
Alif.ServiceShop.API/
├── Modules/
│ ├── Catalog/
│ │ ├── Domain/
│ │ ├── Application/
│ │ ├── Infrastructure/
│ │ └── IntegrationEvents/
│ ├── Cart/
│ ├── Orders/
│ ├── Marketing/
│ ├── Moderation/
│ └── Afghanistan/
└── BuildingBlocks/
├── Domain/
├── Application/
├── Infrastructure/
└── EventBus/
Принципы модульности
1. Инкапсуляция
Каждый модуль скрывает внутренние детали:
- Внутренние сущности недоступны другим модулям
- Публичные контракты через интерфейсы
- Данные изолированы по модулям
2. Единое развертывание
Все модули развертываются как единое приложение:
- Один процесс для всех модулей
- Общие ресурсы (память, CPU)
- Единая точка входа через API Gateway
3. Межмодульное взаимодействие
Модули взаимодействуют через определенные интерфейсы:
// Синхронные вызовы через Module Facades
public interface ICatalogModuleFacade
{
Task<ProductDto> GetProductAsync(int productId);
Task<IEnumerable<ProductDto>> GetProductsAsync(int[] productIds);
}
// Асинхронные события через Integration Events
public class ProductPriceChangedEvent : IntegrationEvent
{
public int ProductId { get; }
public decimal NewPrice { get; }
public decimal OldPrice { get; }
}
Архитектура модуля
Каждый модуль следует Clean Architecture:
Domain Layer
// Доменные сущности
public class Product : Entity
{
public string Name { get; private set; }
public Money Price { get; private set; }
public void UpdatePrice(Money newPrice)
{
if (Price != newPrice)
{
Price = newPrice;
AddDomainEvent(new ProductPriceChangedEvent(Id, newPrice));
}
}
}
Application Layer
// Use Cases через CQRS
public class UpdateProductPriceCommand : IRequest
{
public int ProductId { get; set; }
public decimal NewPrice { get; set; }
}
public class UpdateProductPriceHandler : IRequestHandler<UpdateProductPriceCommand>
{
private readonly IProductRepository _repository;
public async Task<Unit> Handle(UpdateProductPriceCommand request, CancellationToken cancellationToken)
{
var product = await _repository.GetByIdAsync(request.ProductId);
product.UpdatePrice(new Money(request.NewPrice));
await _repository.UpdateAsync(product);
return Unit.Value;
}
}
Infrastructure Layer
// Реализация репозиториев
public class ProductRepository : IProductRepository
{
private readonly CatalogDbContext _context;
public async Task<Product> GetByIdAsync(int id)
{
return await _context.Products.FindAsync(id);
}
}
// Конфигурация модуля
public class CatalogModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ProductRepository>()
.As<IProductRepository>()
.InstancePerLifetimeScope();
}
}
Регистрация модулей
Модули регистрируются в точке входа приложения:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Регистрация модулей
services.AddCatalogModule(Configuration);
services.AddCartModule(Configuration);
services.AddOrdersModule(Configuration);
services.AddMarketingModule(Configuration);
services.AddModerationModule(Configuration);
services.AddAfghanistanModule(Configuration);
}
// Catalog Module Registration
public static class CatalogModuleRegistration
{
public static IServiceCollection AddCatalogModule(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddDbContext<CatalogDbContext>(options =>
options.UseMySql(configuration.GetConnectionString("Catalog")));
services.AddMediatR(typeof(CatalogModule).Assembly);
return services;
}
}
Межмодульные интеграции
Integration Events
Асинхронная связь через события:
// Публикация события
public class OrderCreatedHandler : INotificationHandler<OrderCreatedEvent>
{
private readonly IEventBus _eventBus;
public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
{
// Публикация события для других модулей
await _eventBus.PublishAsync(new OrderCreatedIntegrationEvent
{
OrderId = notification.OrderId,
CustomerId = notification.CustomerId,
Items = notification.Items
});
}
}
// Обработка события в другом модуле
public class UpdateInventoryHandler : IIntegrationEventHandler<OrderCreatedIntegrationEvent>
{
public async Task Handle(OrderCreatedIntegrationEvent @event)
{
// Обновление остатков в модуле Catalog
foreach (var item in @event.Items)
{
await _inventoryService.ReserveItemAsync(item.ProductId, item.Quantity);
}
}
}
Module Facades
Синхронные вызовы между модулями:
// Facade интерфейс
public interface ICatalogModuleFacade
{
Task<ProductDto> GetProductAsync(int productId);
Task<bool> IsProductAvailableAsync(int productId, int quantity);
}
// Реализация в модуле Catalog
public class CatalogModuleFacade : ICatalogModuleFacade
{
private readonly IMediator _mediator;
public async Task<ProductDto> GetProductAsync(int productId)
{
var query = new GetProductQuery { ProductId = productId };
return await _mediator.Send(query);
}
}
// Использование в модуле Cart
public class AddItemToCartHandler : IRequestHandler<AddItemToCartCommand>
{
private readonly ICatalogModuleFacade _catalogFacade;
public async Task<Unit> Handle(AddItemToCartCommand request, CancellationToken cancellationToken)
{
// Проверка доступности товара через фасад
var isAvailable = await _catalogFacade.IsProductAvailableAsync(
request.ProductId, request.Quantity);
if (!isAvailable)
throw new ProductNotAvailableException();
// Добавление в корзину
// ...
}
}
Преимущества модульной архитектуры
Разработка
- Команды могут работать независимо над модулями
- Тестирование модулей изолированно
- Refactoring внутри модуля не влияет на другие
Deployment
- Единое развертывание проще чем микросервисы
- Отсутствие сетевых вызовов между модулями
- Упрощенная инфраструктура
Эволюция
- Готовность к микросервисам - модули можно выделить
- Постепенная миграция по мере роста системы
- Сохранение целостности данных
Ограничения и компромиссы
Развертывание
- Все модули развертываются вместе
- Общие ресурсы - невозможно масштабировать отдельные модули
- Единая точка отказа
Технологический стек
- Единый стек для всех модулей
- Общие зависимости и версии библиотек
- Ограничения на выбор технологий
Модульная монолитная архитектура обеспечивает баланс между простотой развертывания и гибкостью разработки, позволяя команде эффективно работать над сложной доменной логикой.