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

Koto

Koto — набор небольших, сфокусированных NuGet-пакетов, которые связывают Wolverine, Marten, FastEndpoints и EF Core общими конвенциями для DDD, CQRS и Event Sourcing. Берите что нужно — остальное не мешает.

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

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

зачем

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

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

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

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

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

Валидация живёт в фабричном методе, а не разбросана по кодовой базе. Неудача — это значение, 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

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

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

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

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

Создано для реальной продакшен-системы, открыто для всех остальных

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

Пакеты на NuGet