MediatR
- MediatR
- 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
sendpublica para um - Método
publishpublica 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 correto é fazer por
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.
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;
}
}
}