在传统的架构中,增加一个简单的业务写操作(如“民商事案件结案归档”)需要修改 Controller、Service、DTO、Mapper、Entity 多个分散文件。代码散落各处不仅导致极高的认知负担,更使得横切关注点(如权限校验、幂等防重、事务提交、审计记录)在各个服务中被随意复制或遗漏。
在 BitzOrcas.Modern 中,我们坚决贯彻 “一用例一文件(One Use Case, One File)” 原则:将 Minimal API 路由生成契约、参数校验规则、RBAC 鉴权声明与命令处理器 Handler 组织在同一个自包含的 .cs 文件中,并由 10 级管道统一守卫。
本篇指南将以**律所案件结案归档(CloseMatterCommand)**为标准金样板,带你掌握编写写操作切片的完整范式。
写操作切片单文件结构与执行流转
完整单文件写操作切片金样板
在 src/Modules/Legal/BitzOrcas.Modules.Legal/Application/Matters/ 目录下创建 CloseMatterCommand.cs:
using System;using System.Threading;using System.Threading.Tasks;using BitzOrcas.Application.Abstractions;using BitzOrcas.Application.Attributes;using BitzOrcas.Application.Security;using BitzOrcas.Application.Validation;using BitzOrcas.Domain.Abstractions;using BitzOrcas.Domain.Results;using BitzOrcas.Domain.Tenancy;using BitzOrcas.Modules.Legal.Domain;
namespace BitzOrcas.Modules.Legal.Application.Matters;
/// <summary>/// 案件结案归档命令契约/// </summary>/// <remarks>/// <para>承载案件业务主键、结案意见说明与卷宗物理归档柜号。</para>/// <para>由 Roslyn 增量生成器在编译期自动生成 Minimal API 端点并挂载细粒度 RBAC 动作权限。</para>/// </remarks>[GenerateEndpoint( Route = "api/legal/matters/{matterId}/close", Method = EndpointMethod.Post, RequirePermission = "legal.matters.close", // 声明式 RBAC 动作权限码 Summary = "案件结案与卷宗归档", Description = "将处于在审状态的民商事案件正式结案并归档卷宗物理柜位")]public sealed record CloseMatterCommand( string MatterId, string ClosingRemarks, string ArchiveBoxNumber) : ICommand<Result>;
/// <summary>/// 案件业务强类型错误字典/// </summary>public static class MatterErrors{ public static readonly Error IdRequired = Error.Validation("Matter.IdRequired", "案件标识不能为空。"); public static readonly Error ClosingRemarksInvalid = Error.Validation("Matter.ClosingRemarksInvalid", "结案意见不能为空且不能超过 1000 个字符。"); public static readonly Error ArchiveBoxRequired = Error.Validation("Matter.ArchiveBoxRequired", "卷宗归档柜位号不能为空且不能超过 64 个字符。");}
/// <summary>/// 案件结案前置一阶纯函数校验规则/// </summary>/// <remarks>/// <para>实现 <see cref="IRequestRule{TRequest}"/> 接口,属于 10 级管道第 5 级(ValidationBehavior)。</para>/// <para>在进入业务 Handler 之前由框架自动拦截并校验入参合法性,保障领域层不受脏数据污染。</para>/// </remarks>public sealed class CloseMatterCommandRule : IRequestRule<CloseMatterCommand>{ /// <summary> /// 异步执行纯函数参数校验 /// </summary> /// <param name="command">结案命令入参。</param> /// <param name="cancellationToken">取消令牌。</param> /// <returns>校验通过或失败结果。</returns> public ValueTask<Result> ValidateAsync(CloseMatterCommand command, CancellationToken cancellationToken) { // 1. 案件唯一标识非空 if (string.IsNullOrWhiteSpace(command.MatterId)) { return ValueTask.FromResult(Result.Failure(MatterErrors.IdRequired)); }
// 2. 结案意见非空与长度限制 if (string.IsNullOrWhiteSpace(command.ClosingRemarks) || command.ClosingRemarks.Length > 1000) { return ValueTask.FromResult(Result.Failure(MatterErrors.ClosingRemarksInvalid)); }
// 3. 归档卷宗编号格式校验 if (string.IsNullOrWhiteSpace(command.ArchiveBoxNumber) || command.ArchiveBoxNumber.Length > 64) { return ValueTask.FromResult(Result.Failure(MatterErrors.ArchiveBoxRequired)); }
return ValueTask.FromResult(Result.Success()); }}
/// <summary>/// 案件结案用例执行处理器/// </summary>/// <remarks>/// <para>聚焦纯粹的领域编排,事务提交、CAP 领域事件分发与变更审计均由 10 级管道全自动治理。</para>/// <para>依赖规范:写侧仅依赖 <see cref="ICommandRepository{TAggregateRoot, TId}"/> 窄端口。</para>/// </remarks>public sealed class CloseMatterCommandHandler : ICommandHandler<CloseMatterCommand, Result>{ private readonly ICommandRepository<MatterIntake, string> _repository; private readonly ICurrentUser _currentUser;
/// <summary> /// 初始化案件结案处理器 /// </summary> /// <param name="repository">命令仓储窄端口。</param> /// <param name="currentUser">当前登录用户安全上下文。</param> public CloseMatterCommandHandler( ICommandRepository<MatterIntake, string> repository, ICurrentUser currentUser) { _repository = repository; _currentUser = currentUser; }
/// <summary> /// 执行案件结案领域编排 /// </summary> /// <param name="request">结案命令契约。</param> /// <param name="cancellationToken">取消令牌。</param> /// <returns>操作执行结果。</returns> public async Task<Result> Handle(CloseMatterCommand request, CancellationToken cancellationToken) { // 1. 从命令仓储恢复聚合根 (管道与仓储自动叠加当前租户隔离边界) var matterResult = await _repository.FindAsync(request.MatterId, cancellationToken); if (matterResult.IsFailure) { return Result.Failure(matterResult.Error); }
var matter = matterResult.GetValueOrThrow();
// 2. 调用聚合根领域行为流转状态 (内部守护在审状态、封锁不可变字段并登记 CloseDomainEvent) var closeResult = matter.CloseMatter( closingRemarks: request.ClosingRemarks, archiveBoxNumber: request.ArchiveBoxNumber, operatorId: _currentUser.Id);
if (closeResult.IsFailure) { return closeResult; }
// 3. 提交聚合更新 (事务提交、集成事件分发与审计快照由管道自动接管) return await _repository.SaveAsync(matter, cancellationToken); }}编写写操作切片的 4 项核心铁律
- 一用例一文件(One Use Case, One File):Command 契约、
[GenerateEndpoint]、IRequestRule校验与 Handler 必须声明在同一个自包含文件中,严禁为了拆解而拆解; - 严禁在 Handler 中编写横切关注点:严禁手动开启/提交数据库事务、手动调用
_dbContext.SaveChangesAsync()或手工记录审计日志,所有横切逻辑由 10 级管道统一处理; - 聚合根全权负责状态不变量:所有状态流转(如
matter.CloseMatter(remarks, boxNumber, operatorId))必须封装在聚合根内部方法中,严禁在 Handler 中直接对聚合根属性执行setter赋值; - 写侧仅依赖
ICommandRepository窄端口:命令切片禁止直接注入泛型 ORM 上下文执行复杂宽表查询,读逻辑与报表导出必须分流至只读模型 Store。