Definindo Casos de Uso da Aplicação

Casos de uso são os pontos de entrada da camada de aplicação que transformam uma intenção de negócio em software executável. No Lino, eles ficam em Application/UseCases e são organizados em torno das entidades, aggregates, enumerações, módulos e serviços definidos nas etapas anteriores do projeto.


O Lino segue uma estrutura orientada a CQRS: Commands representam operações que alteram estado, enquanto Queries representam operações que leem dados. Essa separação mantém regras de escrita, modelos de leitura, validações, rastreamento, logging e contratos de resposta explícitos e mais fáceis de manter.


O Result Pattern padroniza o retorno das operações, encapsulando sucesso, falha, mensagens de erro e dados resultantes. Em vez de usar exceções para fluxos esperados de negócio, o handler retorna um Result ou Result<T> com valor, erro ou no-content conforme o caso.


O CLI gera os arquivos iniciais de um caso de uso, mas o código gerado não substitui a análise do domínio. O desenvolvedor ainda deve revisar o handler, confirmar as propriedades selecionadas, adicionar regras de negócio, ajustar validações e verificar se o caso de uso respeita os limites do módulo e do serviço.

Importante: projetos Lino podem ser criados com suporte a CQRS e mediator. Commands e Queries gerados usam abstrações de aplicação como ICommand, IQuery, ICommandHandler, IQueryHandler e Tolitech.Results para padronizar o tratamento de requisições e resultados de operações.

Visão Geral de Use Cases

Um Use Case representa uma operação completa da aplicação: recebe um modelo de entrada, valida os dados, coordena dependências, invoca comportamento de domínio quando necessário, persiste ou consulta dados e retorna um resultado padronizado. Ele é o lugar onde a aplicação orquestra um fluxo de negócio sem mover invariantes para fora do modelo de domínio.

Em soluções geradas pelo Lino, os use cases são agrupados no projeto de aplicação usando uma estrutura previsível:

src/Services/<ServiceName>/<ModuleName>/Application/UseCases/<EntityName>/
├── Commands/
│   └── <CommandName>/
└── Queries/
    └── <QueryName>/

Essa organização deixa claro a qual serviço, módulo e entidade cada feature pertence. Também mantém o modelo de escrita e o modelo de leitura independentes, o que é útil quando uma tela, API, integração ou processo em background precisa apenas de um lado do comportamento.

Command ou Query?

Tipo Intenção Exemplos Resultado comum
Command Alterar o estado do sistema. CreateOrder, UpdateVehicle, DeleteMaintenance, SavePermissionsByRoleId. Result, Result<CommandResult> ou no-content em sucesso.
Query Ler dados sem alterar estado. GetOrderById, ListCustomers, GetVehicleAvailability. Result<QueryResult>, lista, página ou DTO específico para o consumidor.

O que pertence ao caso de uso da aplicação

  • Contrato de entrada: o record de command ou query com apenas os dados necessários para a operação.
  • Validação: regras que verificam formato da requisição, campos obrigatórios, intervalos, identificadores, filtros e outras restrições de entrada antes do handler executar o fluxo.
  • Orquestração do handler: chamadas a dependências, acesso a repositórios, consultas ao contexto, uso de Unit of Work, composição de resultado, logging e tracing.
  • Contrato de resultado: um objeto pequeno de resposta ou um resultado sem conteúdo que entrega ao caller exatamente o que ele precisa para continuar.

O que deve ficar fora do caso de uso

  • Invariantes de domínio devem permanecer em entidades, Value Objects, domain services e métodos de domínio.
  • Detalhes de infraestrutura devem permanecer atrás de abstrações, repositórios, contextos de banco de dados, serviços de arquivos, integrações de mensageria e clients externos.
  • Preocupações de apresentação, como estado de UI, labels, layouts e comportamento de componentes, devem permanecer na camada de web app.

Um bom use case é explícito, pequeno o suficiente para ser entendido e rigoroso com fronteiras: ele coordena o trabalho, mas não vira um lugar onde todas as regras do sistema são misturadas.

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. Exemplos comuns são CreateCustomer, UpdateVehicle, DeleteMaintenance, ConfirmOrder e SavePermissionsByRoleId. Commands devem ser nomeados como ações porque dizem para a aplicação fazer algo.

No Lino, Commands gerados são records que implementam a abstração de command e retornam um resultado padronizado. Commands de criação normalmente retornam um objeto de resultado com o identificador ou a informação mínima exigida pelo caller, enquanto commands de atualização e exclusão geralmente retornam no-content quando a operação é concluída com sucesso.

Características de um Command

  • Imutabilidade: implementado como record ou classe apenas com get, sem setters públicos mutáveis.
  • Nome no imperativo: reflete a ação de negócio, como CreateOrder, UpdateCustomerAddress ou ChangeProductPrice.
  • Dados mínimos: contém somente os campos necessários para executar a operação, sem transportar entidades inteiras ou grandes volumes de dados.
  • Validação isolada: cada Command possui regras próprias para garantir que a requisição esteja consistente antes de chegar ao handler.
  • Independência da UI: o Command representa a intenção da aplicação, não o botão, formulário ou componente que disparou a operação.

Quando criar um Command

  • Use um Command quando dados serão criados, alterados, removidos, associados, importados, aprovados, cancelados ou mutados de qualquer forma.
  • Mantenha o payload focado na operação; não passe uma entidade inteira quando poucos campos são suficientes.
  • Prefira nomes que descrevem a ação de negócio, não o evento visual que a iniciou.
  • Revise se a ação pertence ao módulo atual ou se deveria ser expressa por integração, evento ou shadow entity em outro módulo.

Command Validators

Os Command Validators garantem que o Command esteja bem-formado e atenda aos requisitos de entrada antes de ser enviado ao handler. No Lino, essas validações são implementadas com FluentValidation, uma biblioteca comum em projetos .NET por oferecer uma API fluente e legível para criação de regras.

Regras comuns de validação

  • NotEmpty e NotNull para campos obrigatórios.
  • InclusiveBetween para faixas numéricas, valores monetários, percentuais, quantidades mínimas e máximas.
  • MaximumLength, MinimumLength e Length para tamanho de strings.
  • RuleForEach para validar itens de coleções, como List<T>.
  • Must para regras customizadas, como formato de documentos, consistência de datas e combinações inválidas de campos.

O validator protege a borda de entrada. Invariantes reais do domínio, como limites de estado, transições permitidas e regras que devem valer independentemente do caller, continuam pertencendo ao aggregate, entidade, Value Object ou domain service.

Command Handlers

O Command Handler executa a lógica de aplicação associada ao Command. Ele orquestra repositórios, contextos, IUnitOfWork, serviços auxiliares, logging, tracing, chamadas de integração e eventos de domínio quando necessário.

Padrão de implementação de um handler

  • Receber dependências por injeção de dependência, como repositórios, serviços externos, contextos e Unit of Work.
  • Validar a existência e disponibilidade dos dados necessários para a operação.
  • Carregar o aggregate ou entidade correta quando a operação depende de estado existente.
  • Chamar métodos de domínio em vez de duplicar invariantes no handler.
  • Persistir alterações por meio de IUnitOfWork ou abstrações de persistência do projeto.
  • Registrar eventos de domínio, Outbox ou integrações quando a operação precisar comunicar outras partes do sistema.
  • Retornar Result ou Result<T> com sucesso, falha conhecida ou dados mínimos de resposta.

Command Results e Result Pattern

O Command Result deve ser um DTO simples com apenas os dados necessários para que o caller prossiga. Em criação, costuma incluir o Id gerado. Em atualização ou exclusão, pode não haver payload. Quando a operação falha por uma condição esperada, o retorno deve carregar informações padronizadas de erro.

O Result Pattern encapsula o resultado de uma operação como sucesso ou falha. Em vez de lançar exceções para cenários previsíveis, como entidade não encontrada, estado inválido ou validação de negócio, o handler retorna um tipo como Result<T> com valor, erro e metadados quando necessário.

  • Em sucesso, o resultado pode expor um Value, como um DTO pequeno ou identificador criado.
  • Em falha, o resultado armazena códigos ou mensagens padronizadas, frequentemente definidos em error definitions reutilizáveis.
  • O fluxo de tratamento de erros fica consistente entre Application, API, typed clients e UI.

Criando um Command com o CLI

O Lino simplifica a geração dos artefatos necessários para um novo Command por meio do comando:

lino command new

O CLI também aceita opções para reduzir perguntas no assistente:

lino command new --service <ServiceName> --module <ModuleName> --entity <EntityName> --name <CommandName>
lino command new --name <CommandName> --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino command list --service <ServiceName> --module <ModuleName> --entity <EntityName>
  • -s ou --service: serviço de destino.
  • -m ou --module: módulo de destino em serviços modularizados.
  • -e ou --entity: entidade associada ao Command.
  • -n, --name, -c ou --command: nome do Command.

Durante o fluxo interativo, o Lino confirma serviço, módulo, entidade, nome do Command, tipo do Command e, para operações de criação ou atualização, as propriedades que farão parte da requisição. Os tipos expostos pelo CLI são Create, Update e Delete.

Após confirmar, o Lino cria arquivos como:

  • CreateOrderCommand.cs
  • CreateOrderCommandValidator.cs
  • CreateOrderCommandHandler.cs
  • CreateOrderCommandResult.cs

Exemplo de estrutura gerada

Considere o Command CreatePerson. A estrutura gerada será semelhante a:

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Application/
                ├── <ProjectName>.<ServiceName>.Application.csproj
                └── UseCases/
                    └── People/
                        ├── Commands/
                        │   └── CreatePerson/
                        │       ├── CreatePersonCommand.cs
                        │       ├── CreatePersonCommandValidator.cs
                        │       ├── CreatePersonCommandHandler.cs
                        │       └── CreatePersonCommandResult.cs
                        └── Queries/
                            └── ...

Para um Command customizado de frota, a estrutura segue o mesmo padrão:

Application/UseCases/Vehicles/Commands/UpdateVehicle/
├── UpdateVehicleCommand.cs
├── UpdateVehicleCommandValidator.cs
├── UpdateVehicleCommandHandler.cs
└── UpdateVehicleCommandResult.cs

Responsabilidades dos arquivos de Command

  • Command: contrato imutável de requisição com a entrada da operação.
  • Validator: validação de entrada, normalmente com regras de obrigatoriedade, tamanho, faixa, identificadores e coleções.
  • Handler: orquestração de repositórios, Unit of Work, contextos, métodos de domínio, logging, tracing e criação do resultado.
  • Result: contrato mínimo de resposta para operações bem-sucedidas que precisam retornar dados.

Checklist de implementação

  1. Execute lino command new e escolha serviço, módulo, entidade, tipo e propriedades corretas.
  2. Abra o Command gerado e remova qualquer campo que não faça parte do contrato da operação.
  3. Fortaleça o validator com regras de entrada de negócio que não podem ser inferidas automaticamente.
  4. Revise o handler e garanta que ele chame comportamento de domínio em vez de duplicar invariantes na camada de aplicação.
  5. Use IUnitOfWork para persistência e prefira salvamento transacional quando a operação também exigir eventos ou Outbox confiável.
  6. Retorne falhas por erros de Result, não por exceções para resultados de negócio esperados.
  7. Compile e teste o endpoint, página ou integração que chamará o Command.

Regra prática: o gerador entrega o esqueleto arquitetural. A correção final vem da revisão do caso de uso contra a linguagem do domínio, invariantes, necessidades de persistência e fronteiras do módulo.

Queries

Uma Query representa a intenção de obter dados sem alterar o estado do domínio. Queries são projetadas para leitura eficiente, retornando exatamente os campos necessários para o caller, sem carregar entidades inteiras quando isso não é necessário.

Exemplos comuns são GetCustomerById, ListCustomers, ListPhoneTypes, ListOrdersByDateRange e uma query customizada como GetVehicleAvailability. Uma Query deve responder uma pergunta específica da aplicação.

Características de uma Query

  • Imutável: assim como Commands, uma Query não deve permitir alterações depois de criada.
  • Nome descritivo: reflete a informação buscada, como GetCustomerById ou ListOrdersByDateRange.
  • Filtros e paginação: pode carregar datas, status, página, tamanho de página, texto de busca, ordenação e outros parâmetros de leitura.
  • Projeção: deve retornar DTO ou view model com apenas os campos necessários, evitando exposição direta de entidades de domínio.
  • Sem efeitos colaterais: não deve chamar métodos que alterem estado nem salvar alterações no banco.

Quando criar uma Query

  • Use uma Query quando a operação lê dados e não deve modificar estado.
  • Use uma Query de resultado único para telas de detalhe, buscas por identificador, checagens de disponibilidade ou respostas de um objeto.
  • Use uma Query de lista para grids, listas de opções, coleções filhas e controles de seleção.
  • Use paginação para grids e conjuntos de dados potencialmente grandes.
  • Use listas simples para dados de referência pequenos, enumerações e endpoints de opções.

Query Validators

Os Query Validators validam parâmetros de entrada como filtros, valores de paginação, intervalos de data, identificadores e regras de visibilidade. Eles também são implementados com FluentValidation.

Regras comuns de validação em Queries

  • GreaterThanOrEqualTo e LessThanOrEqualTo para filtros de intervalo, como datas inicial e final.
  • Length, MaximumLength e MinimumLength para filtros de texto, como nome, e-mail ou termo de busca.
  • InclusiveBetween para paginação, limitando page e pageSize.
  • NotEmpty para identificadores obrigatórios em consultas de detalhe.
  • Must para combinações inválidas de filtros, como data final menor que data inicial.

Query Handlers

O Query Handler consulta repositórios ou o contexto do banco de dados e retorna projeções otimizadas. No Lino, recomenda-se usar projeções com Select, filtros explícitos, ordenação previsível e leituras sem tracking quando apropriado.

O handler de Query deve ser read-only: ele não chama métodos de domínio que alteram estado, não dispara alterações persistentes e não executa SaveChanges. Caso uma leitura precise registrar auditoria, iniciar uma integração ou recalcular estado, isso geralmente indica outro use case ou processo separado.

Query Results

No Lino, resultados de Queries são representados por records nomeados com o sufixo QueryResult. Essa convenção vale para resposta única, lista simples, lista paginada ou DTO específico para tela, API, integração ou processo em background.

Resultados de consulta devem ser contratos estáveis de leitura. Trate-os como DTOs desenhados para o consumidor, não como atalho para expor entidades de domínio diretamente.

Criando uma Query com o CLI

Similar aos Commands, o Lino disponibiliza o comando:

lino query new

O CLI também aceita opções como:

lino query new --service <ServiceName> --module <ModuleName> --entity <EntityName> --name <QueryName>
lino query new --name <QueryName> --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino query list --service <ServiceName> --module <ModuleName> --entity <EntityName>
  • -s ou --service: serviço de destino.
  • -m ou --module: módulo de destino.
  • -e ou --entity: entidade associada à Query.
  • -n, --name, -q ou --query: nome da Query.

Durante o fluxo interativo, o Lino pergunta se a Query retorna um único resultado ou uma lista. Quando a resposta é uma lista, também pergunta se ela deve ser paginada. Por fim, permite selecionar as propriedades que devem ser retornadas ou usadas pela projeção gerada.

O Lino gerará automaticamente:

  • GetOrderByIdQuery.cs
  • GetOrderByIdQueryValidator.cs
  • GetOrderByIdQueryHandler.cs
  • GetOrderByIdQueryResult.cs

Exemplo de estrutura gerada para Queries

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Application/
                ├── <ProjectName>.<ServiceName>.Application.csproj
                └── UseCases/
                    └── Orders/
                        ├── Commands/
                        |   └── ...
                        └── Queries/
                            └── GetOrderById/
                                ├── GetOrderByIdQuery.cs
                                ├── GetOrderByIdQueryValidator.cs
                                ├── GetOrderByIdQueryHandler.cs
                                └── GetOrderByIdQueryResult.cs

Para uma Query customizada de disponibilidade de veículo, a estrutura segue o mesmo padrão:

Application/UseCases/Vehicles/Queries/GetVehicleAvailability/
├── GetVehicleAvailabilityQuery.cs
├── GetVehicleAvailabilityQueryValidator.cs
├── GetVehicleAvailabilityQueryHandler.cs
└── GetVehicleAvailabilityQueryResult.cs

Responsabilidades dos arquivos de Query

  • Query: contrato imutável de requisição com identificadores, filtros, ordenação, paginação ou intervalos de data.
  • Validator: validação de identificadores, paginação, filtros obrigatórios, intervalos de data e critérios de busca.
  • Handler: orquestração de leitura usando contexto de aplicação, projeções, filtros, ordenação, paginação, logging, tracing e criação do resultado.
  • Result: DTO de resposta modelado para o caller, frequentemente com records internos para itens de lista.

Checklist de implementação

  1. Execute lino query new e escolha serviço, módulo, entidade, nome da Query, tipo de retorno, modo de paginação e propriedades.
  2. Revise o contrato de requisição e mantenha apenas filtros realmente necessários para o caller.
  3. Fortaleça o validator, especialmente para identificadores obrigatórios, intervalos de data, limites de page size e combinações inválidas de filtros.
  4. Ajuste a projeção do handler para retornar apenas os campos exigidos pela UI, API, integração ou processo em background.
  5. Mantenha a Query read-only: não chame métodos de domínio que mutam estado e não salve alterações no handler.
  6. Propague falhas esperadas por Result, usando erros padronizados existentes quando apropriado.
  7. Compile e teste o consumidor que chama a Query, como endpoint de API, página Blazor, integração in-process ou typed client.

Exemplo de Query customizada

No fluxo de integração entre módulos de um SaaS, uma Query customizada de disponibilidade de veículo pode ser criada com lino query new. A estrutura gerada fornece Query, Handler, Result e Validator. Depois, o desenvolvedor adiciona as entradas necessárias, como identificador do veículo, data inicial e data final, valida se o intervalo está correto e implementa a lógica do handler. Esse é o fluxo esperado: gerar primeiro a estrutura consistente e depois completar o comportamento específico do domínio com código explícito.

Orientação de read model: resultados de Query devem ser contratos estáveis. Trate-os como DTOs desenhados para o caller, não como atalho para expor entidades de domínio diretamente.

Conclusão

Definir use cases no Lino é transformar o modelo de domínio em operações claras de aplicação. Commands tratam mudanças de estado, Queries tratam leituras, validators protegem a borda de entrada, handlers orquestram o fluxo e objetos de resultado padronizam a saída.

O fluxo mais rápido costuma ser: modelar o domínio, gerar os Commands e Queries necessários, revisar os arquivos gerados, completar o comportamento de negócio, expor o caso de uso por uma API ou página quando necessário e validar tudo com build e testes. Isso mantém o desenvolvimento rápido sem perder controle arquitetural.

Conforme o projeto cresce, mantenha cada use case focado em uma intenção de negócio, respeite fronteiras de serviço e módulo e use eventos, integrações ou shadow entities quando outro módulo precisar participar do processo.

Ocorreu um erro não tratado. Recarregar 🗙