composable .net building blocks for microservices

Koto

Koto is a set of small, focused NuGet packages that wire together Wolverine, Marten, FastEndpoints, and EF Core with opinionated conventions for DDD, CQRS, and Event Sourcing. Use what you need, leave the rest.

dotnet add package Koto.Domain
browse the packages →

what it looks like ↓

why

DDD in .NET usually means boilerplate or a licensing trap

Either you write always-valid domain objects, Result-based error handling, and outbox-based event delivery from scratch in every service — or you reach for a framework and later discover it went commercial (MediatR v13+, MassTransit v9+, FluentAssertions v8+).

Koto is a thin, opinionated layer over free, best-in-class .NET libraries — Wolverine for messaging, Marten for event sourcing, FastEndpoints for HTTP, EF Core for persistence — so you get the conventions without the boilerplate or the trap.

what it looks like

A domain model that can't be created in an invalid state

Validation lives in the factory method, not scattered across the codebase. Failures are values — Result<T> — not exceptions.

value object

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);
}

aggregate root

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;
    }
}

how it fits together

Four principles instead of a framework

01

Always-valid domain

Domain objects cannot be constructed in an invalid state. Validation lives in factory methods, not in validators.

02

Explicit errors, end to end

No exceptions for expected failures. Result<T> flows from the domain all the way to the HTTP response.

03

Events flow to Kafka automatically

Domain events land in the outbox in the same transaction as your write. Wolverine delivers them, in-process handlers publish integration events, Kafka carries them to other services.

04

No magic

No runtime reflection on hot paths. No hidden conventions that surprise you at 2am. The code you write is the code that runs.

domain event → Kafka, automatically

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

getting started

Add the packages you need

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

mit licensed · published on nuget

Built for a real production system, open-sourced for everyone else

Koto grew out of building a real .NET microservices backend and was extracted into its own repository once the abstractions earned their keep.

Packages on NuGet