定义应用程序用例

用例是将业务意图转换为可执行软件的应用程序层入口点。在Lino,他们在 Application/UseCases 并围绕项目前面阶段定义的实体、aggregates、枚举、模块和服务进行组织。


Lino 遵循面向结构的 __学期0__: __学期0__ 表示改变状态的操作,而 __学期0__ 代表读取数据的操作。这种分离使编写规则、读取模型、验证、跟踪、记录和响应契约变得明确且更易于维护。


__学期0__ 标准化操作的返回,封装成功、失败、错误消息和结果数据。 handler 返回一个 Result 或者 Result<T> 根据需要使用值、错误或 no-content。


CLI 生成用例的初始文件,但生成的代码不会取代域分析。开发人员仍然必须检查 handler,确认所选属性,添加业务规则,调整验证并验证用例是否遵守模块和服务的限制。

重要的: 可以在支持 CQRS 和 mediator 的情况下创建 Lino 项目。 Commands 和 Queries 生成使用应用程序抽象,例如 ICommand, IQuery, ICommandHandler, IQueryHandlerTolitech.Results 标准化请求的处理和操作结果。

用例概述

使用案例 代表一个完整的应用程序操作:接收输入模型,验证数据,协调依赖关系,在必要时调用域行为,保留或查询数据,并返回标准化结果。这是应用程序编排业务流的地方,而无需将不变量移至域模型之外。

在 Lino 生成的解决方案中,用例使用可预测的结构分组在应用程序项目中:

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

这个组织明确了每个功能属于哪个服务、模块和实体。它还保持写入模型和读取模型独立,这在屏幕、API、集成或后台进程仅需要行为的一侧时非常有用。

__学期0__ 还是__学期1__?

类型 意图 示例 常见 Resultado
__学期0__ 更改系统状态。 CreateOrder, UpdateVehicle, DeleteMaintenance, SavePermissionsByRoleId. Result, Result<CommandResult> 或 no-content 成功。
__学期0__ 读取数据而不改变状态。 GetOrderById, ListCustomers, GetVehicleAvailability. Result<QueryResult>、列表、页面或消费者特定的 DTO。

什么属于应用程序用例

  • 入职合同:command 或 query 的 record 仅包含操作所需的数据。
  • 验证:在 handler 执行流程之前检查请求格式、必填字段、范围、标识符、过滤器和其他输入限制的规则。
  • handler 编排:调用依赖项、访问存储库、上下文查询、使用 Unit of Work、结果组合、日志记录和跟踪。
  • 结果契约:一个小的响应对象或无内容的结果,为 caller 提供继续所需的内容。

用例中应该排除什么

  • 域不变量 必须保留在实体、Value Objects、域服务和域方法中。
  • 基础设施详情 它们必须保留在抽象、存储库、数据库上下文、文件服务、消息传递集成和外部客户端后面。
  • 演示问题,例如 UI 状态、标签、布局和组件行为,必须保留在 Web 应用层。

一个好的用例是明确的,小到足以被理解,并且边界严格:它协调工作,但不会成为所有系统规则混合在一起的地方。

__学期0__

__学期0__ 是一条不可变的消息,仅携带执行修改系统状态的操作所需的数据。常见的例子有 CreateCustomer, UpdateVehicle, DeleteMaintenance, ConfirmOrderSavePermissionsByRoleId。 Commands 应该命名为操作,因为它们告诉应用程序执行某些操作。

在 Lino 中,生成的 Commands 是 record,它们实现了 command 的抽象并返回标准化结果。创建 Commands 通常返回一个结果对象,其中包含 caller 所需的标识符或最少信息,而更新和删除 commands 通常在操作成功完成时返回 no-content。

Command 的特征

  • 不变性:实施为 record 或类仅 get,没有可变的公共设置器。
  • 祈使式名称:反映业务行为,例如 CreateOrder, UpdateCustomerAddress 或者 ChangeProductPrice.
  • 最小数据:仅包含执行操作所需的字段,而不传输整个实体或大量数据。
  • 隔离验证:每个Command都有自己的规则,以确保请求在到达handler之前是一致的。
  • 独立于 UI: Command 代表应用程序的意图,而不是触发操作的按钮、表单或组件。

何时创建 Command

  • 当以任何方式创建、更改、删​​除、加入、导入、批准、取消或更改数据时,请使用 Command。
  • 让payload专注于操作;当几个字段就足够时,不要传递整个实体。
  • 选择描述业务操作的名称,而不是启动它的视觉事件。
  • 检查该操作是否属于当前模块,或者是否应该通过另一个模块中的集成、事件或影子实体来表达。

__学期0__

__学期0__ 在将 Command 发送到 handler 之前,请确保 Command 格式正确且满足输入要求。在 Lino 中,这些验证是通过以下方式实现的 __学期0__, .NET 项目中的通用库,因为它提供了流畅且可读的 API 用于创建规则。

通用验证规则

  • NotEmptyNotNull 对于必填字段。
  • InclusiveBetween 用于数字范围、货币值、百分比、最小和最大数量。
  • MaximumLength, MinimumLengthLength 对于字符串大小。
  • RuleForEach 验证集合项目,例如 List<T>.
  • Must 用于自定义规则,例如文档格式、日期一致性和无效字段组合。

validator 保护前沿。实际的域不变量(例如状态边界、允许的转换以及无论 caller 为何都必须保持的规则)继续属于 aggregate、实体、Value Object 或域服务。

__学期0__

__学期0__ 执行与 Command 关联的应用程序逻辑。它协调存储库、上下文、 IUnitOfWork、辅助服务、日志记录、跟踪、集成调用和必要时的域事件。

handler 的实现模式

  • 通过依赖注入接收依赖项,例如存储库、外部服务、上下文和 Unit of Work。
  • 验证操作所需数据的存在和可用性。
  • 当操作依赖于现有状态时,加载正确的 aggregate 或实体。
  • 调用域方法而不是重复 handler 中的不变量。
  • 坚持改变通过 IUnitOfWork 或项目持久性抽象。
  • 当操作需要与系统的其他部分进行通信时,记录域、Outbox 或集成事件。
  • 返回 Result 或者 Result<T> 成功、已知失败或最小响应数据。

Command Results 和 Result Pattern

__学期0__ 应该是一个简单的 DTO ,其中仅包含 caller 继续所需的数据。在创作中,通常包括 Id 生成的。更新或删除时,可能没有 payload。当操作由于预期条件而失败时,返回必须携带标准化的错误信息。

__学期0__ 将操作的结果封装为成功或失败。 handler 返回如下类型,而不是针对可预测的场景(例如未找到实体、无效状态或业务验证)抛出异常 Result<T> 必要时包含值、错误和元数据。

  • 如果成功,结果可能会暴露 Value,例如一个小的 DTO 或精心设计的标识符。
  • 失败时,结果会存储标准化代码或消息,通常在可重用错误定义中定义。
  • Application、API、typed clients 和 UI 之间的错误处理流程是一致的。

使用 CLI 创建 Command

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

lino command new

CLI 还支持减少向导中问题的选项:

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 或者 --service: 目标服务。
  • -m 或者 --module:模块化服务中的目标模块。
  • -e 或者 --entity:与 Command 关联的实体。
  • -n, --name, -c 或者 --command: Command 的名称。

在交互流程中,Lino 确认服务、模块、实体、Command 名称、Command 类型,以及对于创建或更新操作而言,将成为请求一部分的属性。 CLI 公开的类型是 创造, 更新删除.

确认后,Lino 创建如下文件:

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

生成的结构示例

考虑 Command CreatePerson。生成的结构将如下所示:

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

对于自定义的队列 Command,结构遵循相同的模式:

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

Command 文件的职责

  • __学期0__:与操作的入口不可变的请求契约。
  • __学期0__:输入验证,通常具有强制性规则、大小、范围、标识符和集合。
  • __学期0__:存储库、Unit of Work、上下文、域方法、日志记录、跟踪和结果创建的编排。
  • __学期0__:需要返回数据的成功操作的最小响应契约。

实施清单

  1. 跑步 lino command new 并选择正确的服务、模块、实体、类型和属性。
  2. 打开生成的 Command 并删除不属于操作合同的任何字段。
  3. 用无法自动推断的业务录入规则来强化validator。
  4. 检查 handler 并确保它调用域行为而不是在应用程序层复制不变量。
  5. 使用 IUnitOfWork 当操作还需要事件或可靠的 Outbox 时,用于持久性并首选事务保存。
  6. 由于错误而返回失败 Result,而不是预期业务结果的例外情况。
  7. 构建并测试将调用 Command 的端点、页面或集成。

经验法则: 生成器提供建筑骨架。最终的修复来自于根据领域语言、不变量、持久性需求和模块边界检查用例。

__学期0__

__学期0__ 表示在不改变域状态的情况下获取数据的意图。 Queries 旨在高效读取,准确返回 caller 所需的字段,而无需在不需要时加载整个实体。

常见的例子有 GetCustomerById, ListCustomers, ListPhoneTypes, ListOrdersByDateRange 和一个自定义的 query 类似 GetVehicleAvailability。 Query 必须回答特定于应用程序的问题。

Query 的特征

  • 不可变的:就像 Commands 一样,Query 一旦创建就不得允许更改。
  • 描述性名称:反映所寻求的信息,例如 GetCustomerById 或者 ListOrdersByDateRange.
  • 过滤器和分页:可以加载日期、状态、页面、页面大小、搜索文本、排序等阅读参数。
  • 投影:必须返回 DTO 或仅包含必要字段的视图模型,避免直接暴露域实体。
  • 无副作用:您不得调用更改状态或将更改保存到数据库的方法。

何时创建 Query

  • 当操作读取数据且不应修改状态时,请使用 Query。
  • 使用单一结果 Query 进行详细信息屏幕、标识符搜索、可用性检查或对象响应。
  • 对网格、选项列表、子集合和选择控件使用列表 Query。
  • 对网格和潜在的大型数据集使用分页。
  • 对小型参考数据、枚举和选项端点使用简单列表。

__学期0__

__学期0__ 验证输入参数,例如过滤器、分页值、日期范围、标识符和可见性规则。它们还实现了 __学期0__.

Queries 中的常见验证规则

  • GreaterThanOrEqualToLessThanOrEqualTo 用于范围过滤器,例如开始日期和结束日期。
  • Length, MaximumLengthMinimumLength 用于文本过滤器,例如姓名、电子邮件或搜索词。
  • InclusiveBetween 用于寻呼、限制 pagepageSize.
  • NotEmpty 对于详细查询中的强制标识符。
  • Must 对于无效的过滤器组合,例如结束日期小于开始日期。

__学期0__

__学期0__ 查询存储库或数据库上下文并返回优化的投影。在 Lino 中,建议使用投影 Select、显式过滤器、可预测的排序和适当时的未跟踪读数。

Query 的 handler 必须是 read-only:它不调用状态更改域方法,不触发持久更改,并且不执行 SaveChanges。如果读取需要记录审核、启动集成或重新计算状态,这通常表明另一个用例或单独的进程。

__学期0__

在 Lino 中,Queries 的结果由以后缀命名的 record 表示 QueryResult。此约定适用于单一响应、简单列表、分页列表或特定于屏幕的 DTO、API、集成或后台进程。

Result查询数据必须是读取稳定的合约。将它们视为为消费者设计的 DTOs ,而不是直接公开域实体的快捷方式。

使用 CLI 创建 Query

与 Commands 类似,Lino 提供命令:

lino query new

CLI 还接受以下选项:

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 或者 --service: 目标服务。
  • -m 或者 --module:目标模块。
  • -e 或者 --entity:与 Query 关联的实体。
  • -n, --name, -q 或者 --query: Query 的名称。

在交互流程中,Lino 询问 Query 是否返回单个结果或列表。当响应是列表时,它还会询问是否应该分页。最后,它允许您选择生成的投影应返回或使用的属性。

Lino 将自动生成:

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

Queries 生成的结构示例

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

对于定制车辆可用性 Query,结构遵循相同的模式:

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

Query 文件的职责

  • __学期0__:具有标识符、过滤器、排序、分页或日期范围的不可变请求契约。
  • __学期0__:验证标识符、分页、强制过滤器、日期范围和搜索条件。
  • __学期0__:使用应用程序上下文、投影、过滤器、排序、分页、日志记录、跟踪和结果创建来读取编排。
  • __学期0__:响应 DTO 模仿 caller,通常带有用于列表项的内部 record。

实施清单

  1. 跑步 lino query new 并选择服务、模块、实体、Query 名称、返回类型、分页模式和属性。
  2. 检查请求合同并仅保留 caller 真正需要的过滤器。
  3. 加强 validator,特别是对于所需的标识符、日期范围、页面大小限制和无效的过滤器组合。
  4. 调整 handler 投影以仅返回 UI、API、集成或后台进程所需的字段。
  5. 保留 Query read-only:不要调用改变状态的域方法,并且不要将更改保存到 handler。
  6. 传播预期的故障 Result,在适当的情况下使用现有的标准化误差。
  7. 构建并测试调用 Query 的使用者,例如 API 端点、Blazor 页面、进程内集成或 typed client。

自定义 Query 示例

在 SaaS 模块之间的集成流程中,可以使用以下命令创建自定义车辆可用性 Query lino query new。生成的结构提供 Query、Handler、Result 和 Validator。然后,开发人员添加必要的输入,例如车辆标识符、开始日期和结束日期,验证范围是否正确并实现 handler 逻辑。这是预期的流程:首先生成一致的结构,然后使用显式代码完成特定于域的行为。

阅读模型指南: Query 的结果必须是稳定合约。将它们视为专为 caller 设计的 DTOs,而不是直接公开域实体的快捷方式。

结论

在 Lino 中定义用例正在将领域模型转换为清晰的应用程序操作。 Commands 处理状态更改,Queries 处理读取,validators 保护输入边缘,handlers 编排流程,结果对象标准化输出。

最快的流程往往是:对域进行建模,生成必要的 Commands 和 Queries,检查生成的文件,完成业务行为,在必要时通过 API 或页面公开用例,并通过构建和测试验证所有内容。这可以在不失去架构控制的情况下保持快速开发。

随着项目的发展,让每个用例专注于业务意图,尊重服务和模块边界,并在另一个模块需要参与流程时使用事件、集成或影子实体。

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