尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Clean Architecture 仓库深度解析:Minimal Clean Architecture 单项目垂直切片模板的设计决策与源码实现

Clean Architecture 仓库深度解析:Minimal Clean Architecture 单项目垂直切片模板的设计决策与源码实现 Clean Architecture 仓库深度解析Minimal Clean Architecture 单项目垂直切片模板的设计决策与源码实现【免费下载链接】CleanArchitectureClean Architecture Solution Template: A proven Clean Architecture Template for ASP.NET Core 10项目地址: https://gitcode.com/GitHub_Trending/cl/CleanArchitecture本篇技术文章基于 Clean Architecture 仓库中的 Minimal Clean Architecture 文档系统讲解这一“单项目 垂直切片Vertical Slice Architecture”的 ASP.NET Core 10 模板你将掌握它的四项设计哲学、五个架构决策记录ADR、垂直切片的目录组织方式以及它在 MinimalClean 解决方案中真实的源码级落地——从 FastEndpoints 端点到 Mediator 处理器、从聚合根封装到 EF Core 配置——并学会按迁移路径在“极简版”与“完整版”模板之间平滑演进。一、模板定位为小应用保留 Clean Architecture 的全部原则Minimal Clean Architecture模板提供一种简化但务实的 Clean Architecture 实践路线它在保留 Clean Architecture 核心原则关注点分离、依赖倒置、可测试性的前提下通过**单一项目的垂直切片架构VSA**显著降低复杂度。与仓库根目录下 src/Clean.Architecture.Core、src/Clean.Architecture.Infrastructure、src/Clean.Architecture.UseCases、src/Clean.Architecture.Web 这样的四项目完整模板不同Minimal 版把全部代码收敛到一个 Web 工程中用文件夹结构承担原本由项目引用强制执行的边界。核心设计哲学文档明确列出四条核心原则Simplicity First简单优先最小化不必要的抽象与项目边界Vertical Slices垂直切片按业务功能Feature而非技术层Layer组织代码Pragmatic DDD务实的领域驱动设计在能带来价值的地方使用领域模式而不是到处堆砌Progressive Enhancement渐进增强从简单开始只在需要时才引入复杂度。同时模板声称并未放弃 Clean Architecture 的四项基本原则依赖倒置Domain 不依赖 Infrastructure可测试性业务逻辑可以脱离框架单独测试关注点分离领域、基础设施与表现层边界清晰框架无关性领域逻辑不与 ASP.NET Core 耦合。相对完整模板的五项简化简化点说明单项目全部代码位于一个 Web 项目而非 4 个项目简化 DDD只保留必要的模式实体、聚合根不强制大量值对象与 Specification可选 CQRSMediator 是可选的简单场景的逻辑可以直接写在端点里直接数据访问可以直接使用 DbContext 或简单仓储而非复杂的仓储模式垂直组织按功能Cart、Order、Product分组而不是按层分组这套取舍在源码中有直接对应Minimal 解决方案只有 MinimalClean/src/MinimalClean.Architecture.Web 一个业务工程外加 Aspire 编排宿主与 ServiceDefaults域、基础设施、端点全部以文件夹形式共存其中。二、项目结构垂直切片如何落地文档给出的目标结构原文档描述了如下目标结构示意骨架MinimalClean.Architecture.Web/ ├── Domain/ # Domain Layer │ ├── CartAggregate/ │ │ ├── Cart.cs # Aggregate root │ │ ├── CartItem.cs # Entity │ │ └── Events/ # Domain events (optional) │ ├── OrderAggregate/ │ └── ProductAggregate/ ├── Infrastructure/ # Infrastructure Layer │ ├── Data/ │ │ ├── AppDbContext.cs # EF Core DbContext │ │ ├── Config/ # EF configurations │ │ └── Migrations/ # EF migrations │ ├── Email/ # External services │ └── Services/ # Infrastructure services ├── Endpoints/ # Presentation Layer │ ├── Cart/ │ ├── Order/ │ └── Product/ └── Program.cs # Application startup实际仓库中的目录组织对照当前仓库的真实结构MinimalClean.Architecture.Web 工程的组织方式是MinimalClean/src/MinimalClean.Architecture.Web/ ├── Domain/ │ ├── CartAggregate/ # 聚合根 实体 Specifications/ │ ├── GuestUserAggregate/ │ ├── OrderAggregate/ │ ├── ProductAggregate/ │ └── Interfaces/ # 如 IEmailSender端口 ├── Infrastructure/ │ ├── Data/ # AppDbContext、Config/、Migrations/、Queries/ │ └── Email/ # FakeEmailSender / MimeKitEmailSender ├── CartFeatures/ # 购物车功能切片 │ ├── AddToCart/ # Endpoint Handler 同目录 │ ├── Checkout/ │ └── GetById/ ├── ProductFeatures/ # 商品功能切片 │ ├── Create/ │ ├── GetById/ │ └── List/ ├── Configurations/ # 选项、日志、Mediator、中间件等 DI 配置 └── Program.cs从源码结构看文档树中的Endpoints/在实际模板中演进为按功能命名的特性目录CartFeatures/、ProductFeatures/每个特性目录内部再按用例划分如AddToCart/、Checkout/、GetById/。这正是 ADR-002 的直观体现每个切片把该功能的 Domain、数据访问与端点就近放置理解或修改一个功能时几乎不需要跨目录跳转。依赖流向文档定义了模板内部的依赖方向Endpoints ──→ Domain ↓ Infrastructure ──→ Domain端点可以使用 Domain 和 InfrastructureInfrastructure 依赖 Domain用于实体配置与仓储实现Domain 不依赖任何其他层。这一点可以从源码验证Domain/ProductAggregate/Product.cs 的引用仅有Ardalis.GuardClauses与命名空间声明完全不涉及 EF Core、FastEndpoints 或 ASP.NET Core 类型而反向依赖则集中在 Infrastructure/Data/AppDbContext.cs引用四个聚合的实体并暴露DbSet以及 Infrastructure/InfrastructureServiceExtensions.cs注册仓储、DbContext、领域事件分发器。应用入口 Program.cs 的启动流程也印证了分层装配顺序var builder WebApplication.CreateBuilder(args); builder.AddServiceDefaults() // OpenTelemetry 日志/遥测 .AddLoggerConfigs(); // Serilog 控制台格式化 builder.Services.AddOptionConfigs(builder.Configuration, startupLogger, builder); builder.Services.AddServiceConfigs(startupLogger, builder); builder.Services.AddFastEndpoints() .SwaggerDocument(o { o.ShortSchemaNames true; }); var app builder.Build(); await app.UseAppMiddlewareAndSeedDatabase(); app.MapDefaultEndpoints(); // Aspire 健康检查与指标 app.Run();注意最后一行public partial class Program { }它把隐式Program类暴露为 public专门供功能测试通过WebApplicationFactoryProgram引用程序集构建宿主——这是文档“测试策略”一节能够成立的前提之一。三、五项架构决策ADR逐项拆解文档以 ADRArchitecture Decision Record形式记录了模板的五个关键决策状态均为Accepted。以下在继承原文档每条决策的 Context / Decision / Consequences 的基础上补充仓库中的实现证据。ADR-001单项目架构背景需要在架构指导与简单性之间为小型应用取得平衡。决策使用具有清晰文件夹结构的单一 Web 项目而非多个项目。后果✅ 更易于导航和理解✅ 构建更快无项目间引用✅ 重构更容易无项目边界顾虑✅ 初始复杂度更低⚠️ 开发人员必须自觉遵守文件夹边界编译器不强制执行⚠️ 严格的层间隔离更难保证。迁移路径后续如需拆分可提取为多项目结构见第五节迁移路径。对应证据MinimalClean/MinimalClean.Architecture.slnx 解决方案中业务代码只属于MinimalClean.Architecture.Web一个项目Infrastructure只是其中的命名空间而非独立工程。ADR-002垂直切片组织背景需要一种易于理解和修改的代码组织方式。决策按功能垂直切片组织代码而不是按层水平组织。后果✅ 相关代码就近放置易查找✅ 功能可独立修改✅ 天然适配未来微服务拆分✅ 与业务能力对齐⚠️ 功能之间可能存在少量代码重复⚠️ 共享关注点需要谨慎考虑。对应证据CartFeatures/AddToCart/目录同时存放端点、校验器、映射器与 Mediator 处理器见 AddToCartEndpoint.cs 与 AddToCartHandler.cs而同一特性的查询规格Specification则放在该聚合自己的Specifications/下如 CartByIdSpec.cs。ADR-003务实的 DDD背景对简单领域而言完整的 DDD 模式往往过重。决策使用必要的 DDD 模式实体、聚合根但保持简单。包含的模式✅ 带封装的实体✅ 聚合根✅ 领域事件可选。简化/可选的模式⚠️ 值对象有价值时才用⚠️ Specification先用 LINQ需要时再加⚠️ 领域服务仅在需要时添加。后果✅ 更易学习应用✅ 样板代码更少✅ 开发更快⚠️ 领域复杂度增长时可能需要补充模式。对应证据Cart.cs 是标准的聚合根写法——内部private readonly ListCartItem _items只以IReadOnlyListCartItem形式对外暴露变更必须经由AddItem(...)行为方法Product.cs 则展示了“私有无参构造留给 EF Core、工厂方法Product.Create(...)创建新实例、带参构造仅用于重建已持久化实体”的务实封装并用Guard.Against.InvalidInput阻止对ProductId.New的误用。值得注意的是Specification 在模板中并未被“禁用”而是按需出现在聚合目录下CartByIdSpec、ProductByIdSpec、GuestUserByEmailSpec等与 ADR-003 中“需要时再加”的表述一致。ADR-004可选的 Mediator / CQRS背景基于 Mediator 的 CQRS 带来有价值的模式也带来复杂度。决策让 Mediator 成为可选项简单场景允许业务逻辑直接写在端点里。代价是无法依赖自定义管道处理横切关注点。使用指南简单 CRUD逻辑可以直接放在端点里复杂工作流使用 Mediator 的 Command/Query横切关注点使用 Mediator 管道 Behavior。后果✅ 初始复杂度更低✅ 开发者自行选择抽象层级⚠️ 代码库中可能出现不一致的模式⚠️ 需要团队级指南明确何时使用 Mediator。这是 Minimal 模板中最能“从源码结构看”出设计意图的决策仓库里两种风格同时存在恰好就是文档使用指南的活示例风格 A逻辑直接在端点内简单 CRUD。ProductFeatures/Create/CreateEndpoint.cs 直接在端点里创建聚合、调用仓储并返回201 Created全程没有 Command/Handlerpublic override async TaskResultsCreatedProductRecord, ValidationProblem, ProblemHttpResult ExecuteAsync(CreateProductRequest request, CancellationToken cancellationToken) { var product Product.Create(request.Name, request.UnitPrice); await _repository.AddAsync(product, cancellationToken); await _repository.SaveChangesAsync(cancellationToken); var response new ProductRecord(product.Id.Value, product.Name, product.UnitPrice); return TypedResults.Created($/Products/{product.Id.Value}, response); }风格 BCommand Handler复杂工作流。“加入购物车”涉及“校验商品存在 → 查购物车或新建 → 添加商品行 → 更新 → 映射 DTO”多步流程因此走 Mediator端点只做协议转换与 HTTP 映射AddToCartEndpoint.cs#L65-L84业务规则集中在 AddToCartHandler.cspublic record AddToCartCommand(CartId? CartId, int ProductId, int Quantity) : ICommandResultCartDto; public class AddToCartHandler( IRepositoryCart cartRepository, IReadRepositoryProduct productRepository) : ICommandHandlerAddToCartCommand, ResultCartDto { public async ValueTaskResultCartDto Handle(AddToCartCommand request, CancellationToken cancellationToken) { // 1. 校验商品存在ProductByIdSpec // 2. 有 CartId 则用 CartByIdSpec 取购物车否则 new Cart() 并 AddAsync // 3. cart.AddItem(productId, quantity, product.UnitPrice) // 4. UpdateAsync 并映射为 CartDto含行项目与小计 } }Handler 通过ResultCartDto返回领域结果Result.NotFound/IsSuccess端点再把领域结果翻译为NotFound/Problem/Ok等HttpResults——领域层与 HTTP 语义彻底解耦这就是文档所说“测试时可以不经过 HTTP 层”的具体含义。Mediator 的注册见 Configurations/MediatorConfig.cs基于 Mediator 源码生成器SourceGen扫描程序集自动发现 Handler生命周期设为Scoped并在PipelineBehaviors中注册了LoggingBehavior,管道——这正是“横切关注点交给管道 Behavior”的落地。ADR-005FastEndpoints 构建 API背景需要干净、可测试的 API 端点。决策使用 FastEndpoints 并遵循 REPR 模式Request / Endpoint / (Validator) / (Mapper) / Response。后果✅ 每个端点一个文件易查找✅ 内置校验支持✅ 请求/响应类型清晰✅ 可不经过 HTTP 层测试⚠️ 与标准 ASP.NET Core 模式不同⚠️ 团队存在学习曲线。对应证据AddToCartEndpoint.cs 单文件内即包含AddToCartRequest、AddToCartEndpoint、AddToCartValidatorFluentValidation 规则与AddToCartMapper四个类型Configure()中还用Summary/Tags/Description声明了 OpenAPI 示例与响应码200/400/404——“一个文件一个端点、自带校验与文档”的特点一目了然。四、最佳实践文档代码示例与真实源码对照文档“Best Practices”一节给出了三层代码示例下面将其与仓库真实实现对照说明这些示例并非孤立样板。领域层封装的实体 vs 贫血模型文档示例强调用行为方法封装业务规则避免公开集合属性// Good: Encapsulated entity public class Cart { private readonly ListCartItem _items new(); public IReadOnlyCollectionCartItem Items _items.AsReadOnly(); public void AddItem(Product product, int quantity) { var existingItem _items.FirstOrDefault(i i.ProductId product.Id); if (existingItem ! null) { existingItem.IncreaseQuantity(quantity); } else { _items.Add(new CartItem(product, quantity)); } } } // Avoid: Anemic domain model public class Cart { public ListCartItem Items { get; set; } new(); }真实实现 Cart.cs 与之同构私有_items 只读投影 AddItem行为方法并额外提供CreatedOn时间戳与MarkAsDeleted()软删除行为继承EntityBaseCart, CartId并实现IAggregateRoot接口。Product聚合则展示了工厂方法 防护条款的完整封装见第三节 ADR-003 证据。端点层清晰、聚焦的端点文档给出“端点直接操作 DbContext 创建购物车”的示例EndpointWithoutRequestCartResponsePost(/carts)AllowAnonymous()。这与仓库中端点写法一致路由、匿名访问、OpenAPI 元数据集中在Configure()执行逻辑短小且只做“领域结果 → HTTP 结果”的映射例如 CreateEndpoint.cs 的Post(/Products)与Summary(...)声明。基础设施层聚焦的 EF 配置文档示例public class CartConfiguration : IEntityTypeConfigurationCart { public void Configure(EntityTypeBuilderCart builder) { builder.HasKey(c c.Id); builder.HasMany(c c.Items) .WithOne() .HasForeignKey(CartId); } }真实仓库的 Infrastructure/Data/Config/ 目录按实体一对一放置配置类CartConfiguration.cs、CartItemConfiguration.cs、OrderConfiguration.cs、ProductConfiguration.cs、GuestUserConfiguration.cs等并由 AppDbContext.cs 通过modelBuilder.ApplyConfigurationsFromAssembly(...)统一加载——与文档“每个实体一个聚焦配置类”的建议完全对应。五、数据访问与横切能力的源码细节虽然 Minimal 模板主张“直接数据访问”的简化路线但仓库实现仍保留了几个值得注意的工程细节均见 InfrastructureServiceExtensions.cs仓储即 Specification 基类EfRepository.cs 只有 8 行——继承 Ardalis 的RepositoryBaseT同时实现IRepositoryT与IReadRepositoryT读写分离的接口区分。这解释了 Handler 中FirstOrDefaultAsync(spec)这类按规格查询的写法为何“零样板”。领域事件拦截器AddDbContext时注入EventDispatchInterceptor并注册IDomainEventDispatcher的 Mediator 实现。即领域事件通过 EF Core 保存拦截器分发到 Mediator 管道EventDispatcherInterceptor.cs 与 EventDispatchInterceptor 注册 构成完整链路。连接串强约束连接串必须来自配置键AppDb缺失时直接以Guard.Against.Null抛出带提示的异常“请确保通过 Aspire 运行应用”。分页查询服务读侧查询不走仓储而走专用查询服务如 ListProductsQueryService.cs端点侧配合 PagedResult.cs 与Constants中的DEFAULT_PAGE_SIZE/MAX_PAGE_SIZE校验器约束page/per_page参数见 ListEndpoint.cs并输出 GitHub 风格的Link响应头。六、适用场景与不适用场景文档给出了明确的应用边界读者选型时应直接对照理想场景MVP 与原型快速验证想法需要架构指导但不想背项目模板的包袱未来可能成长为大型应用中小型应用5–50 个端点、5–20 个领域实体、1–5 名开发者、简单到中等复杂度的领域学习 Clean Architecture希望先理解原则而非复杂度作为迈向完整 Clean Architecture 的跳板也适合作为团队教学工具偏好垂直切片架构的团队倾向按功能组织代码、计划日后拆微服务、认为内聚比分层更重要。不推荐场景大型企业应用需要大量 DDD 模式的复杂领域、多团队需要严格边界、预期长期演进 → 改用完整 Clean Architecture即仓库根目录 src/ 下的多项目模板一开始就是微服务如果确定要拆成多个服务 → 每个服务各自采用 minimal 模板强监管/强合规需要严格审计要求、必须由编译器强制的层边界 → 改用完整 Clean Architecture。七、迁移路径Minimal ⇄ Full 双向演进从 Minimal 到完整 Clean Architecture应用增长后可按文档四步迁移Step 1提取 Core 项目# Create new Core project dotnet new classlib -n YourProject.Core # Move domain entities mv Domain/* ../YourProject.Core/ # Update namespaces # Update project referencesStep 2提取 Infrastructure 项目# Create Infrastructure project dotnet new classlib -n YourProject.Infrastructure # Move infrastructure code mv Infrastructure/* ../YourProject.Infrastructure/ # Add reference to Core dotnet add YourProject.Infrastructure reference YourProject.CoreStep 3提取 UseCases可选# Create UseCases project dotnet new classlib -n YourProject.UseCases # Move business logic from endpoints to use cases # Add Mediator (if not already using) # Create command/query handlers # Leverage Mediator Behaviors for cross-cutting concernsStep 4清理 Web 项目更新项目引用仅保留端点与启动代码按需引用 UseCases 或 Infrastructure。对照仓库中的完整模板可以发现迁移目标结构正是 src/Clean.Architecture.Core聚合根、事件、服务接口、src/Clean.Architecture.InfrastructureAppDbContext、Migrations、仓储、拦截器与 src/Clean.Architecture.UseCasesCommand/Query 及 Handler三件套迁移后的依赖方向UseCases → CoreInfrastructure → CoreWeb → UseCases与 Minimal 模板文档中的 Dependency Flow 完全同构因此演进成本可控。从完整 Clean Architecture 到 Minimal若觉得完整模板过重反向操作同样可行合并项目把所有代码复制进 Web 项目按垂直切片重新组织简化模式用 LINQ 替代 Specification在有益处处把值对象简化为基元类型移除不必要的抽象垂直组织按功能而非层分组相关代码就近放置。八、测试策略单元测试聚焦领域功能测试覆盖端点文档的测试策略分两层示例如下。单元测试聚焦领域逻辑public class CartTests { [Fact] public void AddItem_NewProduct_AddsToCart() { // Arrange var cart new Cart(guestUserId: Guid.NewGuid()); var product new Product(Test, 10m); // Act cart.AddItem(product, 2); // Assert Assert.Single(cart.Items); Assert.Equal(2, cart.Items.First().Quantity); } }这类测试之所以可行正因为聚合根的业务规则收敛在行为方法内且不带框架依赖ADR-003 的“可测试性”原则。仓库中完整模板与示例的测试工程tests/Clean.Architecture.UnitTests、sample/tests/NimblePros.SampleToDo.UnitTests中大量“聚合构造/行为”命名的测试文件如ContributorConstructor.cs、Project_AddItem.cs印证了同样的测试组织习惯而 Minimal 模板本身在文档层面给出的是方法论指引落地时可按同一模式在独立测试工程中编写。功能测试端到端覆盖端点public class CartEndpointsTests : IClassFixtureWebApplicationFactoryProgram { [Fact] public async Task CreateCart_ReturnsNewCart() { // Arrange var client _factory.CreateClient(); // Act var response await client.PostAsync(/carts, null); // Assert response.EnsureSuccessStatusCode(); var cart await response.Content.ReadFromJsonAsyncCartResponse(); Assert.NotNull(cart); } }其支撑点在 Program.cspublic partial class Program { }使WebApplicationFactoryProgram能定位到正确程序集。仓库中的功能测试工程tests/Clean.Architecture.FunctionalTests使用CustomWebApplicationFactory覆盖端点行为可作为功能测试组织方式的进一步参考。九、构建与运行如何验证本模板依据 MinimalClean 的 README 模板 与源码约束运行方式如下命令在MinimalClean/目录下执行# 构建解决方案 dotnet build # 直接运行 Web 项目 dotnet run --project src/MinimalClean.Architecture.Web # 或推荐方式通过 Aspire AppHost 运行自动提供容器化 SQL Server dotnet run --project src/MinimalClean.Architecture.AspireHost适用前提与限制数据库InfrastructureServiceExtensions.cs 使用UseSqlServer连接串取自AppDb配置键且必填缺失即启动失败。推荐路径是运行 MinimalClean.Architecture.AspireHost由 Aspire 自动拉起 SQL Server 容器、创建数据库并在启动时应用 Migrations/ 中的迁移脱离 Aspire 直跑需自行在 appsettings.json 提供可用的AppDb连接串如 LocalDB并通过dotnet ef database update应用迁移API 文档AddFastEndpoints().SwaggerDocument(...)已启用 Swaggerapi.http文件MinimalClean/src/MinimalClean.Architecture.Web/api.http提供了开箱即用的请求示例。十、延伸阅读与仓库内关联文档本文主体文档docs/content/minimal-clean-architecture.mdMinimal 模板解决方案入口MinimalClean/MinimalClean.Architecture.slnx、模板 READMEMinimalClean/README.template.md完整版模板的入口程序与目录src/Clean.Architecture.Web/Program.cs、src/相关设计文档docs/content/design-decisions.md、docs/content/getting-started.md、docs/content/migration-guides/v10-to-v11.md、docs/content/architecture-decisions/adr-001-dotnet-di-adoption.md行业参考资料文档 Resources 一节提及可检索对应主题FastEndpoints 官方文档、Jimmy Bogard 的 Vertical Slice Architecture 系列文章、领域驱动设计基础课程与 Clean Architecture 入门课程文档末尾的协作与许可说明贡献指南见 CONTRIBUTING.md许可协议为 MIT见 LICENSE。【免费下载链接】CleanArchitectureClean Architecture Solution Template: A proven Clean Architecture Template for ASP.NET Core 10项目地址: https://gitcode.com/GitHub_Trending/cl/CleanArchitecture创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表