Defining Application Use Cases

Use cases are the application layer entry points that transform a business intent into executable software. At Lino, they are in Application/UseCases and are organized around the entities, aggregates, enumerations, modules and services defined in the previous stages of the project.


Lino follows a structure-oriented CQRS: Commands represent operations that change state, while Queries represent operations that read data. This separation keeps writing rules, reading models, validations, tracking, logging, and response contracts explicit and easier to maintain.


THE Result Pattern standardizes the return of operations, encapsulating success, failure, error messages and resulting data. Instead of using exceptions for expected business flows, handler returns a Result or Result<T> with value, error or no-content as appropriate.


CLI generates the initial files for a use case, but the generated code does not replace domain analysis. The developer must still review handler, confirm the selected properties, add business rules, adjust validations and verify that the use case respects the limits of the module and service.

Important: Lino projects can be created with support for CQRS and mediator. Commands and Queries generated use application abstractions like ICommand, IQuery, ICommandHandler, IQueryHandler and Tolitech.Results to standardize the handling of requests and results of operations.

Use Case Overview

One Use Case represents a complete application operation: receives an input model, validates the data, coordinates dependencies, invokes domain behavior when necessary, persists or queries data, and returns a standardized result. It is where the application orchestrates a business flow without moving invariants outside the domain model.

In solutions generated by Lino, use cases are grouped in the application project using a predictable structure:

src/Services/<ServiceName>/<ModuleName>/Application/UseCases/<EntityName>/
β”œβ”€β”€ Commands/
β”‚   └── <CommandName>/
└── Queries/
    └── <QueryName>/

This organization makes it clear to which service, module and entity each feature belongs. It also keeps the writing model and reading model independent, which is useful when a screen, API, integration, or background process only needs one side of the behavior.

Command or Query?

Type Intention Examples common Resultado
Command Change the system state. CreateOrder, UpdateVehicle, DeleteMaintenance, SavePermissionsByRoleId. Result, Result<CommandResult> or no-content on success.
Query Read data without changing state. GetOrderById, ListCustomers, GetVehicleAvailability. Result<QueryResult>, list, page, or consumer-specific DTO.

What belongs to the application use case

  • Entry contract: the record of command or query with only the data necessary for the operation.
  • Validation: rules that check request format, required fields, ranges, identifiers, filters and other input restrictions before handler executes the flow.
  • handler orchestration: calls to dependencies, access to repositories, context queries, use of Unit of Work, result composition, logging and tracing.
  • Result contract: a small response object or contentless result that gives caller exactly what it needs to continue.

What should be left out of the use case

  • Domain invariants must remain in entities, Value Objects, domain services and domain methods.
  • Infrastructure Details they must remain behind abstractions, repositories, database contexts, file services, messaging integrations, and external clients.
  • Presentation concerns, such as UI state, labels, layouts and component behavior, must remain in the web app layer.

A good use case is explicit, small enough to be understood, and strict with boundaries: it coordinates the work, but doesn't become a place where all the system's rules are mixed up.

Commands

One Command is an immutable message that carries only the data necessary to perform an action that modifies the state of the system. Common examples are CreateCustomer, UpdateVehicle, DeleteMaintenance, ConfirmOrder and SavePermissionsByRoleId. Commands should be named actions because they tell the application to do something.

In Lino, Commands generated are records that implement the abstraction of command and return a standardized result. Creation Commands typically returns a result object with the identifier or minimum information required by caller, while update and delete commands typically return no-content when the operation completes successfully.

Characteristics of a Command

  • Immutability: implemented as record or class with only get, without mutable public setters.
  • Name in imperative: reflects business action, such as CreateOrder, UpdateCustomerAddress or ChangeProductPrice.
  • Minimum data: contains only the fields necessary to perform the operation, without transporting entire entities or large volumes of data.
  • Isolated validation: each Command has its own rules to ensure that the request is consistent before reaching handler.
  • Independence from UI: Command represents the intention of the application, not the button, form or component that triggered the operation.

When to create a Command

  • Use a Command when data will be created, changed, removed, joined, imported, approved, canceled, or mutated in any way.
  • Keep payload focused on the operation; don't pass an entire entity when few fields are enough.
  • Choose names that describe the business action, not the visual event that initiated it.
  • Review whether the action belongs to the current module or whether it should be expressed by integration, event or shadow entity in another module.

Command Validators

You Command Validators ensure that Command is well-formed and meets entry requirements before it is sent to handler. In Lino, these validations are implemented with FluentValidation, a common library in .NET projects as it offers a fluent and readable API for creating rules.

Common Validation Rules

  • NotEmpty and NotNull for mandatory fields.
  • InclusiveBetween for numeric ranges, monetary values, percentages, minimum and maximum quantities.
  • MaximumLength, MinimumLength and Length for string size.
  • RuleForEach to validate collection items, such as List<T>.
  • Must for custom rules such as document format, date consistency, and invalid field combinations.

validator protects the leading edge. Actual domain invariants, such as state boundaries, allowed transitions, and rules that must hold regardless of caller, continue to belong to the aggregate, entity, Value Object, or domain service.

Command Handlers

THE Command Handler executes the application logic associated with Command. It orchestrates repositories, contexts, IUnitOfWork, auxiliary services, logging, tracing, integration calls and domain events when necessary.

Implementation pattern for a handler

  • Receive dependencies via dependency injection, such as repositories, external services, contexts and Unit of Work.
  • Validate the existence and availability of data necessary for the operation.
  • Load the correct aggregate or entity when the operation depends on existing state.
  • Call domain methods instead of duplicating invariants in handler.
  • Persist changes through IUnitOfWork or project persistence abstractions.
  • Log domain, Outbox, or integration events when the operation needs to communicate other parts of the system.
  • Return Result or Result<T> success, known failure, or minimal response data.

Command Results and Result Pattern

THE Command Result should be a simple DTO with just the data needed for caller to proceed. In creation, it usually includes the Id generated. On update or delete, there may be no payload. When the operation fails due to an expected condition, the return must carry standardized error information.

THE Result Pattern encapsulates the result of an operation as success or failure. Instead of throwing exceptions for predictable scenarios like entity not found, invalid state, or business validation, handler returns a type like Result<T> with value, error and metadata when necessary.

  • If successful, the result may expose a Value, such as a small DTO or crafted identifier.
  • On failure, the result stores standardized codes or messages, often defined in reusable error definitions.
  • The error handling flow is consistent between Application, API, typed clients, and UI.

Creating a Command with CLI

Lino simplifies the generation of the necessary artifacts for a new Command through the command:

lino command new

CLI also supports options to reduce questions in the wizard:

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 or --service: target service.
  • -m or --module: target module in modularized services.
  • -e or --entity: entity associated with Command.
  • -n, --name, -c or --command: name of Command.

During the interactive flow, Lino confirms service, module, entity, Command name, Command type and, for creation or update operations, the properties that will be part of the request. The types exposed by CLI are Create, Update and Delete.

After confirming, Lino creates files like:

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

Example of generated structure

Consider Command CreatePerson. The generated structure will look like:

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Application/
                β”œβ”€β”€ <ProjectName>.<ServiceName>.Application.csproj
                └── UseCases/
                    └── People/
                        β”œβ”€β”€ Commands/
                        β”‚   └── CreatePerson/
                        β”‚       β”œβ”€β”€ CreatePersonCommand.cs
                        β”‚       β”œβ”€β”€ CreatePersonCommandValidator.cs
                        β”‚       β”œβ”€β”€ CreatePersonCommandHandler.cs
                        β”‚       └── CreatePersonCommandResult.cs
                        └── Queries/
                            └── ...

For a fleet customized Command, the structure follows the same pattern:

Application/UseCases/Vehicles/Commands/UpdateVehicle/
β”œβ”€β”€ UpdateVehicleCommand.cs
β”œβ”€β”€ UpdateVehicleCommandValidator.cs
β”œβ”€β”€ UpdateVehicleCommandHandler.cs
└── UpdateVehicleCommandResult.cs

Responsibilities of Command files

  • Command: immutable request contract with the entry of the operation.
  • Validator: input validation, typically with mandatory rules, size, range, identifiers and collections.
  • Handler: orchestration of repositories, Unit of Work, contexts, domain methods, logging, tracing and result creation.
  • Result: Minimum response contract for successful operations that need to return data.

Implementation checklist

  1. Run lino command new and choose the correct service, module, entity, type, and properties.
  2. Open the generated Command and remove any field that is not part of the operation contract.
  3. Strengthen validator with business entry rules that cannot be automatically inferred.
  4. Review handler and ensure it invokes domain behavior rather than duplicating invariants at the application layer.
  5. Use IUnitOfWork for persistence and prefer transactional saving when the operation also requires events or reliable Outbox.
  6. Return failures due to errors Result, not by exceptions to expected business results.
  7. Build and test the endpoint, page, or integration that will call Command.

Rule of thumb: the generator delivers the architectural skeleton. The final fix comes from reviewing the use case against the domain language, invariants, persistence needs, and module boundaries.

Queries

One Query represents the intent to obtain data without changing the state of the domain. Queries are designed for efficient reading, returning exactly the fields needed by caller, without loading entire entities when this is not necessary.

Common examples are GetCustomerById, ListCustomers, ListPhoneTypes, ListOrdersByDateRange and a custom query like GetVehicleAvailability. A Query must answer an application-specific question.

Characteristics of a Query

  • Immutable: Just like Commands, a Query must not allow changes once created.
  • Descriptive name: reflects the information sought, such as GetCustomerById or ListOrdersByDateRange.
  • Filters and pagination: can load dates, status, page, page size, search text, ordering and other reading parameters.
  • Projection: must return DTO or view model with only the necessary fields, avoiding direct exposure of domain entities.
  • No side effects: you must not call methods that change state or save changes to the database.

When to create a Query

  • Use a Query when the operation reads data and should not modify state.
  • Use a single-result Query for detail screens, identifier searches, availability checks, or object responses.
  • Use a list Query for grids, picklists, child collections, and selection controls.
  • Use pagination for grids and potentially large data sets.
  • Use simple lists for small reference data, enumerations, and option endpoints.

Query Validators

You Query Validators validate input parameters such as filters, pagination values, date ranges, identifiers, and visibility rules. They are also implemented with FluentValidation.

Common validation rules in Queries

  • GreaterThanOrEqualTo and LessThanOrEqualTo for range filters such as start and end dates.
  • Length, MaximumLength and MinimumLength for text filters, such as name, email or search term.
  • InclusiveBetween for paging, limiting page and pageSize.
  • NotEmpty for mandatory identifiers in detail queries.
  • Must for invalid filter combinations, such as end date less than start date.

Query Handlers

THE Query Handler queries repositories or database context and returns optimized projections. In Lino, it is recommended to use projections with Select, explicit filters, predictable ordering and untracked readings when appropriate.

The handler of Query must be read-only: it does not call state-changing domain methods, does not trigger persistent changes, and does not execute SaveChanges. If a read needs to log audit, initiate an integration, or recalculate state, this usually indicates another use case or separate process.

Query Results

In Lino, results from Queries are represented by records named with the suffix QueryResult. This convention applies to single response, simple list, paged list or screen-specific DTO, API, integration or background process.

Resultquery data must be read-stable contracts. Treat them as DTOs designed for the consumer, not as a shortcut for exposing domain entities directly.

Creating a Query with CLI

Similar to Commands, Lino provides the command:

lino query new

CLI also accepts options such as:

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 or --service: target service.
  • -m or --module: target module.
  • -e or --entity: entity associated with Query.
  • -n, --name, -q or --query: name of Query.

During the interactive flow, Lino asks whether Query returns a single result or a list. When the response is a list, it also asks whether it should be paginated. Finally, it allows you to select the properties that should be returned or used by the generated projection.

Lino will automatically generate:

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

Example of generated structure for Queries

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

For a customized vehicle availability Query, the structure follows the same pattern:

Application/UseCases/Vehicles/Queries/GetVehicleAvailability/
β”œβ”€β”€ GetVehicleAvailabilityQuery.cs
β”œβ”€β”€ GetVehicleAvailabilityQueryValidator.cs
β”œβ”€β”€ GetVehicleAvailabilityQueryHandler.cs
└── GetVehicleAvailabilityQueryResult.cs

Responsibilities of Query files

  • Query: immutable request contract with identifiers, filters, ordering, pagination or date ranges.
  • Validator: validation of identifiers, pagination, mandatory filters, date ranges and search criteria.
  • Handler: reading orchestration using application context, projections, filters, ordering, pagination, logging, tracing and result creation.
  • Result: response DTO modeled on caller, often with internal records for list items.

Implementation checklist

  1. Run lino query new and choose service, module, entity, Query name, return type, paging mode and properties.
  2. Review the request contract and only keep filters that are really necessary for caller.
  3. Strengthen validator, especially for required identifiers, date ranges, page size limits, and invalid filter combinations.
  4. Adjust the handler projection to return only the fields required by UI, API, integration, or background process.
  5. Keep Query read-only: don't call domain methods that mutate state and don't save changes to handler.
  6. Propagate expected failures across Result, using existing standardized errors where appropriate.
  7. Build and test the consumer that calls Query, such as API endpoint, Blazor page, in-process integration, or typed client.

Example of custom Query

In the integration flow between SaaS modules, a customized vehicle availability Query can be created with lino query new. The generated structure provides Query, Handler, Result and Validator. Then, the developer adds the necessary inputs, such as vehicle identifier, start date and end date, validates that the range is correct and implements the handler logic. This is the expected flow: generate the consistent structure first and then complete the domain-specific behavior with explicit code.

Read model guidance: results of Query must be stable contracts. Treat them as DTOs designed for caller, not as a shortcut for exposing domain entities directly.

Conclusion

Defining use cases in Lino is transforming the domain model into clear application operations. Commands handle state changes, Queries handle reads, validators protect the input edge, handlers orchestrate the flow, and result objects standardize the output.

The fastest flow tends to be: model the domain, generate the necessary Commands and Queries, review the generated files, complete the business behavior, expose the use case through a API or page when necessary and validate everything with build and tests. This keeps development fast without losing architectural control.

As the project grows, keep each use case focused on a business intention, respect service and module boundaries and use events, integrations or shadow entities when another module needs to participate in the process.

An unhandled error has occurred. Reload πŸ—™