想象一下,您的系统需要支持新的数据库或不同的用户界面。使用 Clean Architecture,此类更改变得更容易处理,因为核心业务逻辑不会受到外部依赖项的影响。正如 Bob 大叔所强调的那样,应用程序的中心应该是用例和业务逻辑,而不是框架或数据库。
为什么选择清洁架构?
清洁架构有助于缓解几个常见的架构问题:
- 早期承诺:传统架构通常会迫使团队在项目开始时就做出关键决策,因为那时他们对问题领域的了解很少。清洁架构鼓励将有关框架、数据库和其他细节的决策推迟到必要时再做,从而随着需求的发展,设计可以随时更改。
- 僵化且难以改变的系统:如果没有清晰的结构,新的需求通常需要“黑客”解决方案或昂贵的重新设计。通过将业务规则与实施细节分离,清晰架构使系统更易于适应和扩展。
- 以框架为中心的设计:框架应该是工具,而不是架构本身。它们可以发展并引入重大变化,但如果您的系统与框架无关,则不会受到严重影响。Bob 大叔强调,框架是细节,应该放在边缘。
- 数据库优先思维:许多系统都是围绕数据库构建的,将所有内容都转化为 CRUD 操作。Clean Architecture 将数据库视为另一个数据提供者,确保业务逻辑与数据库无关。
- 分散的业务逻辑:当业务规则分散在多个层上时,理解或修改行为会变得困难。清晰架构将业务逻辑集中在用例内,使其易于定位和维护。
- 测试缓慢而脆弱:将业务逻辑与 UI 或数据库耦合会使测试缓慢而脆弱。清洁架构提倡解耦,从而实现专注于核心逻辑的快速、可靠的单元测试。
清洁架构的关键概念
1. 分离关注点以实现灵活性
清晰架构将职责组织到不同的层中,从而减少依赖性并简化维护。每层都有特定的角色,从而产生更可预测且更有条理的代码库。
2.以领域为中心的设计
领域层是系统的核心,封装了业务逻辑和必要实体。它独立于其他层,紧密贴合业务需求并简化单元测试。
3. 用例和应用逻辑
用例是特定于应用程序的业务规则,用于协调实体之间的交互。它们处理输入和输出,但不知道数据源或呈现细节。
- 请求和响应模型:使用简单的数据结构将用例与框架分离,保持核心逻辑的集中和可测试。
- CQRS(命令查询职责分离):CQRS 模式将数据读写操作分离,优化性能并使代码更清晰。这种方法可确保应用层处理业务逻辑而无需担心基础设施。
4. 基础设施即插件
基础设施层管理外部集成,例如数据库和消息传递系统,隐藏应用程序其余部分的实现细节。将基础设施视为插件可以更轻松地替换或修改技术,而不会影响业务逻辑。
- 六边形架构:也称为端口和适配器,这种模式强调核心和外部系统之间的明确分离,从而增强灵活性。
5. 表示层:用户界面
表示层处理用户交互,通常通过 RESTful API 或 gRPC 进行。它将业务逻辑委托给应用层,仅关注输入和输出。
6.依赖注入
依赖注入对于维护架构完整性至关重要。它控制各层之间的依赖关系,实现灵活性并简化测试。
让我们通过实际的例子来分解每一层。
领域层
领域层定义业务实体和核心规则。例如,一个Webinar实体可能如下所示:
public class Webinar { public Guid Id { get; private set; } public string Name { get; private set; } public DateTime ScheduledOn { get; private set; }
public Webinar(string name, DateTime scheduledOn) { Id = Guid.NewGuid(); // Generates a unique identifier for the webinar Name = name; ScheduledOn = scheduledOn; }
public void Reschedule(DateTime newDate) { ScheduledOn = newDate; } }
|
实体是自包含的,并根据业务需求而不是外部系统约束进行发展。我们还定义了存储库接口和自定义异常:
public interface IWebinarRepository { Task<Webinar?> GetById(Guid id, CancellationToken cancellationToken);
Task Add(Webinar webinar, CancellationToken cancellationToken); }
public class WebinarNotFoundException : Exception { public WebinarNotFoundException(Guid webinarId) : base($"Webinar with ID {webinarId} was not found.") { } }
|
应用层
应用层管理用例并实现CQRS以分离命令和查询,确保效率。
CreateWebinarCommand命令:
public class CreateWebinarCommand : IRequest<Guid> { public string Name { get; set; } public DateTime ScheduledOn { get; set; } }
|
CreateWebinarCommandHandler处理程序:
public class CreateWebinarCommandHandler : IRequestHandler<CreateWebinarCommand, Guid> { private readonly IWebinarRepository _repository;
public CreateWebinarCommandHandler(IWebinarRepository repository) { _repository = repository; }
public async Task<Guid> Handle(CreateWebinarCommand command, CancellationToken cancellationToken) { var webinar = new Webinar(command.Name, command.ScheduledOn); await _repository.Add(webinar, cancellationToken); return webinar.Id; } }
|
GetWebinarByIdQuery查询:
public class GetWebinarByIdQuery : IRequest<Webinar?> { public Guid Id { get; set; } }
|
GetWebinarByIdQueryHandler处理程序:
public class GetWebinarByIdQueryHandler : IRequestHandler<GetWebinarByIdQuery, Webinar?> { private readonly IWebinarRepository _repository;
public GetWebinarByIdQueryHandler(IWebinarRepository repository) { _repository = repository; }
public async Task<Webinar?> Handle(GetWebinarByIdQuery request, CancellationToken cancellationToken) { var webinar = await _repository.GetById(request.Id, cancellationToken); if (webinar is null) throw new WebinarNotFoundException(request.Id);
return webinar; } }
|
这种结构使用例保持隔离并且易于测试。
基础设施层
基础设施层处理外部系统集成,如数据库访问:
public class WebinarRepository : IWebinarRepository { private readonly AppDbContext _dbContext;
public WebinarRepository(AppDbContext dbContext) { _dbContext = dbContext; }
public async Task<Webinar?> GetById(Guid id, CancellationToken cancellationToken) { return await _dbContext.Webinars.FindAsync(id, cancellationToken); }
public async Task Add(Webinar webinar, CancellationToken cancellationToken) { await _dbContext.Webinars.AddAsync(webinar, cancellationToken); await _dbContext.SaveChangesAsync(cancellationToken); } }
|
通过封装这种逻辑,系统可以灵活地适应技术变化。
表示层
表示层提供用于用户交互的API:
[ ApiController ] [ Route( "api/[controller]" ) ] public class WebinarsController : ControllerBase { private readonly IMediator _mediator;
public WebinarsController ( IMediator mediator ) { _mediator = mediator; }
[ HttpPost ] public async Task<IActionResult> CreateWebinar ( [FromBody] CreateWebinarCommand command ) { var webinarId = await _mediator.Send(command); return CreatedAtAction( nameof (GetWebinar), new { id = webinarId }, webinarId); }
[ HttpGet( "{id}" ) ] public async Task<IActionResult> GetWebinar ( Guid id ) { var query = new GetWebinarByIdQuery { Id = id }; var webinar = await _mediator.Send(query); return Ok(webinar); } }
|
通过将逻辑卸载到应用层,该层专注于处理请求和响应。
使用中间件进行集中错误处理
集中错误处理可提高用户体验和安全性:
public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next;
public ExceptionHandlingMiddleware(RequestDelegate next) { _next = next; }
public async Task Invoke(HttpContext context) { try { await _next(context); } catch(WebinarNotFoundException ex) { context.Response.StatusCode = StatusCodes.Status404NotFound; await context.Response.WriteAsJsonAsync(new { Error = ex.Message }); } catch (Exception ex) { context.Response.StatusCode = StatusCodes.Status500InternalServerError; await context.Response.WriteAsJsonAsync(new { Error = ex.Message }); } } }
|
依赖注册Program.cs
配置依赖项Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers() .AddApplicationPart(typeof(WebinarsController).Assembly);
builder.Services.AddScoped<IWebinarRepository, WebinarRepository>();
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(typeof(CreateWebinarCommandHandler).Assembly));
builder.Services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("webinarsDb"));
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseMiddleware<ExceptionHandlingMiddleware>();
if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
|
清洁架构的好处和权衡
清洁架构的好处和权衡
好处:
- 促进有效的测试策略。
- 与框架无关的设计最大限度地减少了外部依赖。
- 业务逻辑的明确分离增强了理解和修改。
- 支持增量部署和持续集成。
权衡:
- 复杂性:引入多个边界会增加开销。请明智地使用边界。
- 代码重复:实体的不同表示可能看似冗余,但却能促进解耦。
结论
清晰架构提供可持续的模块化软件结构。通过维护定义明确的层次,系统变得更易于维护且不易出错,可满足不断变化的需求和技术。