Camada de Aplicação

Arquitetura Limpa

Criação do Projeto de Aplicação

Um novo projeto do tipo biblioteca de classes é criado para representar a camada de aplicação na estrutura da Arquitetura Limpa:

dotnet new classlib -o CleanArchitectureStore.Application
dotnet sln add .\CleanArchitectureStore.Application\

O projeto CleanArchitectureStore.Application é adicionado à solução, estabelecendo a camada responsável por orquestrar os fluxos de aplicação, implementar casos de uso e mediar a comunicação entre a apresentação e o domínio.

Princípios CQS e CQRS

Segregação de Comando e Consulta (CQS)

O Princípio de Segregação de Comando e Consulta (Command Query Separation - CQS), formulado por Bertrand Meyer, estabelece que:

  1. Comandos (Commands): Realizam ações, alteram estado, não retornam valores

  2. Consultas (Queries): Retornam resultados, não alteram estado, são idempotentes

Segregação de Responsabilidade de Comando e Consulta (CQRS)

CQRS (Command Query Responsibility Segregation) estende o CQS ao utilizar modelos separados para operações de leitura e escrita:

  • Modelo de Comando: Otimizado para operações de escrita, com validação de negócio

  • Modelo de Consulta: Otimizado para operações de leitura, com projeções eficientes

Na implementação atual, adota-se uma abordagem moderada do CQS, onde as responsabilidades são separadas conceitualmente, mantendo a mesma fonte de dados.

Implementação com Padrão Mediator

Instalação e Configuração do MediatR

O padrão Mediator é implementado através do pacote MediatR:

O MediatR atua como mediador entre os comandos e seus manipuladores, desacoplando a invocação da execução. Sua função principal é rotear automaticamente um comando para o handler apropriado, sem necessidade de acoplamento direto.

Estrutura do Caso de Uso

O caso de uso GetById é organizado seguindo o padrão:

Implementação do Result Pattern

Classe de Erro (Error)

No projeto de domínio, é criada uma estrutura para representação padronizada de erros:

Características do record Error:

  • Imutabilidade: record garante que instâncias não podem ser modificadas após criação

  • Igualdade por Valor: Comparação automática baseada no conteúdo, não na referência

  • Códigos Semânticos: Identificadores padronizados para tipos de erro

  • Mensagens Descritivas: Texto legível para diagnóstico

Classe Base Result

Uma implementação do padrão Result é criada para substituir o uso de valores nulos e exceções de controle de fluxo:

Análise da classe base Result:

  • Validação de Invariantes: O construtor valida que resultados bem-sucedidos não têm erros e vice-versa

  • Propriedades de Estado: IsSuccess e IsFailure fornecem verificação simples do estado

  • Métodos de Fábrica: Métodos estáticos para criação consistente de resultados

  • Tipagem Segura: Separação entre resultados com e sem valor

Classe Genérica Result

Funcionalidades do Result<T>:

  • Encapsulamento de Valor: Armazena o valor retornado em operações bem-sucedidas

  • Acesso Seguro: A propriedade Value lança exceção se acessada em resultados falhos

  • Conversão Implícita: Permite uso natural como return someValue; em vez de return Result.Success(someValue);

Definição do Command

O comando é implementado como um record selado, representando a entrada para o caso de uso:

Características do Command:

  • Imutabilidade: record garante que o comando não pode ser alterado após criação

  • Selado: Prevenção de herança não intencional

  • Implementação de IRequest<T>: Define o tipo de resposta esperado pelo MediatR

  • Parâmetros Nomeados: Guid Id identifica claramente o propósito do parâmetro

Definição do Response

A resposta é também implementada como um record selado:

Propriedades do Response:

  • Estrutura Simples: Apenas os dados necessários para o consumidor

  • Separação de Modelos: Diferente da entidade de domínio, pode incluir ou excluir propriedades conforme necessidade

  • Performance: Records têm otimizações para comparação e cópia

Implementação do Handler

O manipulador (handler) é responsável pela orquestração da lógica do caso de uso:

Análise do Handler

Injeção de Dependências:

  • IProductRepository: Interface definida no domínio, implementada na infraestrutura

  • O construtor primário do C# 12 simplifica a declaração de dependências

Processamento:

  1. Busca Assíncrona: Utilização de await para operações não-bloqueantes

  2. Tratamento de CancellationToken: Respeito ao cancelamento cooperativo

  3. Validação de Resultado: Verificação explícita de null

  4. Retorno Tipado: Utilização do padrão Result<T> para comunicação de sucesso/falha

Padrões de Mapeamento: Embora mencionado o uso de mapeadores automáticos como AutoMapper ou Mapster, a implementação atual utiliza mapeamento manual por:

  1. Simplicidade: Para casos simples, o mapeamento manual é mais claro

  2. Performance: Eliminação de overhead de reflexão

  3. Controle: Visibilidade completa da transformação

Alternativas de mapeamento mencionadas:

  • Cast Operator: Sobrecarga de operadores de conversão

  • Extension Methods: Métodos de extensão para conversão

  • Métodos de Fábrica: Métodos estáticos na classe de resposta

Referência ao Projeto de Domínio

O projeto de aplicação referencia o domínio para acessar as definições de interfaces e padrões:

Esta referência estabelece a direção correta de dependência na Arquitetura Limpa: a camada de aplicação depende do domínio, nunca o contrário.

Benefícios da Abordagem

Desacoplamento Arquitetural

  1. Separação de Responsabilidades: Cada componente tem uma única responsabilidade bem definida

  2. Independência de Framework: O domínio permanece livre de dependências externas

  3. Testabilidade Isolada: Cada componente pode ser testado independentemente

Manutenibilidade

  1. Localização de Mudanças: Alterações em regras de negócio são confinadas ao domínio

  2. Evolução Independente: A API pode evoluir sem impactar a lógica de negócio

  3. Reutilização: Casos de uso podem ser consumidos por diferentes apresentações (Web API, Console, etc.)

Robustez

  1. Tratamento Explícito de Erros: O padrão Result elimina exceções para fluxos de negócio esperados

  2. Tipagem Forte: Compilador detecta incompatibilidades em tempo de compilação

  3. Imutabilidade: record e padrões funcionais reduzem efeitos colaterais

Considerações de Implementação

Escolha de Records

A utilização de record oferece:

  • Igualdade por Valor: Comparação baseada em conteúdo, não referência

  • Imutabilidade Nativa: Propriedades são init-only por padrão

  • Sintaxe Concisa: Declaração simplificada com parâmetros posicionais

  • Performance: Otimizações para hash code e igualdade

Padrão Result vs Exceções

O padrão Result é preferido sobre exceções para:

  • Erros Esperados: Fluxos alternativos de negócio (como "recurso não encontrado")

  • Performance: Exceções são computacionalmente caras para fluxos normais

  • Clareza: O tipo de retorno declara explicitamente possíveis falhas

  • Composição: Facilidade em combinar múltiplas operações com resultados

MediatR como Implementação do Padrão Mediator

Vantagens do MediatR:

  • Desacoplamento Total: Emissores não conhecem receptores

  • Pipeline Behaviors: Middleware para cross-cutting concerns (logging, validação, etc.)

  • Pub/Sub: Suporte a notificações para múltiplos handlers

  • Simplicidade: Configuração mínima necessária

Próximas Etapas

Com o caso de uso implementado, as próximas ações incluem:

  1. Implementação de Infraestrutura: Repositórios concretos com Entity Framework

  2. Configuração de API: Endpoints que consomem os casos de uso

  3. Validação: Pipeline behaviors para validação de commands

  4. Logging e Monitoring: Cross-cutting concerns através de behaviors

  5. Testes: Testes unitários para handlers e de integração para fluxos completos

Esta estrutura estabelece uma base sólida para desenvolvimento de aplicações complexas, onde a clareza, manutenibilidade e testabilidade são priorizadas através da aplicação consistente dos princípios da Arquitetura Limpa.

Atualizado