Arquitetura de Mensagens
Introdução aos Conceitos Fundamentais da Arquitetura Mediator
No contexto do padrão Mediator e arquiteturas relacionadas como CQRS (Command Query Responsibility Segregation), é essencial compreender um conjunto de conceitos fundamentais que formam a base para comunicação desacoplada entre componentes de software. Estes conceitos - Request, Command, Response, Handler e Use Case - representam os blocos de construção fundamentais para implementar o padrão Mediator em sistemas distribuídos e complexos, particularmente no ecossistema .NET com bibliotecas como MediatR.
Esta abordagem promove uma separação clara de responsabilidades, facilita a manutenção e testabilidade, e estabelece um padrão consistente para o fluxo de dados através das camadas da aplicação.
Request e Command: Objetos de Entrada de Dados
Definição e Propósito
Um Request (Requisição) ou Command (Comando) é essencialmente um DTO (Data Transfer Object - Objeto de Transferência de Dados) que encapsula toda a informação necessária para executar uma operação específica no sistema. Estes objetos servem como mensagens ou instruções que são enviadas através do mediador para serem processadas pelos manipuladores apropriados.
A distinção semântica entre os dois termos está frequentemente relacionada ao padrão CQRS:
Command: Representa uma intenção de modificar o estado do sistema (operações de escrita).
Request: Termo mais genérico que pode incluir tanto comandos (modificações) quanto queries (consultas), embora em algumas implementações seja usado especificamente para queries.
Características Técnicas
Os Requests/Commands são normalmente implementados como records em C# (a partir da versão 9.0), devido a várias propriedades desejáveis:
// Exemplo de um Command usando record
public record CreateUserCommand(
string FirstName,
string LastName,
string Email,
DateTime DateOfBirth) : IRequest<CreateUserResponse>;
// Exemplo de um Request usando record
public record GetUserDetailsRequest(
Guid UserId) : IRequest<UserDetailsResponse>;Por que records são preferíveis
Imutabilidade: Uma vez criados, seus valores não podem ser alterados, o que elimina efeitos colaterais inesperados e facilita o raciocínio sobre o estado do sistema.
Sem Comportamentos: Seguem o princípio da responsabilidade única - apenas carregam dados, não contêm lógica de negócio. Isso os torna verdadeiros DTOs.
Valor Semântico: Em C#, records possuem igualdade baseada em valor (não em referência), o que é apropriado para objetos de transferência de dados.
Concisão: A sintaxe de records reduz drasticamente o boilerplate código comparado a classes tradicionais.
Suporte a Desconstrução: Permite fácil extração de valores para processamento.
Interface de Marcação
Em implementações como o MediatR, estes objetos normalmente implementam uma interface de marcação que permite ao mediador identificar e roteá-los corretamente:
Esta interface genérica IRequest<TResponse> especifica o tipo de resposta esperada, criando uma relação de tipo forte entre o comando e sua resposta.
Response: Objetos de Saída de Dados
Definição e Propósito
Um Response (Resposta) é o objeto que encapsula o resultado do processamento de um Request ou Command. Ele representa a saída de dados que será retornada à camada que originou a requisição, seja ela um controlador HTTP, uma função serverless, ou outro componente do sistema.
Características e Implementação
Assim como os Requests, os Responses são tipicamente implementados como records imutáveis:
Padrões Comuns em Responses
Respostas Específicas: Cada operação pode ter um tipo de resposta específico que contém exatamente os dados necessários.
Respostas Genéricas: Alguns sistemas usam wrappers genéricos como
OperationResult<T>ouResult<T>que incluem metadados sobre o sucesso/falha da operação.Respostas de Validação: Para comandos de validação, a resposta pode conter uma lista de erros ou avisos.
Respostas Vazias: Para comandos que não precisam retornar dados (apenas indicar sucesso), pode-se usar tipos como
Unit(do MediatR) ouIRequest<Unit>.
Relação com o Padrão Mediator
No fluxo do Mediator, o Response é o objeto que completa o ciclo de comunicação:
Um componente envia um Request/Command ao Mediador
O Mediador roteia para o Handler apropriado
O Handler processa e retorna um Response
O Mediador devolve o Response ao componente original
Esta abordagem cria um pipeline previsível e fortemente tipado para toda comunicação entre componentes.
Handler: O Processador de Requisições
Definição e Papel no Sistema
Um Handler (Manipulador) é o componente responsável por processar um tipo específico de Request ou Command. Ele contém a lógica de negócio necessária para executar a operação solicitada e produzir a Response apropriada. No contexto do padrão Mediator, os Handlers são os "ouvintes" ou "processadores" que respondem a mensagens específicas enviadas através do mediador.
Estrutura de um Handler
Em C# com MediatR, um Handler tipicamente implementa a interface IRequestHandler<TRequest, TResponse>:
Responsabilidades do Handler
Processamento da Lógica de Negócio: Contém o core da operação solicitada.
Validação: Verifica se o Request/Command é válido antes de processar.
Coordenação de Dependências: Orquestra chamadas a repositórios, serviços externos, etc.
Tratamento de Erros: Decide como lidar com exceções e falhas.
Mapeamento: Converte entre objetos de domínio e DTOs de entrada/saída.
Controle de Transações: Em sistemas com persistência, gerencia transações quando necessário.
Analogia com MVC
Como mencionado nas anotações, em uma arquitetura MVC tradicional, os Controllers atuam como uma espécie de Handler primitivo. No entanto, em abordagens com Mediator, essa responsabilidade é extraída para Handlers especializados, resultando em:
Controllers mais magros: Apenas recebem requisições HTTP, criam os Commands/Requests e os enviam ao Mediador.
Separação mais clara: A lógica de negócio fica isolada nos Handlers, não espalhada pelos Controllers.
Maior testabilidade: Handlers são mais fáceis de testar isoladamente do que Controllers que possuem dependências de frameworks.
Use Case: A Camada de Casos de Uso
Definição e Contexto Arquitetural
Um Use Case (Caso de Uso) representa uma ação ou operação específica que o sistema pode realizar do ponto de vista do usuário ou sistema externo. Na arquitetura de software, a camada de Casos de Uso serve como uma camada intermediária entre a infraestrutura (banco de dados, serviços externos, UI) e as entidades de domínio (regras de negócio core).
Composição de um Use Case
Como indicado nas anotações, um Use Case completo é tipicamente composto pelos elementos que já discutimos:
Exemplo de Implementação
Benefícios da Camada de Use Cases
Isolamento do Domínio: Protege as entidades de domínio de detalhes de infraestrutura.
Orquestração Clara: Cada caso de uso tem um fluxo bem definido e documentado.
Independência Tecnológica: A lógica do caso de uso não depende de frameworks específicos.
Testabilidade: Como cada caso de uso é autocontido, pode ser testado em isolamento.
Manutenibilidade: Mudanças em um caso de uso não afetam outros casos de uso.
Documentação Implícita: A estrutura do código documenta os fluxos do sistema.
Relação com Arquitetura Limpa (Clean Architecture)
Na Clean Architecture de Robert C. Martin, os Use Cases ocupam uma posição central:
Camada mais interna: Entidades (regras de negócio essenciais)
Camada intermediária: Use Cases (regras de aplicação/orquestração)
Camada externa: Controladores, Gateways, UI (detalhes de implementação)
Os Use Cases definem o que o sistema faz, enquanto a infraestrutura define como isso é realizado.
Integração com o Padrão Mediator
Fluxo Completo de Comunicação
Quando combinamos todos esses conceitos com o padrão Mediator, obtemos um fluxo de comunicação altamente organizado e desacoplado:
Benefícios desta Abordagem Integrada
Desacoplamento Total: O Controller não conhece o Handler, apenas o Mediador.
Pipeline Comportamentos: O Mediador permite adicionar comportamentos transversais (logging, validação, autorização) que afetam todos os Handlers.
Testabilidade Aprimorada: Cada componente pode ser testado isoladamente.
Flexibilidade: Handlers podem ser facilmente substituídos ou decorados.
Consistência: Padrão uniforme para todas as operações do sistema.
Conclusão e Síntese
Os conceitos de Request/Command, Response, Handler e Use Case formam a base para implementações robustas do padrão Mediator em sistemas .NET modernos. Juntos, eles criam uma arquitetura onde:
Cada operação do sistema é encapsulada em um par Request-Response fortemente tipado.
A lógica de processamento é isolada em Handlers especializados e focados.
Os fluxos de negócio são organizados em Casos de Uso autocontidos.
A comunicação entre componentes é mediada por um objeto central que desacopla produtores de consumidores.
Esta abordagem, popularizada por bibliotecas como MediatR, representa uma evolução natural do padrão Mediator clássico para o contexto de aplicações empresariais modernas. Ela combina os benefícios do desacoplamento do Mediator com a clareza e organização dos princípios de Design Orientado a Domínio e Arquitetura Limpa.
Ao dominar estes conceitos fundamentais, desenvolvedores estão equipados para criar sistemas que são não apenas funcionais, mas também mantíveis, testáveis e adaptáveis às mudanças - qualidades essenciais para software de longo prazo em ambientes empresariais complexos.
Atualizado