.NET : Alternativas ao MediatR (que vai deixar de ser grátis)


 Neste artigo veremos algumas alternativas para substituir o MediatR que em breve não será mais gratuito. (Veja a série de vídeos no meu canal do Youtube)

Conforme post do Jimmy Bogard o criador do AutoMapper e do MediatR publicado neste link , ele anunciou que em breve essas bibliotecas deixariam de ser gratuitas.



Assim,  se você usa o AutoMapper e o Mediator em seus projetos, e quer economizar, deve procurar alternativas gratuitas a ambos os recursos.

A seguir vou apresentar a seguir algumas alterantivas open source (ainda) considerando a implementação do padrão CQRS onde é muito comum usar o MediatR.

Nota: Para ver as alternativas ao AutoMapper veja o meu vídeo neste link (meu canal no YouTube)

Alternativas Open Source ao MediatR

1. Mediator (Microsoft)

Pacote: Microsoft.Extensions.MediatR, implementação leve e oficial da Microsoft similar em conceito ao MediatR original.

// Instale o pacote
// dotnet add package Microsoft.Extensions.MediatR
// Configuração (similar ao MediatR)
services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Startup>());
// Uso em controllers permanece similar
public class MyController : ControllerBase
{
    private readonly IMediator _mediator;
    
    public MyController(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var result = await _mediator.Send(new MyQuery());
        return Ok(result);
    }
}

2. TinyMediator

Pacote: TinyMediator , alternativa minimalista e de alto desempenho

Instalação :   dotnet add package TinyMediator

Configuração básica:

// No seu Program.cs ou Startup.cs
builder.Services.AddTinyMediator(options =>
{
    // Opcional: Configurar assemblies para scan
    options.ScanAssemblies(typeof(Program).Assembly);
});

Definindo Command/Queries

// Command com resposta
public record CreateProductCommand(string Name, decimal Price) : IRequest<ProductResponse>;
// Query
public record GetProductByIdQuery(int Id) : IRequest<ProductResponse>;
// Command sem resposta
public record LogProductActionCommand(string Action) : IRequest;

Implementando Handlers

// Handler com resposta
public class CreateProductHandler : IRequestHandler<CreateProductCommand, ProductResponse>
{
    public async Task<ProductResponse> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        // Lógica de criação do produto
        return new ProductResponse(Guid.NewGuid(), request.Name, request.Price);
    }
}
// Handler sem resposta
public class LogProductActionHandler : IRequestHandler<LogProductActionCommand>
{
    public async Task Handle(LogProductActionCommand request, CancellationToken cancellationToken)
    {
        Console.WriteLine($"Action logged: {request.Action}");
    }
}

Uso em Controllers ou Services

[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    private readonly ISender _mediator;
    public ProductsController(ISender mediator)
    {
        _mediator = mediator;
    }
    [HttpPost]
    public async Task<IActionResult> Create(CreateProductCommand command)
    {
        var result = await _mediator.Send(command);
        return Ok(result);
    }
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        var result = await _mediator.Send(new GetProductByIdQuery(id));
        return Ok(result);
    }
}

Publicando Notificações

// Definindo a notificação
public record ProductCreatedNotification(Guid ProductId) : INotification;
// Handler de notificação
public class ProductCreatedEmailHandler : INotificationHandler<ProductCreatedNotification>
{
    public async Task Handle(ProductCreatedNotification notification, CancellationToken cancellationToken)
    {
        // Enviar e-mail
    }
}
// Outro handler para a mesma notificação
public class ProductCreatedLogHandler : INotificationHandler<ProductCreatedNotification>
{
    public async Task Handle(ProductCreatedNotification notification, CancellationToken cancellationToken)
    {
        // Registrar log
    }
}
// Publicando em um serviço
public class ProductService
{
    private readonly IPublisher _publisher;
    public ProductService(IPublisher publisher)
    {
        _publisher = publisher;
    }
    public async Task CreateProduct(CreateProductCommand command)
    {
        // Cria o produto...
        await _publisher.Publish(new ProductCreatedNotification(productId));
    }
}

Vantagens do TinyMediator:

  Mais leve que o MediatR original
  Melhor performance (menos alocações)
  API similar ao MediatR (facilita migração)
  Suporte a requests com e sem resposta
  Pub/Sub integrado

Diferenças Principais para o MediatR:

  Não tem suporte nativo a pipelines/middlewares
  API mais enxuta
  Foco em desempenho

3. Brighter

Pacote: Paramore.Brighter, mais completo, suporta padrões Command e Event além de CQRS

Instalação

dotnet add package Paramore.Brighter
dotnet add package Paramore.Brighter.Extensions.Hosting

Configuração básica

// No Program.cs
builder.Services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
.UseExternalBus(new InMemoryMessageProducer()) // Para comandos assíncronos
.UseInMemoryOutbox(); // Opcional para resiliência

Definindo Commands

public class CreateProductCommand : Command
{
    public string Name { get; }
    public decimal Price { get; }
    public CreateProductCommand(string name, decimal price) 
        : base(Guid.NewGuid())
    {
        Name = name;
        Price = price;
    }
}

Implementando Handlers

public class CreateProductCommandHandler : RequestHandler<CreateProductCommand>
{
    public override CreateProductCommand Handle(CreateProductCommand command)
    {
        // Lógica para criar produto
        Console.WriteLine($"Criando produto: {command.Name}");
        return base.Handle(command);
    }
}

Registrando Handlers

builder.Services.AddScoped<CreateProductCommandHandler>();

Enviando Commands (Sync)

public class ProductController : ControllerBase
{
    private readonly IAmACommandProcessor _commandProcessor;
    public ProductController(IAmACommandProcessor commandProcessor)
    {
        _commandProcessor = commandProcessor;
    }
    [HttpPost]
    public IActionResult Create([FromBody] ProductRequest request)
    {
        var command = new CreateProductCommand(request.Name, request.Price);
        _commandProcessor.Send(command);
        return Ok();
    }
}

Enviando Commands (Async)

public async Task<IActionResult> CreateAsync([FromBody] ProductRequest request)
{
    var command = new CreateProductCommand(request.Name, request.Price);
    await _commandProcessor.SendAsync(command);
    return Ok();
}

Publicando Events

public class ProductCreatedEvent : Event
{
    public string Name { get; }
    
    public ProductCreatedEvent(Guid id, string name) 
        : base(id)
    {
        Name = name;
    }
}
// Handler do Evento
public class ProductCreatedEventHandler : RequestHandler<ProductCreatedEvent>
{
    public override ProductCreatedEvent Handle(ProductCreatedEvent @event)
    {
        Console.WriteLine($"Produto criado: {@event.Name}");
        return base.Handle(@event);
    }
}
// Publicando
_commandProcessor.Publish(new ProductCreatedEvent(Guid.NewGuid(), "Novo Produto"));

Quando Usar o Brighter:

   Quando você precisa de resiliência avançada (retry, circuit breaker)
   Para sistemas distribuídos com mensageria
   Quando precisa de padrão Outbox para garantia de entrega

O Brighter tem uma curva de aprendizado um pouco maior, mas oferece recursos poderosos para sistemas complexos.

E estamos conversados...  

"Como guardaste a palavra da minha paciência, também eu te guardarei da hora da tentação que há de vir sobre todo o mundo, para tentar os que habitam na terra."
Apocalipse 3:10

Referências:


José Carlos Macoratti