Модульная монолитная архитектура

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

  • Единое развертывание проще чем микросервисы
  • Отсутствие сетевых вызовов между модулями
  • Упрощенная инфраструктура

Эволюция

  • Готовность к микросервисам - модули можно выделить
  • Постепенная миграция по мере роста системы
  • Сохранение целостности данных

Ограничения и компромиссы

Развертывание

  • Все модули развертываются вместе
  • Общие ресурсы - невозможно масштабировать отдельные модули
  • Единая точка отказа

Технологический стек

  • Единый стек для всех модулей
  • Общие зависимости и версии библиотек
  • Ограничения на выбор технологий

Модульная монолитная архитектура обеспечивает баланс между простотой развертывания и гибкостью разработки, позволяя команде эффективно работать над сложной доменной логикой.