Start Debugging

MediatR vs classes de serviço simples em 2026: a mudança de licença deveria te mover?

Para código novo, classes de serviço simples são a melhor opção padrão. A mudança de licença do MediatR de julho de 2025 só importa se você estiver acima do limite Community de 5 milhões de dólares ou recusar o copyleft da RPL-1.5. Mantenha o MediatR quando os pipeline behaviors forem essenciais.

A versão curta: para código novo em .NET 11 em 2026, use por padrão classes de serviço simples e recorra ao MediatR somente quando você realmente se apoiar nos seus pipeline behaviors. A mudança de licença de julho de 2025 é uma razão real para reavaliar, mas não é o abismo que alguns pintaram. Se a sua empresa está abaixo de 5.000.000 de dólares de receita bruta anual, você pode continuar usando gratuitamente a versão mais recente do MediatR sob a edição Community, então a questão do licenciamento só força uma decisão para times maiores ou para qualquer um que não queira aceitar o copyleft da RPL-1.5 na licença open source gratuita. A pergunta técnica (você precisa de um mediator afinal?) é a que de fato deveria guiar a escolha, e para a maior parte do despacho de requisições é a indireção, não a biblioteca, que você pode remover.

Versões referenciadas ao longo do texto: MediatR 12.5.0 é a última versão sob a licença Apache 2.0 simples; MediatR 13.0 (lançada em 2025-07-02) e a posterior linha 14.x são distribuídas sob um modelo dual Reciprocal Public License 1.5 / comercial da Lucky Penny Software. O código tem como alvo <TargetFramework>net11.0</TargetFramework> com o SDK do .NET 11 e C# 14.

O que realmente mudou em julho de 2025

MediatR e AutoMapper foram mantidos por anos por Jimmy Bogard com uma licença permissiva de código aberto. Em 2025-07-02 ele transferiu ambos para uma nova empresa, a Lucky Penny Software, e mudou o modelo de licenciamento. Os mecanismos que importam para a sua decisão:

Então a bifurcação prática é esta. Uma equipe pequena abaixo de 5 M? Você pode permanecer gratuitamente na versão atual do MediatR, e o único atrito é o aviso no log até você registrar uma chave Community. Maior ou bem financiado? Você ou paga, ou aceita as obrigações recíprocas da RPL-1.5, ou sai. Esse terceiro grupo é exatamente quem deveria estar lendo uma comparação “vs classes de serviço simples”.

A matriz de recursos num relance

AspectoMediatR 13+Classes de serviço simples
Despacho de requisiçõesIndireção com ISender.SendChamada direta a um método de uma interface injetada
Preocupações transversaisIPipelineBehavior<,> de primeira classeDecoradores (Scrutor) ou chamadas explícitas
Ir para definição a partir do chamadorCai em Send, não no handlerCai na implementação
Segurança de ligação em compilaçãoResolução em tempo de execução, pode falhar na primeira chamadaInjeção por construtor, falha ao iniciar
Notificações / fan-outPublicação INotification integradaLista de handlers feita à mão
Custo de inicializaçãoVarredura de assemblies para registrar handlersNenhum além do registro normal de DI
Sobrecarga por chamadaAlocação de wrapper + busca em dicionário + despacho virtualQuase zero, o JIT pode desvirtualizar
Native AOT / trimmingRequer cuidado, registro baseado em reflexãoLimpo
Licença (acima de 5 M de receita)Compra comercial ou RPL-1.5Nenhuma, é o seu próprio código
Código novo em 2026Só se os behaviors forem essenciaisA opção padrão

A linha que decide a maioria das discussões é “preocupações transversais”. Quase tudo o mais nessa tabela favorece as classes de serviço simples. O valor genuíno e difícil de substituir do MediatR é o pipeline: um único lugar para envolver cada requisição com validação, log, transações e cache. Se você não usa esse pipeline, está pagando o custo de indireção e de licença por um localizador de serviços glorificado.

Como o MediatR se parece, e o equivalente simples

Esta é a forma canônica do MediatR: uma requisição, um handler e um chamador que despacha através do ISender.

// .NET 11, C# 14, MediatR 13+ - request + handler
using MediatR;

public record GetOrderById(int OrderId) : IRequest<OrderDto>;

public sealed class GetOrderByIdHandler(AppDbContext db)
    : IRequestHandler<GetOrderById, OrderDto>
{
    public async Task<OrderDto> Handle(GetOrderById request, CancellationToken ct)
    {
        var order = await db.Orders.FindAsync([request.OrderId], ct)
            ?? throw new OrderNotFoundException(request.OrderId);
        return order.ToDto();
    }
}

// In an endpoint:
public async Task<OrderDto> Get(int id, ISender sender, CancellationToken ct)
    => await sender.Send(new GetOrderById(id), ct);

A versão simples colapsa a requisição e o handler em um único método de um serviço injetado. O chamador depende diretamente da interface.

// .NET 11, C# 14 - plain service class, no MediatR
public interface IOrderService
{
    Task<OrderDto> GetByIdAsync(int orderId, CancellationToken ct);
}

public sealed class OrderService(AppDbContext db) : IOrderService
{
    public async Task<OrderDto> GetByIdAsync(int orderId, CancellationToken ct)
    {
        var order = await db.Orders.FindAsync([orderId], ct)
            ?? throw new OrderNotFoundException(orderId);
        return order.ToDto();
    }
}

// In an endpoint:
public async Task<OrderDto> Get(int id, IOrderService orders, CancellationToken ct)
    => await orders.GetByIdAsync(id, ct);

A versão simples é mais curta e, o que é crucial, pressionar ir para definição em orders.GetByIdAsync te leva ao método que executa. Com sender.Send(new GetOrderById(id)) você cai no Send do MediatR, e navega até o handler adivinhando ou com um desvio de “encontrar implementações”. Em um time pequeno essa diferença é leve. Em uma base de código grande com centenas de handlers, a perda de navegabilidade direta é um imposto real e diário que o modelo do MediatR impõe em troca de desacoplar o chamador do tipo do handler. Se esse desacoplamento te traz algo depende de se alguém um dia troca um handler sem tocar no chamador, o que na prática é raro.

O registro também merece uma comparação. O MediatR varre assemblies ao iniciar; as classes de serviço simples se registram de forma explícita, e você obtém uma falha em tempo de inicialização (não na primeira requisição) se esquecer alguma, o que se relaciona diretamente com o tipo de erros por trás de Unable to resolve service for type while attempting to activate.

// .NET 11, C# 14 - registration, side by side
// MediatR: scan an assembly, register every handler reflectively
builder.Services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssemblyContaining<GetOrderById>());

// Plain: explicit, trim-friendly, fails fast at startup if a dep is missing
builder.Services.AddScoped<IOrderService, OrderService>();

O pipeline behavior, e como viver sem ele

Aqui é onde o MediatR conquista o seu lugar. Um pipeline behavior envolve cada requisição, o que te dá exatamente um lugar para adicionar validação, log, medição de tempo ou uma transação.

// .NET 11, C# 14, MediatR 13+ - cross-cutting validation for ALL requests
using MediatR;

public sealed class ValidationBehavior<TRequest, TResponse>(
    IEnumerable<IValidator<TRequest>> validators)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        foreach (var validator in validators)
            await validator.ValidateAndThrowAsync(request, ct);
        return await next(ct);
    }
}

Registrado uma vez, esse behavior executa na frente de cada handler. Substituir isso por chamadas de validação copiadas e coladas em cada serviço simples seria uma regressão, e é o melhor argumento para manter um mediator. Mas você pode obter a mesma propriedade de “envolver tudo” em DI simples com o padrão decorador, usando Scrutor (licenciado sob MIT) para registrar um decorador ao redor de uma interface:

// .NET 11, C# 14, Scrutor 6.x - a decorator gives you the same cross-cutting hook
public sealed class LoggingOrderService(
    IOrderService inner,
    ILogger<LoggingOrderService> logger) : IOrderService
{
    public async Task<OrderDto> GetByIdAsync(int orderId, CancellationToken ct)
    {
        logger.LogInformation("Fetching order {OrderId}", orderId);
        return await inner.GetByIdAsync(orderId, ct);
    }
}

// Registration: decorate the real implementation
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.Decorate<IOrderService, LoggingOrderService>();

A troca é honesta: o behavior do MediatR é genérico sobre cada requisição com um único registro, ao passo que um decorador é por interface. Se você tem uma ou duas preocupações transversais e um punhado de interfaces de serviço, os decoradores ganham em clareza. Se você tem dez behaviors que precisam se aplicar de forma uniforme a duzentos tipos de requisição, o pipeline genérico do MediatR é genuinamente menos código, e esse é o cenário em que ficar (e pagar, ou qualificar para Community) é defensável. Para preocupações no nível da requisição que na verdade são preocupações de HTTP, note que parte do que as pessoas colocam em behaviors pertence ao middleware ou aos filtros, a mesma superfície coberta por adicionar um filtro global de exceções no ASP.NET Core 11.

Quanto o despacho custa de verdade

O desempenho é o argumento mais fraco em qualquer direção, e você não deveria escolher só por ele, mas vale a pena saber onde o custo vive para que ninguém faça afirmações vagas. Uma chamada a uma interface simples é um único despacho virtual que o JIT muitas vezes pode desvirtualizar e colocar inline; não aloca nada e custa da ordem de um nanossegundo. Um Send do MediatR faz mais trabalho por chamada: ele procura o tipo da requisição em um cache de handlers, constrói ou recupera um RequestHandlerWrapper, percorre a cadeia de behaviors via delegates e resolve o handler a partir do contêiner. Isso é da ordem de dezenas de nanossegundos mais uma pequena alocação por envio, antes de o corpo do seu handler executar.

AspectoSend do MediatRChamada a interface simples
Resolução tipo-para-handlerBusca em dicionário por chamadaNenhuma, ligada na injeção
Cadeia wrapper / delegateAlocada por envioNenhuma
Desvirtualização pelo JITNãoFrequentemente sim
Varredura de assemblies ao iniciarSim, cresce com o número de handlersNenhuma
Ordem de grandeza por chamadaDezenas de ns + pequena alocação~1 ns, zero alocação

A honestidade metodológica aqui: contra um handler que toca um banco de dados ou a rede, dezenas de nanossegundos são invisíveis, e você deveria medir as suas próprias formas de objeto com BenchmarkDotNet em vez de confiar em um número genérico. O custo que de fato aparece na prática é a inicialização. Varrer assemblies para registrar centenas de handlers adiciona milissegundos mensuráveis à inicialização a frio, o que importa para serverless e é o tipo de coisa contra a qual você luta ao reduzir o tempo de inicialização a frio de uma AWS Lambda com .NET 11. O registro explícito e simples evita isso por completo e é mais amigável com o trimming e o Native AOT, porque não há descoberta reflexiva para manter segura ao trimming.

O detalhe que decide por você

Algumas restrições resolvem isso antes de o gosto arquitetural entrar em cena.

A RPL-1.5 é a verdadeira força decisória, não o preço. A opção gratuita de código aberto no MediatR 13+ é a Reciprocal Public License 1.5, que carrega obrigações de copyleft recíprocas: ela é projetada para fechar a brecha do SaaS, então implantar um serviço de rede construído sobre código licenciado sob RPL pode te obrigar a disponibilizar o seu código-fonte. Se você distribui software comercial de código fechado e está acima do limite Community, a licença OSS gratuita não é realmente utilizável para você, e “o MediatR ainda é open source” é enganoso no seu caso. Você ou compra a licença comercial ou sai. Para um despachador fino que você na verdade não estava usando, sair é fácil.

A linha de 5 M de receita e 10 M de capital é generosa. A maioria dos times de produto pequenos, consultorias e startups fica abaixo dela e pode continuar usando gratuitamente a versão mais nova do MediatR sob a edição Community. Se esse é você, a licença não é razão para arrancar nada; registre uma chave Community, silencie o aviso e siga em frente. Gastar um sprint removendo o MediatR para evitar uma conta que você não deve é a troca errada.

Fixar 12.5.0 é uma opção real com um custo real. Apache 2.0 na última versão gratuita não expira. Mas você congela em uma versão que não recebe patches de segurança, e herda você mesmo o risco de manutenção. Isso é aceitável para um app interno estável e perigoso para qualquer coisa exposta à internet.

Se você só chama Send, você não precisa de um mediator. O teste honesto: abra a sua solução e procure IPipelineBehavior e INotification. Se há zero behaviors e você não publica notificações, o MediatR está funcionando como uma camada de indireção sobre chamadas de método, e removê-lo é uma refatoração mecânica que torna o código mais navegável, remove uma dependência e apaga a questão da licença em um único movimento. Esse é o mesmo instinto de racionalização de bibliotecas por trás de escolher a opção embutida em System.Text.Json vs Newtonsoft.Json em 2026.

A decisão, em uma linha

Para código novo em .NET 11 em 2026, escreva classes de serviço simples e injete as interfaces diretamente: você ganha ir para definição, ligação de inicialização que falha rápido, sem sobrecarga por chamada, amizade com o trimming e zero exposição a licenças. Mantenha o MediatR só quando os seus pipeline behaviors estiverem fazendo trabalho real e uniforme através de muitos tipos de requisição, e nesse caso decida deliberadamente: fique gratuitamente sob a edição Community se você está abaixo de 5 M, pague se você está acima e o pipeline justifica a conta, e fixe 12.5.0 apenas como medida paliativa. O erro é tratar “o MediatR ficou comercial” ou como um não-evento ou como um alarme de cinco sinos. Não é nenhum dos dois. É um aviso para perguntar se você precisava do mediator em primeiro lugar, e para a maior parte do código de despacho de requisições a resposta sempre foi não.

Relacionados

Fontes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Voltar