在实际企业级业务迭代中,为既有模块持续交付新功能(Feature)是研发团队最频繁的日常任务。在传统分层架构中,不同水平的工程师往往会导致代码风格割裂:有人在 Controller 中直拼 SQL,有人在 Service 中手工启动数据库事务,还有人通过全局共享的 God Service 随意读写聚合根私有字段。
BitzOrcas.Modern 确立了工业级标准的 5 步 Feature 交付流水线:
- 领域不变量优先(Invariants First):状态变迁必须由聚合根的显式领域方法驱动,严禁外界裸写属性;
- 单文件垂直切片(One Use Case, One File):契约、Minimal API 元数据、参数校验规则与业务编排 Handler 高内聚闭环;
- 窄端口命令仓储(
ICommandRepository):写路径仅依赖最简仓储端口,杜绝在写逻辑中混入复杂 ORM 读查询; - 管道全自动托管:认证、RBAC 细粒度动作权限、三阶校验、事务与事件 Outbox 均由 10 级管道统一处理;
- 真实容器集成测试验收:基于 Testcontainers 启动真实数据库实弹验证,彻底拒绝 Mock 鱼目混珠。
本指南以法律科技核心场景——在案件管理模块(BitzOrcas.Modules.Legal)中新增**“案件审理阶段流转(AdvanceMatterStageCommand)”**为例,演示完整的交付全景。
新增 Feature 标准 5 步交付流水线
第一步:在领域聚合根中增加业务状态流转方法
聚合根是一致性边界与状态不变量的唯一守护者。严禁在 Handler 中直接对聚合根字段进行赋值,所有状态变更必须封装为聚合根的领域行为:
using System;using BitzOrcas.Domain.Entities;using BitzOrcas.Domain.Results;using BitzOrcas.Persistence.Metadata;
namespace BitzOrcas.Modules.Legal.Domain;
/// <summary>/// 案件基础状态枚举/// </summary>public enum MatterStatus{ Draft = 1, Active = 2, Closed = 3, Archived = 4}
/// <summary>/// 案件审理阶段定义枚举/// </summary>public enum MatterStage{ /// <summary> /// 立案受理与管辖权确认 /// </summary> IntakeAccepted = 1,
/// <summary> /// 证据交换与质证准备 /// </summary> EvidenceDiscovery = 2,
/// <summary> /// 法庭开庭审理 /// </summary> TrialHearing = 3,
/// <summary> /// 判决生效与强制执行 /// </summary> Enforcement = 4}
/// <summary>/// 案件审理阶段推进领域事件/// </summary>public sealed record MatterStageAdvancedDomainEvent( string MatterId, string MatterCode, string TenantId, int PreviousStage, int NewStage, string OperatorId, DateTime OccurredAtUtc) : IDomainEvent;
/// <summary>/// 案件阶段流转强类型错误字典/// </summary>public static class MatterStageErrors{ public static readonly Error InvalidStatusForAdvance = Error.Conflict( "Matter.InvalidStatusForAdvance", "当前案件未处于正式立案审理状态,禁止推进审理阶段。"); public static readonly Error StageRegressionForbidden = Error.Validation( "Matter.StageRegressionForbidden", "目标审理阶段必须高于当前所处阶段,不允许向后倒退。"); public static readonly Error StageRemarksRequired = Error.Validation( "Matter.StageRemarksRequired", "流转备注说明不能为空且长度不能超过 500 个字符。"); public static readonly Error IdRequired = Error.Validation( "Matter.IdRequired", "案件标识不能为空。"); public static readonly Error InvalidTargetStage = Error.Validation( "Matter.InvalidTargetStage", "目标审理阶段取值超出合法枚举范围。"); public static readonly Error RemarksInvalid = Error.Validation( "Matter.RemarksInvalid", "阶段流转备注不能为空且长度不能超过 500 字。");}
public sealed partial class MatterIntake : TenantAggregateRoot<string>{ /// <summary> /// 案件当前业务状态 /// </summary> [BitzColumn(Description = "案件业务状态")] public MatterStatus Status { get; private set; } = MatterStatus.Active;
/// <summary> /// 当前所处审理阶段 /// </summary> [BitzColumn(Description = "当前审理阶段")] public MatterStage CurrentStage { get; private set; } = MatterStage.IntakeAccepted;
/// <summary> /// 推进案件审理阶段领域行为 /// </summary> /// <param name="targetStage">目标阶段枚举。</param> /// <param name="stageRemarks">流转备注说明。</param> /// <param name="operatorId">经办人员标识。</param> /// <returns>操作结果。</returns> public Result AdvanceStage(MatterStage targetStage, string stageRemarks, string operatorId) { // 1. 校验案件基础状态不变量:只有已立案 (Status == MatterStatus.Active) 的案件允许流转 if (Status != MatterStatus.Active) { return Result.Failure(MatterStageErrors.InvalidStatusForAdvance); }
// 2. 校验阶段不可逆流转 (不允许向后倒退阶段) if ((int)targetStage <= (int)CurrentStage) { return Result.Failure(MatterStageErrors.StageRegressionForbidden); }
// 3. 校验阶段备注长度业务约束 if (string.IsNullOrWhiteSpace(stageRemarks) || stageRemarks.Length > 500) { return Result.Failure(MatterStageErrors.StageRemarksRequired); }
var previousStage = (int)CurrentStage;
// 4. 执行状态变更 CurrentStage = targetStage;
// 5. 登记领域事件 (在事务提交后由框架自动派发) Raise(new MatterStageAdvancedDomainEvent( MatterId: Id, MatterCode: MatterCode, TenantId: TenantId, PreviousStage: previousStage, NewStage: (int)targetStage, OperatorId: operatorId, OccurredAtUtc: DateTime.UtcNow));
return Result.Success(); }}第二步:编写自包含单文件垂直切片(One Use Case, One File)
在 src/Modules/Legal/BitzOrcas.Modules.Legal/Application/Matters/ 目录下创建 AdvanceMatterStageCommand.cs。
该文件包含完整的命令契约、Minimal API 路由绑定、纯函数校验规则与业务处理器:
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>/// 编译期由 Roslyn 增量生成器自动绑定为 Minimal API 端点,并挂载细粒度动作权限。/// </remarks>[GenerateEndpoint( Route = "api/legal/matters/{matterId}/advance-stage", Method = EndpointMethod.Post, RequirePermission = "legal.matters.advance-stage", // 细粒度 RBAC 权限 Summary = "推进案件审理阶段", Description = "将民商事案件推进至证据交换、开庭辩论或强制执行阶段并记录审计流水")]public sealed record AdvanceMatterStageCommand( string MatterId, MatterStage TargetStage, string StageRemarks) : ICommand<Result>;
/// <summary>/// 案件阶段流转纯函数一阶校验规则/// </summary>/// <remarks>/// 在进入 Handler 之前由 10 级管道第 5 级(ValidationBehavior)自动执行,阻断不合法入参。/// </remarks>public sealed class AdvanceMatterStageCommandRule : IRequestRule<AdvanceMatterStageCommand>{ /// <summary> /// 异步校验参数合法性 /// </summary> public ValueTask<Result> ValidateAsync(AdvanceMatterStageCommand command, CancellationToken cancellationToken) { // 1. 案件主键非空校验 if (string.IsNullOrWhiteSpace(command.MatterId)) { return ValueTask.FromResult(Result.Failure(MatterStageErrors.IdRequired)); }
// 2. 目标阶段枚举有效性校验 if (!Enum.IsDefined(typeof(MatterStage), command.TargetStage)) { return ValueTask.FromResult(Result.Failure(MatterStageErrors.InvalidTargetStage)); }
// 3. 阶段流转备注非空与长度限制 if (string.IsNullOrWhiteSpace(command.StageRemarks) || command.StageRemarks.Length > 500) { return ValueTask.FromResult(Result.Failure(MatterStageErrors.RemarksInvalid)); }
return ValueTask.FromResult(Result.Success()); }}
/// <summary>/// 案件阶段流转命令执行处理器/// </summary>/// <remarks>/// 纯粹的领域编排,事务、事件分发与变更审计均由框架 10 级管道自动治理。/// 依赖规范:写侧仅依赖 <see cref="ICommandRepository{TAggregateRoot, TId}"/> 窄端口。/// </remarks>public sealed class AdvanceMatterStageCommandHandler : ICommandHandler<AdvanceMatterStageCommand, Result>{ private readonly ICommandRepository<MatterIntake, string> _repository; private readonly ICurrentUser _currentUser;
/// <summary> /// 初始化命令处理器 /// </summary> public AdvanceMatterStageCommandHandler( ICommandRepository<MatterIntake, string> repository, ICurrentUser currentUser) { _repository = repository; _currentUser = currentUser; }
/// <summary> /// 执行业务编排 /// </summary> public async Task<Result> Handle(AdvanceMatterStageCommand 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. 调用聚合根领域行为推进阶段 var advanceResult = matter.AdvanceStage( targetStage: request.TargetStage, stageRemarks: request.StageRemarks, operatorId: _currentUser.Id);
if (advanceResult.IsFailure) { return advanceResult; }
// 3. 提交持久化更新 (由管道外层 UnitOfWorkBehavior 自动完成事务提交与审计快照) return await _repository.SaveAsync(matter, cancellationToken); }}第三步:编写 Testcontainers 容器化实弹集成测试
在 tests/BitzOrcas.Integration.Tests/Legal/ 目录下编写真实容器集成测试。
测试依托官方 SHA-256 锁定的 SQL Server 2022 镜像与 BitzOrcasWebApplicationFactory,杜绝任何本地环境差异:
using System.Net;using System.Net.Http.Json;using System.Threading.Tasks;using BitzOrcas.Domain.Results;using BitzOrcas.Integration.Tests.Fixtures;using BitzOrcas.Modules.Legal.Application.Matters;using BitzOrcas.Modules.Legal.Domain;using FluentAssertions;using Xunit;
namespace BitzOrcas.Integration.Tests.Legal;
/// <summary>/// 案件审理阶段流转全链路容器化集成测试/// </summary>public sealed class AdvanceMatterStageTests : IClassFixture<BitzOrcasWebApplicationFactory>{ private readonly BitzOrcasWebApplicationFactory _factory;
public AdvanceMatterStageTests(BitzOrcasWebApplicationFactory factory) { _factory = factory; }
[Fact] public async Task AdvanceStage_WithValidTargetStage_ShouldSucceedAndPersist() { // 1. 创建具备测试租户凭证的 HttpClient using var client = _factory.CreateClient(); client.DefaultRequestHeaders.Add("X-Tenant-Id", "tenant-alpha-lawfirm");
// 2. 准备阶段流转命令 var command = new AdvanceMatterStageCommand( MatterId: "MAT-2026-0001", TargetStage: MatterStage.TrialHearing, StageRemarks: "已完成第一次庭前证据交换与质证,法庭决定正式进入一审开庭审理。");
// 3. 发起 HTTP POST 请求 var response = await client.PostAsJsonAsync( "api/legal/matters/MAT-2026-0001/advance-stage", command);
// 4. 断言 HTTP 状态码为 200 OK response.StatusCode.Should().Be(HttpStatusCode.OK);
// 5. 校验统一业务结果对象 var result = await response.Content.ReadFromJsonAsync<Result>(); result.Should().NotBeNull(); result!.IsSuccess.Should().BeTrue(); }
[Fact] public async Task AdvanceStage_WithStageRegression_ShouldReturnValidationError() { using var client = _factory.CreateClient(); client.DefaultRequestHeaders.Add("X-Tenant-Id", "tenant-alpha-lawfirm");
// 试图向后倒退审理阶段 (目标阶段等于或低于当前阶段) var regressionCommand = new AdvanceMatterStageCommand( MatterId: "MAT-2026-0001", TargetStage: MatterStage.IntakeAccepted, StageRemarks: "试图倒退回立案阶段。");
var response = await client.PostAsJsonAsync( "api/legal/matters/MAT-2026-0001/advance-stage", regressionCommand);
// 断言业务冲突校验拦截 (400 校验错误) response.StatusCode.Should().Be(HttpStatusCode.BadRequest); var result = await response.Content.ReadFromJsonAsync<Result>(); result.Should().NotBeNull(); result!.IsFailure.Should().BeTrue(); result.Error.Code.Should().Be("Matter.StageRegressionForbidden"); }}第四步:静态编译与架构守卫断言
在代码落地后,主动在终端执行编译与测试命令,确保零编译错误与警告:
# 1. 编译全解决方案 (启用严格空安全与 Roslyn 生成器)dotnet build BitzOrcas.Modern.slnx -c Release
# 2. 运行模块边界架构守卫测试dotnet test tests/BitzOrcas.Architecture.Tests/ --filter FullyQualifiedName~ModuleBoundaryTests
# 3. 运行实弹集成测试 (拉起 Testcontainers)dotnet test tests/BitzOrcas.Integration.Tests/ --filter FullyQualifiedName~AdvanceMatterStageTests总结
通过标准 5 步交付流水线,为现有模块新增 Feature 具备以下确定性:
- 开发零冲突:业务用例自包含于单一文件,无需修改任何共享的 God Service;
- 管线全自动:RBAC 鉴权、参数校验、租户隔离与事务管理由 10 级管道统一管控;
- 写侧窄端口:严格使用
ICommandRepository<T, TId>,杜绝 ORM 泄漏与写放大; - 可信交付:基于真实容器集成测试,杜绝运行时故障。