компонуемые .net-блоки для микросервисов

Koto

Koto — это набор небольших NuGet-пакетов, которые аккуратно сшивают Wolverine, Marten, FastEndpoints и EF Core общими правилами игры для DDD, CQRS и event sourcing. Берёте столько, сколько нужно, — остальное спокойно ждёт на полке.

dotnet add package Koto.Domain
смотреть пакеты →

как это выглядит ↓

зачем

DDD в .NET — это обычно либо гора boilerplate, либо лицензионная ловушка

Либо вы в каждом сервисе заново пишете always-valid доменные объекты, обработку ошибок через Result и доставку событий через outbox — либо берёте готовый фреймворк и однажды с удивлением узнаёте, что он тихо стал коммерческим (привет, MediatR v13+, MassTransit v9+, FluentAssertions v8+).

Koto — тонкая прослойка поверх бесплатных и лучших в своём классе библиотек: Wolverine для messaging, Marten для event sourcing, FastEndpoints для HTTP, EF Core для хранения. Конвенции — есть. Boilerplate и юридических сюрпризов — нет.

как это выглядит

Доменная модель, которую нельзя собрать в невалидном состоянии

Валидация живёт в фабричном методе, а не размазана по всему проекту. Ошибка — это не исключение, а обычное значение: Result<T>.

объект-значение

public sealed class Email : ValueObject
{
    public string Value { get; }
    private Email(string value) => Value = value;

    public static Result<Email> Create(string value) =>
        string.IsNullOrWhiteSpace(value) ? Errors.General.ValueIsRequired() :
        value.Length > 150              ? Errors.General.InvalidLength(1, 150) :
                                          new Email(value);
}

корень агрегата

public class Order : AggregateRoot<OrderId>
{
    public static Result<Order> Place(Customer customer, IReadOnlyList<OrderItem> items)
    {
        if (items.Count == 0)
            return OrderErrors.NoItems();

        var order = new Order(OrderId.New(), customer.Id);
        order.AddDomainEvent(new OrderPlacedDomainEvent(order.Id, customer.Id));
        return order;
    }

    public Result<Unit> Cancel(string reason)
    {
        if (Status == OrderStatus.Cancelled)
            return OrderErrors.AlreadyCancelled();

        Status = OrderStatus.Cancelled;
        AddDomainEvent(new OrderCancelledDomainEvent(Id, reason));
        return Unit.Value;
    }
}

как это собирается вместе

Четыре принципа вместо фреймворка

01

Всегда валидный домен

Доменный объект физически нельзя собрать в невалидном состоянии. Вся проверка — в фабричном методе, а не в отдельных валидаторах где-то сбоку.

02

Явные ошибки, от начала до конца

Никаких исключений на ожидаемые сбои. Result<T> едет от домена до самого HTTP-ответа, не теряясь по дороге.

03

События сами долетают до Kafka

Доменное событие попадает в outbox в той же транзакции, что и запись. Дальше — почти автомагически: Wolverine его достаёт, in-process хендлер публикует интеграционное событие, а Kafka разносит по остальным сервисам.

04

Без магии

Никакой рефлексии на горячем пути и скрытых конвенций, которые выстрелят в два часа ночи. Код, который вы написали, — ровно тот код, что выполняется.

доменное событие → Kafka, автоматически

Order.Cancel()
  → AddDomainEvent(OrderCancelledDomainEvent)
  → DbContext.SaveChangesAsync()            // outbox, same transaction
  → Wolverine delivers to in-process handler
  → handler publishes OrderCancelledIntegrationEvent
  → Kafka → other services

пакеты

Четырнадцать пакетов. Берите нужные — остальные не мешают.

Koto.Domain

DDD-примитивы для .NET-микросервисов. Ноль внешних зависимостей.

Koto.Application

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

Koto.Validation

Расширения FluentValidation и pipeline-поведение для CQRS.

Koto.Infrastructure.EFCore

Интеграция с EF Core 10: универсальный репозиторий, типобезопасные ID, спецификации и outbox через Wolverine.

Koto.Infrastructure.Http

Anti-Corruption Layer для синхронных HTTP-вызовов между сервисами.

Koto.EventSourcing.Marten

Event sourcing на PostgreSQL через Marten.

Koto.Messaging.Wolverine

Messaging-абстракции Koto поверх Wolverine и Kafka.

Koto.Messaging.Wolverine.Postgres

Надёжное хранилище идемпотентности на PostgreSQL — дедупликация переживает перезапуски.

Koto.Api.AspNetCore

Транспорт-независимая интеграция с ASP.NET Core — Minimal API, MVC или любой хост.

Koto.Api.FastEndpoints

Интеграция FastEndpoints с DDD-примитивами Koto.

Koto.Observability

Observability одной строкой: Serilog и OpenTelemetry.

Koto.Scheduling

Плановые задачи и пакетная обработка на Quartz.NET.

Koto.Testing

Тестовые хелперы под DDD: Given/When/Then для агрегатов.

Koto.Testing.Integration

Кирпичики для интеграционных тестов: тестовая авторизация через заголовок, проверки Eventually и продюсер raw-JSON в Kafka.

начало работы

Добавьте пакеты, которые нужны

dotnet add package Koto.Domain
dotnet add package Koto.Application
dotnet add package Koto.Infrastructure.EFCore

лицензия mit · опубликовано на nuget

Сделано для настоящего продакшена — и открыто для всех остальных

Koto вырос из живого .NET-бэкенда на микросервисах и переехал в отдельный репозиторий тогда, когда абстракции наконец отработали свой хлеб.

Пакеты на NuGet