Nexo

Documentação

O que é o Nexo

Um mediator CQRS e uma abstração de dados no mesmo pacote, desenhados para que o handler só fale de entidades.

A ideia

O Nexo junta duas coisas que quase todo projeto .NET escreve de novo: um mediator para commands, queries e events, e uma camada de acesso a dados que não seja um repositório por agregado. A união não é acidental — é dela que sai o ganho real.

Quando o mediator conhece o unit of work, o handler para de ser responsável pelo ciclo de vida da transação. Ele declara o que precisa (ICreate<Product>, IQueryable<Order>), faz o trabalho, e termina. O pipeline abre a transação, salva uma vez, despacha os eventos de domínio e faz commit. Se algo falhar, nada é gravado.

O de sempre
public class ProductService(AppDbContext db, IMediator mediator)
{
    public async Task<Guid> CreateAsync(CreateProductDto dto, CancellationToken ct)
    {
        using var tx = await db.Database.BeginTransactionAsync(ct);

        var category = await db.Categories
            .FirstOrDefaultAsync(c => c.ExternalId == dto.CategoryId, ct);
        if (category is null) throw new NotFoundException();

        var product = new Product { /* ... */ };
        db.Products.Add(product);
        await db.SaveChangesAsync(ct);          // 1

        db.PriceHistory.Add(new PriceHistory
        {
            ProductId = product.Id,             // precisa da chave
            Price = dto.Price,
        });
        await db.SaveChangesAsync(ct);          // 2

        await mediator.Publish(new ProductCreated(product.Id), ct);
        await db.SaveChangesAsync(ct);          // 3

        await tx.CommitAsync(ct);
        return product.ExternalId;
    }
}
Com Nexo
public sealed class CreateProductHandler(
    ICreate<Product> create,
    IReadByExternalId<Category> categories)
    : ICommandHandler<CreateProduct, Guid>
{
    public async Task<Guid> HandleAsync(CreateProduct command, CancellationToken ct)
    {
        var category = await categories.ReadRequiredAsync(command.CategoryId, ct);

        var product = await create.CreateAsync(
            new Product(category.Id, command.Sku, command.Price),
            ct);

        return product.ExternalId;
    }
}

// A linha de histórico é escrita pelo handler de ProductCreated,
// que a própria entidade registrou no construtor. Mesma transação.

A versão de baixo não é só mais curta. Ela é atômica (uma transação, um save), não tem NotFoundException escrita à mão, não precisa da chave do banco para funcionar, e não publica evento nenhum — a entidade registra, o pipeline despacha.

O que tem no pacote

PacoteConteúdo
Nexo.AbstractionsContratos sem dependência nenhuma: mensagens, handlers, IPipeline, operações de dados, IUnitOfWork, Page<T>, traits de entidade e exceções.
NexoO mediator, o registro na DI e os pipelines embutidos: logging, timeout, enriquecimento e unit of work. Traz dentro o gerador de código que registra os handlers em tempo de compilação.
Nexo.EntityFrameworkCoreImplementação sobre um DbContext, mais auditoria, soft delete, operações em lote, outbox, health checks e coleta de eventos de domínio.
Nexo.FluentValidationPipeline de validação que reporta falhas de forma agnóstica.
Nexo.TestingHost de teste, registro de despachos e usuário fixo, para testar handlers com o pipeline de verdade.

Alvo: net10.0, com EF Core 10 e Microsoft.Extensions.* 10. O .NET 8, o .NET 9 e o EF Core 9 saem de suporte em novembro de 2026, então o 1.0 nasce só na versão LTS atual; o net11.0 entra quando sair.

Os princípios

  • O handler declara capacidade, não infraestrutura. ICreate<Product> diz exatamente o que aquele handler pode fazer. Um DbContext injetado diz "qualquer coisa".
  • Atomicidade é padrão, não disciplina. Salvar uma vez no fim é o que faz três escritas serem uma mudança. Um auto-save por operação parece conveniente e entrega escrita não atômica.
  • Nada é obrigatório. Auditoria, soft delete, eventos de domínio, identificador público: cada um liga porque a entidade implementou uma interface. Uma entidade que não implementa nada se comporta como POCO puro.
  • Falha cedo, e no lugar certo. Dois handlers no mesmo comando falham no start-up. Um handler que não existe traz o nome exato do tipo que faltou registrar.
  • Sem vazamento de biblioteca. A falha de validação chega como ValidationFailedException, de Nexo.Abstractions. Sua camada web nunca referencia FluentValidation nem EF Core.

Por onde começar

  1. Primeiros passos — instalar, configurar e escrever o primeiro handler.
  2. CRUD completo — o sample que exercita todos os casos de uso, endpoint por endpoint.
  3. Unit of work — o que exatamente acontece em volta do seu handler.