定义应用程序用例
用例是将业务意图转换为可执行软件的应用程序层入口点。在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, IQueryHandler 和 Tolitech.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, ConfirmOrder 和 SavePermissionsByRoleId。 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 用于创建规则。
通用验证规则
NotEmpty和NotNull对于必填字段。InclusiveBetween用于数字范围、货币值、百分比、最小和最大数量。MaximumLength,MinimumLength和Length对于字符串大小。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.csCreateOrderCommandValidator.csCreateOrderCommandHandler.csCreateOrderCommandResult.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__:需要返回数据的成功操作的最小响应契约。
实施清单
- 跑步
lino command new并选择正确的服务、模块、实体、类型和属性。 - 打开生成的 Command 并删除不属于操作合同的任何字段。
- 用无法自动推断的业务录入规则来强化validator。
- 检查 handler 并确保它调用域行为而不是在应用程序层复制不变量。
- 使用
IUnitOfWork当操作还需要事件或可靠的 Outbox 时,用于持久性并首选事务保存。 - 由于错误而返回失败
Result,而不是预期业务结果的例外情况。 - 构建并测试将调用 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 中的常见验证规则
GreaterThanOrEqualTo和LessThanOrEqualTo用于范围过滤器,例如开始日期和结束日期。Length,MaximumLength和MinimumLength用于文本过滤器,例如姓名、电子邮件或搜索词。InclusiveBetween用于寻呼、限制page和pageSize.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.csGetOrderByIdQueryValidator.csGetOrderByIdQueryHandler.csGetOrderByIdQueryResult.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。
实施清单
- 跑步
lino query new并选择服务、模块、实体、Query 名称、返回类型、分页模式和属性。 - 检查请求合同并仅保留 caller 真正需要的过滤器。
- 加强 validator,特别是对于所需的标识符、日期范围、页面大小限制和无效的过滤器组合。
- 调整 handler 投影以仅返回 UI、API、集成或后台进程所需的字段。
- 保留 Query read-only:不要调用改变状态的域方法,并且不要将更改保存到 handler。
- 传播预期的故障
Result,在适当的情况下使用现有的标准化误差。 - 构建并测试调用 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 或页面公开用例,并通过构建和测试验证所有内容。这可以在不失去架构控制的情况下保持快速开发。
随着项目的发展,让每个用例专注于业务意图,尊重服务和模块边界,并在另一个模块需要参与流程时使用事件、集成或影子实体。
