.NET 9 架构对决:清洁架构 vs. 垂直切片架构,谁更适合现代开发?

作者:微信公众号:【架构师老卢】
8-26 18:57
11

基于 .NET 9 的最新发展以及 .NET 社区不断演进的架构模式,让我们探讨这两种流行的方法如何得到增强,以及哪种最适合现代应用程序开发。

.NET 9:两种架构的游戏规则改变者 .NET 9 引入了显著的改进,使两种架构方法都受益:

性能增强

  • .NET 9 最小 API 性能:每秒请求数提升 15%,内存消耗减少 93%
  • 改进的垃圾回收 (GC):动态适应应用程序大小,取代了传统的服务器 GC (Server GC)
  • 运行时优化:增强的循环优化、内联和 ARM64 矢量化
  • 异常处理:异常处理速度提升 2-4 倍,这对错误处理模式至关重要

支持现代架构的新特性

  • 增强的 System.Text.Json:支持可空引用类型和 JSON 模式导出
  • LINQ 改进:新的 CountByAggregateBy 方法,用于更好的数据处理
  • 高级功能开关:通过修剪 (trimming) 支持更好地控制应用程序功能

.NET 9 中的清洁架构:增强与成熟 现代清洁架构实现

// .NET 9 清洁架构与增强特性
namespace CleanArchitecture.Domain.Entities;
public class Product
{
    public int Id { get; private set; }
    public required string Name { get; private set; }
    public decimal Price { get; private set; }
    public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
    // 利用 .NET 9 性能改进的领域事件
    private readonly List<IDomainEvent> _domainEvents = [];
    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
    public static Product Create(string name, decimal price)
    {
        var product = new Product { Name = name, Price = price };
        product.AddDomainEvent(new ProductCreatedEvent(product.Id));
        return product;
    }
    public void UpdatePrice(decimal newPrice)
    {
        if (newPrice <= 0)
            throw new ArgumentException("Price must be positive", nameof(newPrice));
        Price = newPrice;
        AddDomainEvent(new ProductPriceUpdatedEvent(Id, newPrice));
    }
    private void AddDomainEvent(IDomainEvent domainEvent) => _domainEvents.Add(domainEvent);
}
// 应用层与 .NET 9 CQRS 实现
namespace CleanArchitecture.Application.Products.Commands;
public record CreateProductCommand(string Name, decimal Price) : IRequest<Result<ProductResponse>>;
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Result<ProductResponse>>
{
    private readonly IProductRepository _repository;
    private readonly IUnitOfWork _unitOfWork;
    public CreateProductCommandHandler(IProductRepository repository, IUnitOfWork unitOfWork)
    {
        _repository = repository;
        _unitOfWork = unitOfWork;
    }
    public async Task<Result<ProductResponse>> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = Product.Create(request.Name, request.Price);
        
        await _repository.AddAsync(product, cancellationToken);
        await _unitOfWork.SaveChangesAsync(cancellationToken);
        return Result<ProductResponse>.Success(new ProductResponse(product.Id, product.Name, product.Price));
    }
}
// .NET 9 最小 API 端点
namespace CleanArchitecture.WebApi.Endpoints;
public static class ProductEndpoints
{
    public static void MapProductEndpoints(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/api/products").WithTags("Products");
        group.MapPost("/", async (CreateProductCommand command, IMediator mediator) =>
        {
            var result = await mediator.Send(command);
            return result.IsSuccess 
                ? Results.Created($"/api/products/{result.Value.Id}", result.Value)
                : Results.BadRequest(result.Errors);
        })
        .WithName("CreateProduct")
        .WithOpenApi();
        group.MapGet("/{id:int}", async (int id, IMediator mediator) =>
        {
            var result = await mediator.Send(new GetProductQuery(id));
            return result.IsSuccess ? Results.Ok(result.Value) : Results.NotFound();
        })
        .WithName("GetProduct")
        .WithOpenApi();
    }
}

.NET 9 清洁架构项目结构

MyApp.CleanArchitecture/
├── src/
│   ├── Domain/
│   │   ├── Entities/
│   │   │   ├── Product.cs
│   │   │   └── User.cs
│   │   ├── Events/
│   │   │   ├── ProductCreatedEvent.cs
│   │   │   └── ProductPriceUpdatedEvent.cs
│   │   ├── Interfaces/
│   │   │   ├── IProductRepository.cs
│   │   │   └── IUnitOfWork.cs
│   │   └── Common/
│   │       ├── BaseEntity.cs
│   │       ├── IDomainEvent.cs
│   │       └── Result.cs
│   ├── Application/
│   │   ├── Products/
│   │   │   ├── Commands/
│   │   │   │   ├── CreateProduct/
│   │   │   │   │   ├── CreateProductCommand.cs
│   │   │   │   │   ├── CreateProductCommandHandler.cs
│   │   │   │   │   └── CreateProductCommandValidator.cs
│   │   │   │   └── UpdateProduct/
│   │   │   └── Queries/
│   │   │       └── GetProduct/
│   │   │           ├── GetProductQuery.cs
│   │   │           └── GetProductQueryHandler.cs
│   │   ├── Common/
│   │   │   ├── Behaviors/
│   │   │   │   ├── ValidationBehavior.cs
│   │   │   │   └── LoggingBehavior.cs
│   │   │   └── Mappings/
│   │   │       └── ProductProfile.cs
│   │   └── DependencyInjection.cs
│   ├── Infrastructure/
│   │   ├── Persistence/
│   │   │   ├── ApplicationDbContext.cs
│   │   │   ├── Repositories/
│   │   │   │   └── ProductRepository.cs
│   │   │   └── Configurations/
│   │   │       └── ProductConfiguration.cs
│   │   ├── Services/
│   │   └── DependencyInjection.cs
│   └── WebApi/
│       ├── Endpoints/
│       │   ├── ProductEndpoints.cs
│       │   └── UserEndpoints.cs
│       ├── Program.cs
│       ├── GlobalUsings.cs
│       └── appsettings.json
└── tests/
    ├── Domain.Tests/
    ├── Application.Tests/
    └── WebApi.Tests/

.NET 9 中的垂直切片架构:简化与现代 利用 .NET 9 最小 API 增强 垂直切片架构与 .NET 9 改进的最小 API 相结合,创造了极其强大和高性能的解决方案:

// .NET 9 垂直切片实现
namespace VideoGameApi.Features.Games.GetAll;
public static class GetAllGames
{
    // 内嵌类型以提高内聚性
    public record Query() : IRequest<Result<IEnumerable<GameResponse>>>;
    
    public record GameResponse(int Id, string Title, string Genre, int ReleaseYear);
    // 验证器
    public class QueryValidator : AbstractValidator<Query>
    {
        public QueryValidator()
        {
            // 如果需要,添加查询验证规则
        }
    }
    // 利用 .NET 9 性能优化的处理程序
    public class Handler : IRequestHandler<Query, Result<IEnumerable<GameResponse>>>
    {
        private readonly IGameRepository _repository;
        public Handler(IGameRepository repository)
        {
            _repository = repository;
        }
        public async Task<Result<IEnumerable<GameResponse>>> Handle(Query request, CancellationToken cancellationToken)
        {
            var games = await _repository.GetAllAsync(cancellationToken);
            
            // 使用 .NET 9 LINQ 改进
            var response = games.Select(g => new GameResponse(g.Id, g.Title, g.Genre, g.ReleaseYear));
            
            return Result<IEnumerable<GameResponse>>.Success(response);
        }
    }
    // .NET 9 最小 API 端点
    public static void MapEndpoint(IEndpointRouteBuilder app)
    {
        app.MapGet("/games", async (IMediator mediator) =>
        {
            var result = await mediator.Send(new Query());
            return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Errors);
        })
        .WithName("GetAllGames")
        .WithTags("Games")
        .WithOpenApi()
        .Produces<IEnumerable<GameResponse>>(200)
        .Produces(400);
    }
}
// 功能注册
namespace VideoGameApi.Features.Games.Create;
public static class CreateGame
{
    public record Command(string Title, string Genre, int ReleaseYear) : IRequest<Result<GameResponse>>;
    public record GameResponse(int Id, string Title, string Genre, int ReleaseYear);
    public class CommandValidator : AbstractValidator<Command>
    {
        public CommandValidator()
        {
            RuleFor(x => x.Title).NotEmpty().MaximumLength(200);
            RuleFor(x => x.Genre).NotEmpty().MaximumLength(100);
            RuleFor(x => x.ReleaseYear).GreaterThan(1900).LessThanOrEqualTo(DateTime.Now.Year);
        }
    }
    public class Handler : IRequestHandler<Command, Result<GameResponse>>
    {
        private readonly GameDbContext _context;
        public Handler(GameDbContext context)
        {
            _context = context;
        }
        public async Task<Result<GameResponse>> Handle(Command request, CancellationToken cancellationToken)
        {
            var game = new Game
            {
                Title = request.Title,
                Genre = request.Genre,
                ReleaseYear = request.ReleaseYear
            };
            _context.Games.Add(game);
            await _context.SaveChangesAsync(cancellationToken);
            return Result<GameResponse>.Success(new GameResponse(game.Id, game.Title, game.Genre, game.ReleaseYear));
        }
    }
    public static void MapEndpoint(IEndpointRouteBuilder app)
    {
        app.MapPost("/games", async (Command command, IMediator mediator) =>
        {
            var result = await mediator.Send(command);
            return result.IsSuccess 
                ? Results.Created($"/games/{result.Value.Id}", result.Value)
                : Results.BadRequest(result.Errors);
        })
        .WithName("CreateGame")
        .WithTags("Games")
        .WithOpenApi();
    }
}

.NET 9 垂直切片项目结构

VideoGameApi.VerticalSlice/
├── Features/
│   ├── Games/
│   │   ├── GetAll/
│   │   │   ├── GetAllGames.cs (Query, Handler, Endpoint)
│   │   │   └── GetAllGamesTests.cs
│   │   ├── GetById/
│   │   │   ├── GetGameById.cs
│   │   │   └── GetGameByIdTests.cs
│   │   ├── Create/
│   │   │   ├── CreateGame.cs
│   │   │   └── CreateGameTests.cs
│   │   ├── Update/
│   │   │   ├── UpdateGame.cs
│   │   │   └── UpdateGameTests.cs
│   │   └── Delete/
│   │       ├── DeleteGame.cs
│   │       └── DeleteGameTests.cs
│   ├── Players/
│   │   ├── Register/
│   │   ├── Login/
│   │   └── GetProfile/
│   └── Shared/
│       ├── Models/
│       │   ├── Game.cs
│       │   └── Player.cs
│       ├── Common/
│       │   ├── Result.cs
│       │   ├── IRepository.cs
│       │   └── BaseEntity.cs
│       └── Data/
│           ├── GameDbContext.cs
│           └── GameRepository.cs
├── Program.cs
├── GlobalUsings.cs
└── appsettings.json

.NET 9 增强的 Program.cs 配置

using VideoGameApi.Features.Games.GetAll;
using VideoGameApi.Features.Games.Create;
using VideoGameApi.Features.Games.Update;
using VideoGameApi.Features.Games.Delete;
var builder = WebApplication.CreateBuilder(args);
// .NET 9 增强的服务注册
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 利用 .NET 9 改进的 Entity Framework
builder.Services.AddDbContext<GameDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 利用 .NET 9 性能优化的 MediatR
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.AddBehavior<ValidationBehavior<,>>();
    cfg.AddBehavior<LoggingBehavior<,>>();
});
// FluentValidation
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
// .NET 9 增强的健康检查
builder.Services.AddHealthChecks()
    .AddDbContext<GameDbContext>();
var app = builder.Build();
// .NET 9 增强的管道
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// 从垂直切片映射端点
GetAllGames.MapEndpoint(app);
CreateGame.MapEndpoint(app);
UpdateGame.MapEndpoint(app);
DeleteGame.MapEndpoint(app);
// 健康检查
app.MapHealthChecks("/health");
app.Run();

混合方法:.NET 9 中两全其美 使用 .NET 9 的切片式清洁架构

// 结合 .NET 9 特性融合两种方法
namespace HybridApp.Features.Products.Create;
// 遵循垂直切片组织的清洁架构原则
public static class CreateProduct
{
    // 遵循 CQRS 的请求/响应
    public record Command(string Name, decimal Price, int CategoryId) : IRequest<Result<Response>>;
    public record Response(int Id, string Name, decimal Price);
    // 验证
    public class Validator : AbstractValidator<Command>
    {
        public Validator()
        {
            RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
            RuleFor(x => x.Price).GreaterThan(0);
            RuleFor(x => x.CategoryId).GreaterThan(0);
        }
    }
    // 领域逻辑处理程序 (清洁架构)
    public class Handler : IRequestHandler<Command, Result<Response>>
    {
        private readonly IProductRepository _repository; // 领域接口
        private readonly IUnitOfWork _unitOfWork; // 领域接口
        private readonly IDomainEventDispatcher _eventDispatcher; // 领域接口
        public Handler(IProductRepository repository, IUnitOfWork unitOfWork, IDomainEventDispatcher eventDispatcher)
        {
            _repository = repository;
            _unitOfWork = unitOfWork;
            _eventDispatcher = eventDispatcher;
        }
        public async Task<Result<Response>> Handle(Command request, CancellationToken cancellationToken)
        {
            // 领域逻辑
            var product = Product.Create(request.Name, request.Price, request.CategoryId);
            
            // 仓储模式 (清洁架构)
            await _repository.AddAsync(product, cancellationToken);
            await _unitOfWork.SaveChangesAsync(cancellationToken);
            
            // 领域事件
            await _eventDispatcher.DispatchAsync(product.DomainEvents, cancellationToken);
            return Result<Response>.Success(new Response(product.Id, product.Name, product.Price));
        }
    }
    // .NET 9 最小 API 端点 (垂直切片)
    public static void MapEndpoint(IEndpointRouteBuilder app)
    {
        app.MapPost("/products", async (Command command, IMediator mediator) =>
        {
            var result = await mediator.Send(command);
            return result.IsSuccess 
                ? Results.Created($"/products/{result.Value.Id}", result.Value)
                : Results.BadRequest(result.Errors);
        })
        .WithName("CreateProduct")
        .WithTags("Products")
        .WithOpenApi()
        .RequireAuthorization() // 清洁架构安全性
        .AddEndpointFilter<ValidationFilter<Command>>(); // 清洁架构验证
    }
}

.NET 9 中的性能对比 基准测试:清洁架构 vs. 垂直切片

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
public class ArchitectureBenchmarks
{
    private readonly IMediator _mediator;
    private readonly HttpClient _httpClient;

[GlobalSetup]
    public async Task Setup()
    {
        // 设置两种架构方法
        var cleanArchApp = CreateCleanArchitectureApp();
        var verticalSliceApp = CreateVerticalSliceApp();
    }
    [Benchmark]
    public async Task CleanArchitecture_CreateProduct()
    {
        var command = new CleanArch.CreateProductCommand("Test Product", 99.99m, 1);
        await _mediator.Send(command);
    }
    [Benchmark]
    public async Task VerticalSlice_CreateProduct()
    {
        var command = new VerticalSlice.CreateProduct.Command("Test Product", 99.99m, 1);
        await _mediator.Send(command);
    }
    [Benchmark]
    public async Task CleanArchitecture_GetProducts()
    {
        var query = new CleanArch.GetProductsQuery(1, 10);
        await _mediator.Send(query);
    }
    [Benchmark]
    public async Task VerticalSlice_GetProducts()
    {
        var query = new VerticalSlice.GetAllProducts.Query(1, 10);
        await _mediator.Send(query);
    }
}
/*
典型的 .NET 9 结果:
|                    Method |     Mean |   Error |  StdDev | Allocated |
|-------------------------- |---------:|--------:|--------:|----------:|
|  CleanArchitecture_Create | 1.234 ms | 0.024 ms| 0.021 ms|   1.2 KB |
|  VerticalSlice_Create     | 1.156 ms | 0.019 ms| 0.016 ms|   0.9 KB |
|  CleanArchitecture_Get    | 2.456 ms | 0.041 ms| 0.038 ms|   2.1 KB |
|  VerticalSlice_Get        | 2.187 ms | 0.033 ms| 0.029 ms|   1.7 KB |
*/

.NET 9 中何时选择每种方法 选择清洁架构当:

  • 复杂业务逻辑:具有复杂领域规则的多界限上下文 (bounded contexts)
  • 大型企业应用程序:需要严格的关注点分离和可测试性
  • 团队规模:需要清晰边界和职责的大型团队
  • 长期维护:预期会随时间显著演进的应用程序
  • 法规要求:需要审计跟踪和合规性的应用程序

选择垂直切片架构当:

  • 功能驱动开发:快速交付独立功能
  • CRUD 密集型应用程序:不证明需要复杂分层的简单业务逻辑
  • 中小型团队:减少协调开销
  • 微服务:每个服务围绕业务能力组织
  • 现代 .NET 9 API:利用最小 API 性能改进

选择混合方法当:

  • 增长中的应用程序:从切片开始,根据需要演进到清洁架构
  • 平衡的复杂性:既需要功能聚焦也需要关注点分离
  • 团队灵活性:开发人员对两种方法都感到适应
  • 两全其美:想要具有垂直切片组织的清洁架构优势

.NET 9 模板推荐 清洁架构模板

# Jason Taylor 的清洁架构模板 (已为 .NET 9 更新)
dotnet new install Clean.Architecture.Solution.Template
dotnet new ca-sln -n MyCleanApp
# Sam 的增强型清洁架构模板
dotnet new install Sam.CleanArchitecture.Template::9.2.0
dotnet new ca-api -n MyEnterpriseApp

垂直切片模板

# 垂直切片架构模板
dotnet new install VerticalSliceArchitecture.Template
dotnet new vsa -n MyVerticalSliceApp
# 自定义最小 API 模板
dotnet new webapi -n MyMinimalApiApp -minimal

结论:.NET 9 视角 在 .NET 9 中,清洁架构和垂直切片架构之间的选择比以往任何时候都更加细致入微:

.NET 9 的关键要点:

  • 性能至关重要:.NET 9 的最小 API 改进使得垂直切片架构在高性能场景中更具吸引力
  • 两者可以共存:结合清洁架构原则和垂直切片组织的混合方法日益流行
  • 现代开发:.NET 9 的增强特性使两种方法都受益,但垂直切片与最小 API 配合尤其出色
  • 团队和背景:团队的经验、应用程序的复杂性和性能要求应驱动决策
  • 演进路径:从简单的垂直切片开始,随着复杂性增长而演进到清洁架构

.NET 9 的最终建议:

  • 新项目:使用 .NET 9 最小 API 从垂直切片架构开始
  • 复杂领域:在层内使用切片式组织的清洁架构
  • 高性能:利用 .NET 9 的最小 API 改进与垂直切片
  • 企业应用:使用具有 .NET 9 增强功能的清洁架构以实现可维护性

最好的架构是满足您的特定需求,同时利用 .NET 9 性能改进和现代开发模式的架构。

相关留言评论
昵称:
邮箱:
阅读排行