Skip to main content

MediatR

  • MediatR
info
  • Reduzir o acoplamento entre uma camada A e B
    • O contrato interliga os dois
    • Command e o Handler precisa estar na mesma camada

Breve explicação

  • Método send publica para um
  • Método publish publica para vários
  • Quando se fala em casos de uso, não é interessante ter mais de uma request dentro de um handler
  • IRequestHandler é para request
  • INotificationHandler é usada para notificar que algo foi feito
  • Chamar um handler dentro de outro não é para ser feito porque o caso de uso é para apenas uma coisa
    • O correto é fazer por INotificationHandler

O padrão Mediator

  • É um padrão de design comportamental que permite reduzir as dependências complexas entre objetos, promovendo um acoplamento mais solto. Ele atua como um intermediário centralizado que coordena a comunicação entre objetos em um sistema, fazendo com que eles não comuniquem diretamente uns com os outros, mas através do mediador. Isso simplifica a manutenção e a evolução do código. O padrão mediator foi pensando muito para as aplicações desktop

MediatR

  • É uma biblioteca leve e extensível em .NET para implementação do padrão Mediator, facilitando a comunicação entre os componentes de uma aplicação de maneira desacoplada e organizada. Suas funcionalidades centrais giram em torno do envio de comandos, manipulação de eventos (notificações) e solicitações de dados, permitindo que você estruture sua aplicação em torno de operações claramente definidas e manipuladores de eventos. Aqui está um resumo das funcionalidades principais do MediatR e como elas são usadas:

1. Envio de Comandos/Requisições (Requests)

Esta é provavelmente a funcionalidade mais usada do MediatR. Permite enviar comandos (ou solicitações que alteram o estado do sistema) para o manipulador correspondente. Cada comando é uma classe que implementa a interface IRequest, podendo opcionalmente retornar um valor.

using MediatR;

public class CreateUserCommand : IRequest<int>
{
public string Name { get; set; }
public string Email { get; set; }
}

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, int>
{
public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// Aqui, você implementaria a lógica para criar um usuário no banco de dados
// e retornaria o ID do usuário criado
return newUser.Id; // Simulando um ID de usuário retornado após a criação
}
}

2. Publicação de Notificações (Events)

O MediatR permite publicar eventos (notificações) que múltiplos manipuladores podem processar. Isso é útil para implementar funcionalidades baseadas em eventos, onde várias partes do sistema precisam reagir a algo que aconteceu. Cada evento é uma classe que implementa a interface INotification.

public class UserCreatedEvent : INotification
{
public int UserId { get; set; }
}

public class UserCreatedEventHandler : INotificationHandler<UserCreatedEvent>
{
public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
{
// Lógica para processar o evento, como enviar um email de boas-vindas
}
}

3. Pedido de Informações (Queries)

Embora tecnicamente seja um caso específico de envio de comandos/requisições, as consultas (queries) são usadas para solicitar dados do sistema sem modificar seu estado. Geralmente, são implementadas como classes que implementam IRequest<T>, onde T é o tipo de dado a ser retornado.

public class GetUserByIdQuery : IRequest<User>
{
public int UserId { get; set; }
}

public class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, User>
{
public async Task<User> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
// Implementar lógica para buscar o usuário pelo ID
return user; // Retornando um objeto usuário simulado
}
}

Funcionalidades Avançadas e Extensões

  • Processadores de Pipeline (Behaviors): MediatR suporta middlewares, conhecidos como Behaviors, que permitem a execução de lógica antes ou depois dos manipuladores de requisições. Isso é útil para validação, logging, transações de banco de dados, etc.

    public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
    {
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
    // Log antes da execução do Request
    Console.WriteLine($"Handling {typeof(TRequest).Name}");

    var response = await next();

    // Log após a execução do Request
    Console.WriteLine($"Handled {typeof(TResponse).Name}");

    return response;
    }
    }

  • Suporte a Cancelamento: As operações assíncronas no MediatR podem ser canceladas usando CancellationToken, permitindo que operações de longa duração sejam interrompidas conforme necessário.

  • Construção e Configuração Flexíveis: MediatR pode ser facilmente integrado com contêineres de injeção de dependência (DI), permitindo uma configuração e inicialização flexíveis dentro de aplicações .NET.

Quando Usar o MediatR

  • Para Aplicações com Alta Complexidade de Domínio: O MediatR é particularmente útil em aplicações onde a complexidade do domínio é alta, ajudando a manter uma separação clara entre a lógica de domínio e a infraestrutura.
  • Implementação de CQRS: O MediatR se alinha bem com o padrão de arquitetura CQRS (Command Query Responsibility Segregation), facilitando a separação entre a execução de comandos (ações que alteram o estado) e queries (solicitações de dados).
  • Desacoplamento de Componentes: Quando você precisa de uma arquitetura que promova o desacoplamento entre diferentes partes do sistema, facilitando a manutenção e a testabilidade.
  • Comunicação complexa: seu sistema envolve um conjunto de objetos que precisam se comunicar entre si de maneira complexa, e você quer evitar dependências diretas entre eles.
  • Acoplamento flexível: você deseja promover o acoplamento flexível entre objetos, permitindo que eles interajam sem conhecer os detalhes das implementações uns dos outros.
  • Controle Centralizado: Você precisa de um mecanismo centralizado para coordenar e controlar as interações entre objetos, garantindo um sistema mais organizado e sustentável.
  • Mudanças no comportamento: você antecipa mudanças no comportamento dos componentes e deseja encapsular essas mudanças dentro do mediador, evitando modificações generalizadas.
  • Reutilização aprimorada: você deseja reutilizar componentes individuais em diferentes contextos sem alterar sua lógica interna ou padrões de comunicação.
danger

O MediatR brilha quando utilizado em propagação de notificações ou publicação de eventos. Em um cenário 1 para N, onde um componente emite uma mensagem e múltiplos receptores podem agir sobre ela sem depender de respostas diretas, ele pode ser bastante útil.

Além disso, o MediatR pode ser uma solução eficiente para evitar dependências cíclicas dentro do domínio da aplicação.

Por outro lado, se o fluxo continua sendo estritamente sequencial e cada chamada depende da resposta da outra, o benefício do MediatR se torna questionável. Nesses casos, um serviço injetado diretamente pode ser uma solução mais simples, eficiente e direta

Impacto na performance

Outro aspecto que raramente é considerado é o impacto na performance. Embora em aplicações pequenas isso não seja um grande problema, em sistemas maiores a indireção extra pode gerar latências desnecessárias.

Cada requisição passa por um pipeline, o que pode aumentar a sobrecarga, especialmente se houver behaviors e middlewares adicionais configurados. Isso pode resultar em maior consumo de memória e pior desempenho em comparação com uma abordagem baseada em injeção de dependência direta.

Portanto, antes de adotar o MediatR, é importante avaliar se essa complexidade adicional realmente compensa em termos de manutenção e performance.

CUIDADO

Ao adicionar o mediatr na injeção de dependência, devemos se atentar se as classes que serão usadas pelo mediatr estão em outros projetos. Para adicionar outros assembly além do que estão executando, segue o código abaixo:

    services.AddMediatR(Assembly.GetExecutingAssembly(), Assembly.AssemblyLoad("Assembly"));

Exemplo de Uso

  • Código ruim porque esse componente irá conhecer todos os componentes que precisam de um GuestCheckBox
public class GuestCheckBox
{
public bool IsChecked { get; private set; } = false;

private readonly LoginTextBox _loginTextBox;
private readonly PasswordTextBox _passwordTextBox;
private readonly OkButton _okButton;

public GuestCheckBox(LoginTextBox loginTextBox, PasswordTextBox passwordTextBox, OkButton okButton)
{
_loginTextBox = loginTextBox;
_passwordTextBox = passwordTextBox;
_okButton = okButton;
}

public void Toggle()
{
IsChecked = !IsChecked;

SetLoginFieldsVisible(!IsChecked);

if (IsChecked)
_okButton.Enabled = true;
else
_okButton.CheckEnabled(_loginTextBox, _passwordTextBox);
}

private void SetLoginFieldsVisible(bool visible)
{
_loginTextBox.Visible = visible;
_passwordTextBox.Visible = visible;
}
}

Aplicando mediatR

public class CheckBox
{
private readonly IMediator _mediator;
public bool IsChecked { get; private set; }

public CheckBox(IMediator mediator) => _mediator = mediator;

public void Check()
{
IsChecked = true;
_mediator.Notify(this, "Checked");
}

public void Uncheck()
{
IsChecked = false;
_mediator.Notify(this, "Unchecked");
}
}
public class AuthenticationDialog : IMediator
{
public TextBox Login;
public TextBox Password;
public CheckBox Guest;
public Button Ok;

public void Notify(object sender, string ev)
{
if (sender == Guest && ev == "Checked")
{
Login.Visible = false;
Password.Visible = false;
Ok.Enabled = true;
}
else if (sender == Guest && ev == "Unchecked")
{
Login.Visible = true;
Password.Visible = true;
Ok.Enabled = false;
}
}
}