定义应用程序的用例

在本节中,我们将探讨如何使用 Lino CLI 定义应用程序的用例。用例表示用户或外部系统与应用程序之间的特定交互,封装业务规则并促进领域驱动架构。我们将讨论通过 CQRS 模式分离读写操作,并使用 Result Pattern 标准化响应返回。


CQRS(命令查询职责分离)模式提出将修改应用程序状态的操作(Commands)与仅查询数据的操作(Queries)分离。该方法允许独立优化每种操作类型,从而提升系统的可扩展性、性能和维护性。


Result Pattern 用于标准化操作返回结果,封装有关成功或失败、错误信息及结果数据的信息。这有助于在应用程序的不同层中一致地处理响应。


我们将了解 CommandsQueries 文件的结构,数据验证,Handlers 中领域逻辑的应用,以及如何标准化返回结果。所有这些都通过 Lino CLI 自动生成这些组件来实现。

注意: 虽然不是强制性的,但 Lino 当前提供两种在应用层应用 CQRS 模式的选项:通过 Jimmy Bogard 的 MediatR 库或 Martin Othamar 的 Mediator 库使用 Mediator 模式。

用例概述

用例表示用户或外部系统与应用程序之间的完整交互,描述特定的业务场景。在 Lino 中,每个用例被划分为:

  • 命令 (Command):表示修改系统状态的意图(创建、更新、删除等)。
  • 查询 (Query):表示查询数据而不更改域状态的意图。

这种划分有助于代码的清晰性,便于测试,允许读写操作的独立扩展,并符合清洁架构原则和 领域驱动设计 (DDD) 的最佳实践。

命令(Commands)

命令 是一种不可变的消息,仅携带执行修改系统状态操作所需的数据(例如,CreateInvoiceDeactivateUser)。 它应仅包含表示执行操作所必需的关键信息的属性。

命令的特征

  • 不可变性:以 record 或仅含 get 的类实现,不包含公共 setter。
  • 命令式命名:反映将执行的动作,例如 CreateOrderUpdateCustomerAddress
  • 最小数据:仅包含执行操作所需的字段,不返回大量数据。
  • 独立验证:每个命令拥有自己的验证规则,确保在到达处理器(Handler)前处于一致状态。

命令验证器(Command Validators)

命令验证器 确保命令是 格式正确 并满足业务需求,然后才发送到处理器。在 Lino 中,我们使用广泛应用于 .NET 项目的 FluentValidation 库来实现这些验证,提供流畅的 API 来定义规则。

常见验证规则

  • NotEmptyNotNull 用于必填字段。
  • InclusiveBetween 用于数值范围(例如,货币金额,最小/最大数量)。
  • MaximumLengthMinimumLength 用于字符串长度。
  • RuleForEach 用于集合中的项目验证(List<T>)。
  • Must 用于自定义规则(例如,文档格式,日期验证)。

命令处理器(Command Handlers)

命令处理器 负责执行与该命令相关的领域逻辑。它协调仓储、工作单元(IUnitOfWork)、辅助服务,并在必要时触发领域事件。

处理器的实现模式

  • 通过依赖注入接收依赖(仓储、外部服务、工作单元)。
  • 映射并实例化领域实体,确保初始一致性。
  • 应用业务规则(额外验证、数值计算、触发领域事件)。
  • 通过 IUnitOfWork 或直接仓储持久化更改。
  • 返回一个 命令结果,表示成功或失败(Result<T>)。

命令结果与结果模式(Result Pattern)

命令结果 应该是一个简单的 DTO,仅包含调用方(例如 API 或前端)继续操作所需的最小数据。通常包括创建或更新实体的标识符(Id)和失败时的标准化错误信息。

对于命令可能失败的场景,我们使用 结果模式:一种封装操作结果的抽象,可以表示 成功失败。在 .NET 中,常用 Result<T>(或类似库),它们:

  • 允许在成功时定义 Value(例如简单 DTO)。
  • 失败时存储标准化代码或消息(Error),可能带有额外元数据(HTTP 代码、验证细节等)。
  • 简化整个 API 的错误处理流程统一,确保层间一致性。

使用 CLI 创建命令

Lino 通过以下命令简化了新命令所需所有工件的生成:

lino command new

执行该命令时,交互式助手会询问:

  • 服务 — 创建命令的服务名称。
  • 模块 — 在模块化服务中指定目标模块。
  • 实体 — 与命令相关的领域实体(可选,但推荐)。
  • 命令名称 — 命令的最终名称(例如,CreateOrder)。

确认后,Lino 会自动创建以下文件:

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

生成结构示例

以命令 CreatePerson 为例,生成的结构大致如下:

MyApp/
└── src/
    └── Services/
        └── MyService/
            └── Application/
                ├── MyApp.MyService.Application.csproj
                └── UseCases/
                    └── People/
                        ├── Commands/
                        │   └── CreatePerson/
                        │       ├── CreatePersonCommand.cs
                        │       ├── CreatePersonCommandValidator.cs
                        │       ├── CreatePersonCommandHandler.cs
                        │       └── CreatePersonCommandResult.cs
                        └── Queries/
                            └── ...

查询

一个 查询 表示获取数据的意图,而不改变领域的状态。查询被设计为读取高效,只返回所需的字段,在未请求的情况下不会加载整个实体。

查询的特点

  • 不可变 — 与命令类似,查询在创建后不应被修改。
  • 描述性名称 — 反映所要获取的信息,例如:GetCustomerByIdListOrdersByDateRange
  • 过滤与分页 — 可以携带参数用于特定查询(日期、状态、页码、排序)。
  • 投影 — 应返回只包含必要字段的 DTO 或视图模型,避免加载完整的领域对象。

查询验证器

查询验证器 负责验证输入参数,如过滤条件、分页值(page, pageSize)以及安全规则(用户权限、数据可见性)。它们同样使用 FluentValidation 实现。

查询中常见的验证规则

  • GreaterThanOrEqualToLessThanOrEqualTo 用于区间过滤(例如:日期)。
  • Length 用于文本过滤(例如:按名称或邮箱搜索)。
  • InclusiveBetween 用于分页参数(例如:每页最小/最大项数)。

查询处理器

查询处理器 负责访问仓储或数据库上下文,并返回优化后的投影数据,避免加载完整的领域实体。 在 Lino 中,推荐使用如 .Select() 的方法来直接将数据映射为所需的 DTO 格式,以确保效率和更好的性能。

查询结果

在 Lino 中,查询的结果始终使用以 QueryResult 为后缀的命名记录来表示。 这一约定适用于所有类型的响应 —— 无论是带分页或不带分页的项目列表,还是单个详细项。

使用 CLI 创建查询

与命令类似,Lino 提供以下命令:

lino query new

向导将提示您:

  • 服务 — 查询将被创建在哪个服务中。
  • 模块 — (如适用)定义领域模块。
  • 实体 — 与该查询关联的实体或聚合(可选)。
  • 查询名称 — 例如:GetOrderByIdListProductsByCategory

Lino 将自动生成以下文件:

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

查询生成结构示例

MyApp/
└── src/
    └── Services/
        └── MyService/
            └── Application/
                ├── MyApp.MyService.Application.csproj
                └── UseCases/
                    └── Orders/
                        ├── Commands/
                        |   └── ...
                        └── Queries/
                            └── GetOrderById/
                                ├── GetOrderByIdQuery.cs
                                ├── GetOrderByIdQueryValidator.cs
                                ├── GetOrderByIdQueryHandler.cs
                                └── GetOrderByIdQueryResult.cs

结论

了解了如何在 Lino 中定义和构建 Use Cases(Commands 和 Queries)后,您已经准备好创建健壮且可扩展的业务场景,明确区分写入和读取逻辑。 使用 CLI 加速开发,但请始终根据您的领域特定需求审查并调整实现。

接下来,探索如何处理持久化、领域事件和基础设施服务,以构建整个应用程序的架构。

发生了未处理的错误。 重新加载 🗙