.NET 9 架構(gòu)對(duì)決:清潔架構(gòu) vs. 垂直切片架構(gòu),誰(shuí)更適合現(xiàn)代開(kāi)發(fā)?
基于 .NET 9 的最新發(fā)展以及 .NET 社區(qū)不斷演進(jìn)的架構(gòu)模式,讓我們探討這兩種流行的方法如何得到增強(qiáng),以及哪種最適合現(xiàn)代應(yīng)用程序開(kāi)發(fā)。
.NET 9:兩種架構(gòu)的游戲規(guī)則改變者
.NET 9 引入了顯著的改進(jìn),使兩種架構(gòu)方法都受益:
性能增強(qiáng)
? .NET 9 最小 API 性能:每秒請(qǐng)求數(shù)提升 15%,內(nèi)存消耗減少 93%
? 改進(jìn)的垃圾回收 (GC):動(dòng)態(tài)適應(yīng)應(yīng)用程序大小,取代了傳統(tǒng)的服務(wù)器 GC (Server GC)
? 運(yùn)行時(shí)優(yōu)化:增強(qiáng)的循環(huán)優(yōu)化、內(nèi)聯(lián)和 ARM64 矢量化
? 異常處理:異常處理速度提升 2-4 倍,這對(duì)錯(cuò)誤處理模式至關(guān)重要
支持現(xiàn)代架構(gòu)的新特性
? 增強(qiáng)的 System.Text.Json:支持可空引用類(lèi)型和 JSON 模式導(dǎo)出
? LINQ 改進(jìn):新的 CountBy 和 AggregateBy 方法,用于更好的數(shù)據(jù)處理
? 高級(jí)功能開(kāi)關(guān):通過(guò)修剪 (trimming) 支持更好地控制應(yīng)用程序功能
.NET 9 中的清潔架構(gòu):增強(qiáng)與成熟
現(xiàn)代清潔架構(gòu)實(shí)現(xiàn)
// .NET 9 清潔架構(gòu)與增強(qiáng)特性
namespaceCleanArchitecture.Domain.Entities;
publicclassProduct
{
publicint Id { get; privateset; }
publicrequiredstring Name { get; privateset; }
publicdecimal Price { get; privateset; }
public DateTime CreatedAt { get; privateset; } = DateTime.UtcNow;
// 利用 .NET 9 性能改進(jìn)的領(lǐng)域事件
privatereadonly 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)
thrownew ArgumentException("Price must be positive", nameof(newPrice));
Price = newPrice;
AddDomainEvent(new ProductPriceUpdatedEvent(Id, newPrice));
}
private void AddDomainEvent(IDomainEvent domainEvent) => _domainEvents.Add(domainEvent);
}
// 應(yīng)用層與 .NET 9 CQRS 實(shí)現(xiàn)
namespaceCleanArchitecture.Application.Products.Commands;
public record CreateProductCommand(string Name, decimal Price) : IRequest<Result<ProductResponse>>;
publicclassCreateProductCommandHandler : IRequestHandler<CreateProductCommand, Result<ProductResponse>>
{
privatereadonly IProductRepository _repository;
privatereadonly IUnitOfWork _unitOfWork;
public CreateProductCommandHandler(IProductRepository repository, IUnitOfWork unitOfWork)
{
_repository = repository;
_unitOfWork = unitOfWork;
}
publicasync 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 端點(diǎn)
namespaceCleanArchitecture.WebApi.Endpoints;
publicstaticclassProductEndpoints
{
public static void MapProductEndpoints(this IEndpointRouteBuilder app)
{
vargroup = 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 清潔架構(gòu)項(xiàng)目結(jié)構(gòu)
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 中的垂直切片架構(gòu):簡(jiǎn)化與現(xiàn)代
利用 .NET 9 最小 API 增強(qiáng)
垂直切片架構(gòu)與 .NET 9 改進(jìn)的最小 API 相結(jié)合,創(chuàng)造了極其強(qiáng)大和高性能的解決方案:
// .NET 9 垂直切片實(shí)現(xiàn)
namespaceVideoGameApi.Features.Games.GetAll;
publicstaticclassGetAllGames
{
// 內(nèi)嵌類(lèi)型以提高內(nèi)聚性
public record Query() : IRequest<Result<IEnumerable<GameResponse>>>;
public record GameResponse(int Id, string Title, string Genre, int ReleaseYear);
// 驗(yàn)證器
publicclassQueryValidator : AbstractValidator<Query>
{
public QueryValidator()
{
// 如果需要,添加查詢(xún)驗(yàn)證規(guī)則
}
}
// 利用 .NET 9 性能優(yōu)化的處理程序
publicclassHandler : IRequestHandler<Query, Result<IEnumerable<GameResponse>>>
{
privatereadonly IGameRepository _repository;
public Handler(IGameRepository repository)
{
_repository = repository;
}
publicasync Task<Result<IEnumerable<GameResponse>>> Handle(Query request, CancellationToken cancellationToken)
{
var games = await _repository.GetAllAsync(cancellationToken);
// 使用 .NET 9 LINQ 改進(jìn)
var response = games.Select(g => new GameResponse(g.Id, g.Title, g.Genre, g.ReleaseYear));
return Result<IEnumerable<GameResponse>>.Success(response);
}
}
// .NET 9 最小 API 端點(diǎn)
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);
}
}
// 功能注冊(cè)
namespaceVideoGameApi.Features.Games.Create;
publicstaticclassCreateGame
{
public record Command(string Title, string Genre, int ReleaseYear) : IRequest<Result<GameResponse>>;
public record GameResponse(int Id, string Title, string Genre, int ReleaseYear);
publicclassCommandValidator : 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);
}
}
publicclassHandler : IRequestHandler<Command, Result<GameResponse>>
{
privatereadonly GameDbContext _context;
public Handler(GameDbContext context)
{
_context = context;
}
publicasync 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 垂直切片項(xiàng)目結(jié)構(gòu)
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 增強(qiáng)的 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 增強(qiáng)的服務(wù)注冊(cè)
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 利用 .NET 9 改進(jìn)的 Entity Framework
builder.Services.AddDbContext<GameDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 利用 .NET 9 性能優(yōu)化的 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 增強(qiáng)的健康檢查
builder.Services.AddHealthChecks()
.AddDbContext<GameDbContext>();
var app = builder.Build();
// .NET 9 增強(qiáng)的管道
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// 從垂直切片映射端點(diǎn)
GetAllGames.MapEndpoint(app);
CreateGame.MapEndpoint(app);
UpdateGame.MapEndpoint(app);
DeleteGame.MapEndpoint(app);
// 健康檢查
app.MapHealthChecks("/health");
app.Run();混合方法:.NET 9 中兩全其美
使用 .NET 9 的切片式清潔架構(gòu)
// 結(jié)合 .NET 9 特性融合兩種方法
namespaceHybridApp.Features.Products.Create;
// 遵循垂直切片組織的清潔架構(gòu)原則
publicstaticclassCreateProduct
{
// 遵循 CQRS 的請(qǐng)求/響應(yīng)
public record Command(string Name, decimal Price, int CategoryId) : IRequest<Result<Response>>;
public record Response(int Id, string Name, decimal Price);
// 驗(yàn)證
publicclassValidator : AbstractValidator<Command>
{
public Validator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
RuleFor(x => x.Price).GreaterThan(0);
RuleFor(x => x.CategoryId).GreaterThan(0);
}
}
// 領(lǐng)域邏輯處理程序 (清潔架構(gòu))
publicclassHandler : IRequestHandler<Command, Result<Response>>
{
privatereadonly IProductRepository _repository; // 領(lǐng)域接口
privatereadonly IUnitOfWork _unitOfWork; // 領(lǐng)域接口
privatereadonly IDomainEventDispatcher _eventDispatcher; // 領(lǐng)域接口
public Handler(IProductRepository repository, IUnitOfWork unitOfWork, IDomainEventDispatcher eventDispatcher)
{
_repository = repository;
_unitOfWork = unitOfWork;
_eventDispatcher = eventDispatcher;
}
publicasync Task<Result<Response>> Handle(Command request, CancellationToken cancellationToken)
{
// 領(lǐng)域邏輯
var product = Product.Create(request.Name, request.Price, request.CategoryId);
// 倉(cāng)儲(chǔ)模式 (清潔架構(gòu))
await _repository.AddAsync(product, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 領(lǐng)域事件
await _eventDispatcher.DispatchAsync(product.DomainEvents, cancellationToken);
return Result<Response>.Success(new Response(product.Id, product.Name, product.Price));
}
}
// .NET 9 最小 API 端點(diǎn) (垂直切片)
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() // 清潔架構(gòu)安全性
.AddEndpointFilter<ValidationFilter<Command>>(); // 清潔架構(gòu)驗(yàn)證
}
}.NET 9 中的性能對(duì)比
基準(zhǔn)測(cè)試:清潔架構(gòu) vs. 垂直切片
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
publicclassArchitectureBenchmarks
{
privatereadonly IMediator _mediator;
privatereadonly HttpClient _httpClient;
[GlobalSetup]
public async Task Setup()
{
// 設(shè)置兩種架構(gòu)方法
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 結(jié)果:
| 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 中何時(shí)選擇每種方法
選擇清潔架構(gòu)當(dāng):
? 復(fù)雜業(yè)務(wù)邏輯:具有復(fù)雜領(lǐng)域規(guī)則的多界限上下文 (bounded contexts)
? 大型企業(yè)應(yīng)用程序:需要嚴(yán)格的關(guān)注點(diǎn)分離和可測(cè)試性
? 團(tuán)隊(duì)規(guī)模:需要清晰邊界和職責(zé)的大型團(tuán)隊(duì)
? 長(zhǎng)期維護(hù):預(yù)期會(huì)隨時(shí)間顯著演進(jìn)的應(yīng)用程序
? 法規(guī)要求:需要審計(jì)跟蹤和合規(guī)性的應(yīng)用程序
選擇垂直切片架構(gòu)當(dāng):
? 功能驅(qū)動(dòng)開(kāi)發(fā):快速交付獨(dú)立功能
? CRUD 密集型應(yīng)用程序:不證明需要復(fù)雜分層的簡(jiǎn)單業(yè)務(wù)邏輯
? 中小型團(tuán)隊(duì):減少協(xié)調(diào)開(kāi)銷(xiāo)
? 微服務(wù):每個(gè)服務(wù)圍繞業(yè)務(wù)能力組織
? 現(xiàn)代 .NET 9 API:利用最小 API 性能改進(jìn)
選擇混合方法當(dāng):
? 增長(zhǎng)中的應(yīng)用程序:從切片開(kāi)始,根據(jù)需要演進(jìn)到清潔架構(gòu)
? 平衡的復(fù)雜性:既需要功能聚焦也需要關(guān)注點(diǎn)分離
? 團(tuán)隊(duì)靈活性:開(kāi)發(fā)人員對(duì)兩種方法都感到適應(yīng)
? 兩全其美:想要具有垂直切片組織的清潔架構(gòu)優(yōu)勢(shì)
.NET 9 模板推薦
清潔架構(gòu)模板
# Jason Taylor 的清潔架構(gòu)模板 (已為 .NET 9 更新)
dotnet new install Clean.Architecture.Solution.Template
dotnet new ca-sln -n MyCleanApp
# Sam 的增強(qiáng)型清潔架構(gòu)模板
dotnet new install Sam.CleanArchitecture.Template::9.2.0
dotnet new ca-api -n MyEnterpriseApp垂直切片模板
# 垂直切片架構(gòu)模板
dotnet new install VerticalSliceArchitecture.Template
dotnet new vsa -n MyVerticalSliceApp
# 自定義最小 API 模板
dotnet new webapi -n MyMinimalApiApp -minimal結(jié)論:.NET 9 視角
在 .NET 9 中,清潔架構(gòu)和垂直切片架構(gòu)之間的選擇比以往任何時(shí)候都更加細(xì)致入微:
.NET 9 的關(guān)鍵要點(diǎn):
? 性能至關(guān)重要:.NET 9 的最小 API 改進(jìn)使得垂直切片架構(gòu)在高性能場(chǎng)景中更具吸引力
? 兩者可以共存:結(jié)合清潔架構(gòu)原則和垂直切片組織的混合方法日益流行
? 現(xiàn)代開(kāi)發(fā):.NET 9 的增強(qiáng)特性使兩種方法都受益,但垂直切片與最小 API 配合尤其出色
? 團(tuán)隊(duì)和背景:團(tuán)隊(duì)的經(jīng)驗(yàn)、應(yīng)用程序的復(fù)雜性和性能要求應(yīng)驅(qū)動(dòng)決策
? 演進(jìn)路徑:從簡(jiǎn)單的垂直切片開(kāi)始,隨著復(fù)雜性增長(zhǎng)而演進(jìn)到清潔架構(gòu)
.NET 9 的最終建議:
? 新項(xiàng)目:使用 .NET 9 最小 API 從垂直切片架構(gòu)開(kāi)始
? 復(fù)雜領(lǐng)域:在層內(nèi)使用切片式組織的清潔架構(gòu)
? 高性能:利用 .NET 9 的最小 API 改進(jìn)與垂直切片
? 企業(yè)應(yīng)用:使用具有 .NET 9 增強(qiáng)功能的清潔架構(gòu)以實(shí)現(xiàn)可維護(hù)性
最好的架構(gòu)是滿(mǎn)足您的特定需求,同時(shí)利用 .NET 9 性能改進(jìn)和現(xiàn)代開(kāi)發(fā)模式的架構(gòu)。

























