Definindo Casos de Uso da Aplicação
Nesta seção, exploraremos como definir os casos de uso da sua aplicação utilizando o Lino CLI. Casos de uso representam interações específicas entre usuários ou sistemas externos e a aplicação, encapsulando regras de negócio e promovendo uma arquitetura orientada ao domínio. Abordaremos a separação entre operações de leitura e escrita por meio do padrão CQRS, além de padronizar o retorno de respostas utilizando o Result Pattern.
O padrão CQRS (Command Query Responsibility Segregation) propõe a separação das operações que modificam o estado da aplicação (Commands) daquelas que apenas consultam dados (Queries). Essa abordagem permite otimizar cada tipo de operação de forma independente, melhorando a escalabilidade, a performance e a manutenção do sistema.
O Result Pattern é utilizado para padronizar o retorno de operações, encapsulando informações sobre sucesso ou falha, mensagens de erro e dados resultantes. Isso facilita o tratamento consistente de respostas em diferentes camadas da aplicação.
Veremos como são estruturados os arquivos de Commands e Queries, as validações de dados, como são aplicadas as lógicas de domínio nos Handlers e como retornar resultados de forma padronizada. Tudo isso através da criação automática desses componentes por meio do CLI do Lino.
Nota: Embora não seja obrigatório, o Lino atualmente oferece duas opções para aplicar o padrão CQRS na camada de aplicação: utilizando o padrão Mediator por meio da biblioteca MediatR (de Jimmy Bogard) ou da biblioteca Mediator (de Martin Othamar).
Visão Geral de Use Cases
Um Use Case representa uma interação completa entre usuários ou sistemas externos e a aplicação, descrevendo cenários de negócio específicos. No Lino, cada Use Case é dividido em:
- Command: representa a intenção de modificar o estado do sistema (criar, atualizar, excluir, etc.).
- Query: representa a intenção de consultar dados sem alterar o estado do domínio.
Essa separação favorece a clareza do código, facilita testes, permite escalabilidade independente para leitura e escrita, e alinha-se a princípios de arquitetura limpos e boas práticas de Domain-Driven Design (DDD).
Commands
Um Command é uma mensagem imutável que carrega apenas os dados necessários para executar uma ação que modifica o estado do sistema (por exemplo, CreateInvoice, DeactivateUser).
Ele deve conter apenas as propriedades que representam as informações essenciais para realizar a operação.
Características de um Command
- Imutabilidade: implementado como um
recordou classe apenas comget, sem setters públicos. - Nome no imperativo: reflete a ação que será executada, por exemplo,
CreateOrder,UpdateCustomerAddress. - Dados mínimos: contém somente os campos necessários para executar a operação, sem retornar grandes volumes de dados.
- Validação isolada: cada Command possui regras de validação próprias, garantindo que esteja consistente antes de chegar ao manipulador (Handler).
Command Validators
Os Command Validators garantem que o Command esteja bem-formado e atendendo aos requisitos de negócio antes de ser enviado ao Handler. No Lino, utilizamos a biblioteca FluentValidation para implementar essas validações, pois é amplamente adotada em projetos .NET e oferece Fluent API para criação de regras.
Regras Comuns de Validação
NotEmpty,NotNullpara campos obrigatórios.InclusiveBetweenpara faixas numéricas (ex.: valores monetários, quantidade mínima/máxima).MaximumLengtheMinimumLengthpara tamanho de strings.RuleForEachpara validação de itens em coleções (List<T>).Mustpara regras customizadas (ex.: formato de documentos, validação de data).
Command Handlers
O Command Handler é responsável por executar a lógica de domínio associada àquele Command. Ele orquestra repositórios, unidades de trabalho (IUnitOfWork), serviços auxiliares e dispara eventos de domínio quando necessário.
Padrão de Implementação de um Handler
- Receber dependências (repositórios, serviços externos, UnitOfWork) por injeção de dependência.
- Mapear e instanciar entidades do domínio, garantindo a consistência inicial.
- Aplicar regras de negócio (validações adicionais, cálculo de valores, disparo de eventos de domínio).
- Persistir alterações por meio de
IUnitOfWorkou repositórios diretos. - Retornar um Command Result, que indica sucesso ou fracasso (
Result<T>).
Command Results e Result Pattern
O Command Result deve ser um DTO simples contendo apenas os dados mínimos necessários para que o caller (por exemplo, uma API ou front-end) prossiga. Geralmente, inclui o identificador da entidade criada ou atualizada (Id) e, se houver falha, informações padronizadas sobre o erro.
Para cenários onde o Command pode falhar, utilizamos o Result Pattern: uma abstração que encapsula o resultado de uma operação, podendo ser sucesso ou falha. Em .NET, é comum usar tipos como Result<T> (ou bibliotecas similares), que:
- Permitem definir um
Valueem caso de sucesso (por exemplo, um DTO simples). - Em caso de falha, armazenam códigos ou mensagens padronizadas (
Error), possivelmente com metadados extras (códigos HTTP, detalhes de validação, etc.). - Facilitam a unificação do fluxo de tratamento de erros em toda a API, mantendo consistência entre camadas.
Criando um Command com o CLI
O Lino simplifica a geração de todos os artefatos necessários para um novo Command por meio do comando:
lino command new
Ao executar esse comando, o assistente interativo solicitará:
- Serviço — Nome do serviço onde o Command será criado.
- Módulo — Em serviços modularizados, define o módulo de destino.
- Entidade — Entidade de domínio relacionada ao Command (opcional, mas recomendado).
- Nome do Command — Nome final do Command (por exemplo,
CreateOrder).
Após confirmar, o Lino criará automaticamente os arquivos:
CreateOrderCommand.csCreateOrderCommandValidator.csCreateOrderCommandHandler.csCreateOrderCommandResult.cs
Exemplo de Estrutura Gerada
Considere o Command CreatePerson. A estrutura gerada será semelhante a:
MyApp/
└── src/
└── Services/
└── MyService/
└── Application/
├── MyApp.MyService.Application.csproj
└── UseCases/
└── People/
├── Commands/
│ └── CreatePerson/
│ ├── CreatePersonCommand.cs
│ ├── CreatePersonCommandValidator.cs
│ ├── CreatePersonCommandHandler.cs
│ └── CreatePersonCommandResult.cs
└── Queries/
└── ...
Queries
Uma Query representa a intenção de obter dados sem alterar o estado do domínio. As Queries são projetadas para ser eficientes em leitura, retornando exatamente os campos necessários, sem carregar entidades inteiras quando não requisitado.
Características de uma Query
- Imutável — semelhante aos Commands, a Query não deve permitir alterações após a criação.
- Nome descritivo — reflete a informação buscada, por exemplo,
GetCustomerById,ListOrdersByDateRange. - Filtros e Paginação — pode carregar parâmetros para aplicar buscas específicas (datas, status, página, ordenação).
- Projeção — deve retornar um DTO ou view model com apenas os campos necessários, evitando carregamento de objetos de domínio completos.
Query Validators
Os Query Validators têm a responsabilidade de validar parâmetros de entrada, como filtros, valores de paginação (page, pageSize) e regras de segurança (permissões de usuário, visibilidade de dados). Também são implementados com FluentValidation.
Regras Comuns de Validação em Queries
GreaterThanOrEqualToouLessThanOrEqualTopara filtros de intervalo (ex.: datas).Lengthpara filtros de texto (ex.: busca por nome, e-mail).InclusiveBetweenpara parâmetros de paginação (ex.: número mínimo/máximo de itens por página).
Query Handlers
O Query Handler é responsável por consultar os repositórios ou o contexto do banco de dados e retornar projeções otimizadas, evitando o carregamento completo das entidades de domínio.
No Lino, recomenda-se o uso de projeções com métodos como .Select() para mapear diretamente os dados no formato esperado de DTO, garantindo eficiência e melhor desempenho.
Query Results
No Lino, os resultados de queries são sempre representados por records nomeados com o sufixo QueryResult.
Essa convenção se aplica independentemente do tipo de resposta — seja uma lista de itens (com ou sem paginação) ou um único item detalhado.
Criando uma Query com o CLI
Similar aos Commands, o Lino disponibiliza o comando:
lino query new
O assistente solicitará:
- Serviço — Serviço onde a Query será criada.
- Módulo — Quando aplicável, define o módulo de domínio.
- Entidade — Entidade ou agregado ao qual a Query está associada (opcional).
- Nome da Query — Por exemplo,
GetOrderByIdouListProductsByCategory.
O Lino gerará automaticamente:
GetOrderByIdQuery.csGetOrderByIdQueryValidator.csGetOrderByIdQueryHandler.csGetOrderByIdQueryResult.cs
Exemplo de Estrutura Gerada para Queries
MyApp/
└── src/
└── Services/
└── MyService/
└── Application/
├── MyApp.MyService.Application.csproj
└── UseCases/
└── Orders/
├── Commands/
| └── ...
└── Queries/
└── GetOrderById/
├── GetOrderByIdQuery.cs
├── GetOrderByIdQueryValidator.cs
├── GetOrderByIdQueryHandler.cs
└── GetOrderByIdQueryResult.cs
Conclusão
Após entender como definir e estruturar Use Cases (Commands e Queries) no Lino, você está preparado para criar cenários de negócio robustos e escaláveis, segmentando claramente a lógica de escrita e leitura. Use o CLI para acelerar o desenvolvimento, mas sempre revise e ajuste as implementações conforme necessidades específicas do seu domínio.
Em seguida, explore como lidar com Persistência, Eventos de Domínio e Serviços de Infraestrutura para compor toda a arquitetura da sua aplicação.
