定义应用程序的用例
在本节中,我们将探讨如何使用 Lino CLI 定义应用程序的用例。用例表示用户或外部系统与应用程序之间的特定交互,封装业务规则并促进领域驱动架构。我们将讨论通过 CQRS 模式分离读写操作,并使用 Result Pattern 标准化响应返回。
CQRS(命令查询职责分离)模式提出将修改应用程序状态的操作(Commands)与仅查询数据的操作(Queries)分离。该方法允许独立优化每种操作类型,从而提升系统的可扩展性、性能和维护性。
Result Pattern 用于标准化操作返回结果,封装有关成功或失败、错误信息及结果数据的信息。这有助于在应用程序的不同层中一致地处理响应。
我们将了解 Commands 和 Queries 文件的结构,数据验证,Handlers 中领域逻辑的应用,以及如何标准化返回结果。所有这些都通过 Lino CLI 自动生成这些组件来实现。
注意: 虽然不是强制性的,但 Lino 当前提供两种在应用层应用 CQRS 模式的选项:通过 Jimmy Bogard 的 MediatR
库或 Martin Othamar 的 Mediator
库使用 Mediator 模式。
用例概述
用例表示用户或外部系统与应用程序之间的完整交互,描述特定的业务场景。在 Lino 中,每个用例被划分为:
- 命令 (Command):表示修改系统状态的意图(创建、更新、删除等)。
- 查询 (Query):表示查询数据而不更改域状态的意图。
这种划分有助于代码的清晰性,便于测试,允许读写操作的独立扩展,并符合清洁架构原则和 领域驱动设计 (DDD) 的最佳实践。
命令(Commands)
命令 是一种不可变的消息,仅携带执行修改系统状态操作所需的数据(例如,CreateInvoice
,DeactivateUser
)。
它应仅包含表示执行操作所必需的关键信息的属性。
命令的特征
- 不可变性:以
record
或仅含get
的类实现,不包含公共 setter。 - 命令式命名:反映将执行的动作,例如
CreateOrder
,UpdateCustomerAddress
。 - 最小数据:仅包含执行操作所需的字段,不返回大量数据。
- 独立验证:每个命令拥有自己的验证规则,确保在到达处理器(Handler)前处于一致状态。
命令验证器(Command Validators)
命令验证器 确保命令是 格式正确 并满足业务需求,然后才发送到处理器。在 Lino 中,我们使用广泛应用于 .NET 项目的 FluentValidation 库来实现这些验证,提供流畅的 API 来定义规则。
常见验证规则
NotEmpty
,NotNull
用于必填字段。InclusiveBetween
用于数值范围(例如,货币金额,最小/最大数量)。MaximumLength
和MinimumLength
用于字符串长度。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/ └── ...
查询
一个 查询 表示获取数据的意图,而不改变领域的状态。查询被设计为读取高效,只返回所需的字段,在未请求的情况下不会加载整个实体。
查询的特点
- 不可变 — 与命令类似,查询在创建后不应被修改。
- 描述性名称 — 反映所要获取的信息,例如:
GetCustomerById
、ListOrdersByDateRange
。 - 过滤与分页 — 可以携带参数用于特定查询(日期、状态、页码、排序)。
- 投影 — 应返回只包含必要字段的 DTO 或视图模型,避免加载完整的领域对象。
查询验证器
查询验证器 负责验证输入参数,如过滤条件、分页值(page, pageSize)以及安全规则(用户权限、数据可见性)。它们同样使用 FluentValidation 实现。
查询中常见的验证规则
GreaterThanOrEqualTo
或LessThanOrEqualTo
用于区间过滤(例如:日期)。Length
用于文本过滤(例如:按名称或邮箱搜索)。InclusiveBetween
用于分页参数(例如:每页最小/最大项数)。
查询处理器
查询处理器 负责访问仓储或数据库上下文,并返回优化后的投影数据,避免加载完整的领域实体。
在 Lino 中,推荐使用如 .Select()
的方法来直接将数据映射为所需的 DTO 格式,以确保效率和更好的性能。
查询结果
在 Lino 中,查询的结果始终使用以 QueryResult
为后缀的命名记录来表示。
这一约定适用于所有类型的响应 —— 无论是带分页或不带分页的项目列表,还是单个详细项。
使用 CLI 创建查询
与命令类似,Lino 提供以下命令:
lino query new
向导将提示您:
- 服务 — 查询将被创建在哪个服务中。
- 模块 — (如适用)定义领域模块。
- 实体 — 与该查询关联的实体或聚合(可选)。
- 查询名称 — 例如:
GetOrderById
或ListProductsByCategory
。
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 加速开发,但请始终根据您的领域特定需求审查并调整实现。
接下来,探索如何处理持久化、领域事件和基础设施服务,以构建整个应用程序的架构。