Документация

Koto.Application

CQRS-диспетчер, pipeline-поведения и абстракции межсервисной интеграции.

Установка

dotnet add package Koto.Application

Что внутри

ТипНазначение
ICommand / ICommand<TResult>Маркерные интерфейсы для операций записи (ICommandBase)
IQuery<TResult>Маркерный интерфейс для операций чтения (IQueryBase)
ICommandHandler<T> / ICommandHandler<T, TResult>Контракты обработчиков команд
IQueryHandler<TQuery, TResult>Контракт обработчика запроса
ICqrsDispatcherОтправляет команды и запросы зарегистрированным обработчикам
IPipelineBehavior<TRequest, TResponse>Middleware для CQRS-пайплайна
KotoApplicationOptionsОпциональная регистрация поведений (AddLoggingBehavior(), AddTransactionBehavior(), AddBehavior(type))
LoggingBehavior<,>Логирует каждую команду/запрос с замером времени
TransactionBehavior<,>Оборачивает каждую команду в транзакцию IUnitOfWork
IUnitOfWorkАбстракция коммита транзакции БД
IRepository<TAgg, TId>Контракт персистентности для агрегатов (в паре с IUnitOfWork)
IIntegrationEvent / IntegrationEventКонтракт внешнего события
IIntegrationCommand / IIntegrationCommand<TResult>Контракт межсервисной команды
IIntegrationEventPublisherПубликует интеграционные события (реализация в инфраструктуре)
IIntegrationCommandDispatcherОтправляет интеграционные команды (реализация в инфраструктуре)
AssemblyScanningGetLoadableTypes — защита сканирования от ReflectionTypeLoadException

Использование

Регистрация

// Только обработчики — без pipeline-поведений:
builder.Services.AddKotoApplication(typeof(Program).Assembly);

// С поведениями (опционально; порядок регистрации = порядок выполнения, первое — самое внешнее):
builder.Services.AddKotoApplication(
    o => o.AddLoggingBehavior().AddTransactionBehavior(),
    typeof(Program).Assembly);
builder.Services.AddKotoValidation(typeof(Program).Assembly); // ValidationBehavior из Koto.Validation
// Рекомендуемый порядок: Logging → Validation → Transaction.

Реализация команды

public sealed record PlaceOrderCommand(Guid CustomerId, List<OrderItem> Items)
    : ICommand<OrderId>;

public sealed class PlaceOrderHandler : ICommandHandler<PlaceOrderCommand, OrderId>
{
    public async Task<Result<OrderId>> HandleAsync(PlaceOrderCommand cmd, CancellationToken ct)
    {
        // бизнес-логика
    }
}

Отправка

var result = await _dispatcher.SendAsync(new PlaceOrderCommand(customerId, items), ct);

Pipeline-поведения

Поведения резолвятся относительно конкретного типа команды/запроса: открытая generic-регистрация вроде ValidationBehavior<,> замыкается на PlaceOrderCommand в момент отправки, поэтому находится IValidator<PlaceOrderCommand>. Поведение также может быть нацелено на один конкретный запрос через IPipelineBehavior<PlaceOrderCommand, Result<OrderId>>.

// Собственное открытое generic-поведение:
builder.Services.AddKotoApplication(
    o => o.AddLoggingBehavior().AddBehavior(typeof(MyMetricsBehavior<,>)),
    typeof(Program).Assembly);

Заметки по дизайну

  • IDomainEvent — внутренний; можно менять свободно; никогда не пересекает границу сервиса.
  • IIntegrationEvent — внешний контракт; версионируется; публикуется в Kafka через Wolverine.
  • IIntegrationCommand — fire-and-forget команда другому сервису; IIntegrationCommand<TResult> ожидает ответ.