CleanArchitecture分层架构深度剖析:Domain层与Application层设计

CleanArchitecture分层架构深度剖析:Domain层与Application层设计

【免费下载链接】CleanArchitecture Jason Taylor开发的Clean Architecture项目模板同样基于.NET Core,遵循干净架构原则。它提供了一种组织代码结构的良好起点,鼓励面向接口编程和关注点分离,适用于需要长期维护和易于扩展的企业级应用程序开发。 【免费下载链接】CleanArchitecture 项目地址: https://gitcode.com/GitHub_Trending/cle/CleanArchitecture

本文深入解析Clean Architecture分层架构中Domain层与Application层的核心设计模式。Domain层作为系统核心,承载业务逻辑与领域知识,通过实体(Entity)与值对象(Value Object)设计模式构建健壮的领域模型,包含统一标识管理、领域事件机制和审计追踪功能。Application层则采用MediatR实现命令查询职责分离(CQRS)模式,通过管道行为处理横切关注点,将用例封装为独立命令和查询对象,确保业务逻辑高度内聚和低耦合。

Domain层核心实体与值对象设计模式

在Clean Architecture中,Domain层是整个系统的核心,承载着业务逻辑和领域知识。Domain层的设计质量直接影响整个系统的可维护性和扩展性。本文将深入剖析CleanArchitecture项目中Domain层的核心实体与值对象设计模式,通过具体的代码示例和设计模式分析,帮助开发者理解如何构建健壮的领域模型。

实体(Entity)设计模式

在领域驱动设计中,实体是具有唯一标识和生命周期的对象。CleanArchitecture项目通过精心设计的基类体系来确保实体的一致性和可维护性。

基础实体基类设计

项目定义了BaseEntity抽象基类,为所有实体提供统一的标识和领域事件管理机制:

public abstract class BaseEntity
{
    public int Id { get; set; }

    private readonly List<BaseEvent> _domainEvents = new();

    [NotMapped]
    public IReadOnlyCollection<BaseEvent> DomainEvents => _domainEvents.AsReadOnly();

    public void AddDomainEvent(BaseEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }

    public void RemoveDomainEvent(BaseEvent domainEvent)
    {
        _domainEvents.Remove(domainEvent);
    }

    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
}

这个设计体现了几个重要原则:

  • 统一标识管理:所有实体都使用int类型的Id作为主键
  • 领域事件支持:内置的领域事件机制支持事件驱动架构
  • 关注点分离:通过[NotMapped]特性确保领域事件不被持久化
可审计实体设计

项目进一步扩展了BaseAuditableEntity来支持审计追踪:

public abstract class BaseAuditableEntity : BaseEntity
{
    public DateTimeOffset Created { get; set; }
    public string? CreatedBy { get; set; }
    public DateTimeOffset LastModified { get; set; }
    public string? LastModifiedBy { get; set; }
}

这种设计模式确保了:

  • 创建时间和创建人信息的自动记录
  • 最后修改时间和修改人信息的跟踪
  • 符合企业级应用的审计需求
具体实体实现

以TodoList实体为例,展示了简洁而强大的实体设计:

public class TodoList : BaseAuditableEntity
{
    public string? Title { get; set; }
    public Colour Colour { get; set; } = Colour.White;
    public IList<TodoItem> Items { get; private set; } = new List<TodoItem>();
}

TodoItem实体则展示了更复杂的业务逻辑封装:

public class TodoItem : BaseAuditableEntity
{
    public int ListId { get; set; }
    public string? Title { get; set; }
    public string? Note { get; set; }
    public PriorityLevel Priority { get; set; }
    public DateTime? Reminder { get; set; }

    private bool _done;
    public bool Done
    {
        get => _done;
        set
        {
            if (value && !_done)
            {
                AddDomainEvent(new TodoItemCompletedEvent(this));
            }
            _done = value;
        }
    }

    public TodoList List { get; set; } = null!;
}

这个设计体现了领域驱动设计的重要原则:

  • 富领域模型:业务逻辑封装在实体内部
  • 领域事件触发:状态变更时自动触发相应事件
  • 导航属性:明确的关联关系表达

值对象(Value Object)设计模式

值对象是没有唯一标识,通过属性值来定义相等性的对象。CleanArchitecture项目通过ValueObject基类提供了强大的值对象支持。

值对象基类设计
public abstract class ValueObject
{
    protected static bool EqualOperator(ValueObject left, ValueObject right)
    {
        if (left is null ^ right is null) return false;
        return left?.Equals(right!) != false;
    }

    protected static bool NotEqualOperator(ValueObject left, ValueObject right)
    {
        return !(EqualOperator(left, right));
    }

    protected abstract IEnumerable<object> GetEqualityComponents();

    public override bool Equals(object? obj)
    {
        if (obj == null || obj.GetType() != GetType()) return false;
        var other = (ValueObject)obj;
        return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
    }

    public override int GetHashCode()
    {
        var hash = new HashCode();
        foreach (var component in GetEqualityComponents())
        {
            hash.Add(component);
        }
        return hash.ToHashCode();
    }
}

这个基类实现了:

  • 值相等性比较:基于属性值的深度比较
  • 哈希码一致性:确保相等的对象有相同的哈希码
  • 类型安全:严格的类型检查
具体值对象实现

Colour值对象展示了完整的值对象设计模式:

public class Colour(string code) : ValueObject
{
    public static Colour From(string code)
    {
        var colour = new Colour(code);
        if (!SupportedColours.Contains(colour))
        {
            throw new UnsupportedColourException(code);
        }
        return colour;
    }

    public static Colour White => new("#FFFFFF");
    public static Colour Red => new("#FF5733");
    public static Colour Orange => new("#FFC300");
    // 其他预定义颜色...

    public string Code { get; private set; } = string.IsNullOrWhiteSpace(code)?"#000000":code;

    public static implicit operator string(Colour colour) => colour.ToString();
    public static explicit operator Colour(string code) => From(code);

    public override string ToString() => Code;

    protected static IEnumerable<Colour> SupportedColours
    {
        get
        {
            yield return White;
            yield return Red;
            // 其他支持的颜色...
        }
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Code;
    }
}

这个设计体现了值对象的典型特征:

  • 不可变性:通过私有setter保护内部状态
  • 验证逻辑:构造函数中的业务规则验证
  • 工厂方法:提供安全的对象创建方式
  • 隐式转换:与string类型的无缝转换
  • 预定义实例:提供常用的值对象实例

设计模式与最佳实践

1. 领域事件模式

项目通过领域事件实现了松耦合的架构:

mermaid

2. 值对象验证模式

值对象内置验证机制确保数据完整性:

mermaid

3. 实体关系管理

实体之间的关系通过清晰的导航属性管理:

关系类型实现方式示例
一对多IList TodoList.Items
多对一外键属性TodoItem.ListId
导航属性引用属性TodoItem.List
4. 状态变更模式

通过属性setter封装业务逻辑:

private bool _done;
public bool Done
{
    get => _done;
    set
    {
        if (value && !_done)  // 状态从未完成变为完成
        {
            AddDomainEvent(new TodoItemCompletedEvent(this));
        }
        _done = value;
    }
}

这种模式确保了:

  • 状态变更时的业务规则执行
  • 领域事件的自动发布
  • 线程安全的属性访问

总结与最佳实践

CleanArchitecture项目的Domain层设计体现了领域驱动设计的核心原则:

  1. 明确的边界上下文:通过命名空间和项目结构明确划分领域边界
  2. 丰富的领域模型:业务逻辑封装在实体和值对象内部
  3. 不可变的值对象:确保值对象的状态安全和线程安全
  4. 事件驱动架构:通过领域事件实现松耦合的系统组件
  5. 统一的基类设计:通过基类确保一致性和可维护性

这种设计模式为构建复杂的企业级应用提供了坚实的基础,确保了代码的可读性、可维护性和可扩展性。通过遵循这些设计原则,开发者可以构建出更加健壮和灵活的领域模型。

Application层用例实现与MediatR集成

在Clean Architecture中,Application层作为业务逻辑的核心承载层,通过MediatR库实现了命令查询职责分离(CQRS)模式。这种设计模式将应用程序的用例封装为独立的命令和查询对象,每个用例都有对应的处理器来处理特定的业务逻辑。

MediatR管道架构设计

Application层通过MediatR构建了一个强大的请求处理管道,该管道包含多个行为(Behaviors)来增强系统的功能性和健壮性:

mermaid

命令与查询的实现模式

命令(Command)实现示例

命令用于执行会改变系统状态的操作,如创建、更新、删除等。每个命令都实现了IRequestIRequest<TResponse>接口:

// 创建待办事项列表命令
public record CreateTodoListCommand : IRequest<int>
{
    public string? Title { get; init; }
}

// 命令处理器
public class CreateTodoListCommandHandler : IRequestHandler<CreateTodoListCommand, int>
{
    private readonly IApplicationDbContext _context;

    public CreateTodoListCommandHandler(IApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<int> Handle(CreateTodoListCommand request, CancellationToken cancellationToken)
    {
        var entity = new TodoList();
        entity.Title = request.Title;
        _context.TodoLists.Add(entity);
        await _context.SaveChangesAsync(cancellationToken);
        return entity.Id;
    }
}
查询(Query)实现示例

查询用于获取系统状态信息,不改变系统状态。查询同样实现了IRequest<TResponse>接口:

// 获取待办事项查询
[Authorize]
public record GetTodosQuery : IRequest<TodosVm>;

// 查询处理器
public class GetTodosQueryHandler : IRequestHandler<GetTodosQuery, TodosVm>
{
    private readonly IApplicationDbContext _context;
    private readonly IMapper _mapper;

    public GetTodosQueryHandler(IApplicationDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<TodosVm> Handle(GetTodosQuery request, CancellationToken cancellationToken)
    {
        return new TodosVm
        {
            PriorityLevels = Enum.GetValues(typeof(PriorityLevel))
                .Cast<PriorityLevel>()
                .Select(p => new LookupDto { Id = (int)p, Title = p.ToString() })
                .ToList(),
            Lists = await _context.TodoLists
                .AsNoTracking()
                .ProjectTo<TodoListDto>(_mapper.ConfigurationProvider)
                .OrderBy(t => t.Title)
                .ToListAsync(cancellationToken)
        };
    }
}

管道行为详解

Application层通过MediatR的管道行为机制实现了横切关注点的统一处理:

1. 验证行为(ValidationBehaviour)
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, 
        CancellationToken cancellationToken)
    {
        if (_validators.Any())
        {
            var context = new ValidationContext<TRequest>(request);
            var validationResults = await Task.WhenAll(
                _validators.Select(v => v.ValidateAsync(context, cancellationToken)));
            var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
            
            if (failures.Count != 0)
                throw new ValidationException(failures);
        }
        return await next();
    }
}
2. 授权行为(AuthorizationBehaviour)
public class AuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> 
    where TRequest : notnull
{
    private readonly IUser _user;
    private readonly IIdentityService _identityService;

    public AuthorizationBehaviour(IUser user, IIdentityService identityService)
    {
        _user = user;
        _identityService = identityService;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, 
        CancellationToken cancellationToken)
    {
        var authorizeAttributes = request.GetType().GetCustomAttributes<AuthorizeAttribute>();
        if (authorizeAttributes.Any())
        {
            // 验证用户是否已认证
            if (_user.Id == null)
                throw new UnauthorizedAccessException();
            
            // 检查角色授权
            var authorizeAttributesWithRoles = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Roles));
            if (authorizeAttributesWithRoles.Any())
            {
                var authorized = false;
                foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(',')))
                {
                    foreach (var role in roles)
                    {
                        var isInRole = await _identityService.IsInRoleAsync(_user.Id, role.Trim());
                        if (isInRole)
                        {
                            authorized = true;
                            break;
                        }
                    }
                }
                if (!authorized)
                    throw new ForbiddenAccessException();
            }
            
            // 检查策略授权
            var authorizeAttributesWithPolicies = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Policy));
            if (authorizeAttributesWithPolicies.Any())
            {
                foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy))
                {
                    var authorized = await _identityService.AuthorizeAsync(_user.Id, policy);
                    if (!authorized)
                        throw new ForbiddenAccessException();
                }
            }
        }
        return await next();
    }
}

用例组织结构

Application层采用功能文件夹结构组织用例,每个功能模块包含Commands和Queries子文件夹:

Application/
├── TodoLists/
│   ├── Commands/
│   │   ├── CreateTodoList/
│   │   ├── UpdateTodoList/
│   │   └── DeleteTodoList/
│   └── Queries/
│       └── GetTodos/
├── TodoItems/
│   ├── Commands/
│   │   ├── CreateTodoItem/
│   │   ├── UpdateTodoItem/
│   │   └── DeleteTodoItem/
│   └── Queries/
│       └── GetTodoItemsWithPagination/
└── WeatherForecasts/
    └── Queries/
        └── GetWeatherForecasts/

依赖注入配置

Application层的服务注册通过扩展方法实现,自动注册所有MediatR处理器、验证器和AutoMapper配置:

public static void AddApplicationServices(this IHostApplicationBuilder builder)
{
    builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
    builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
    builder.Services.AddMediatR(cfg => {
        cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
        cfg.AddOpenRequestPreProcessor(typeof(LoggingBehaviour<>));
        cfg.AddOpenBehavior(typeof(UnhandledExceptionBehaviour<,>));
        cfg.AddOpenBehavior(typeof(AuthorizationBehaviour<,>));
        cfg.AddOpenBehavior(typeof(ValidationBehaviour<,>));
        cfg.AddOpenBehavior(typeof(PerformanceBehaviour<,>));
    });
}

用例模板生成

项目提供了dotnet模板工具,可以快速生成新的用例:

# 创建新命令
dotnet new ca-usecase --name CreateProduct --feature-name Products --usecase-type command --return-type int

# 创建新查询
dotnet new ca-usecase -n GetProducts -fn Products -ut query -rt ProductsVm

这种基于MediatR的用例实现方式确保了业务逻辑的高度内聚和低耦合,每个用例都是独立的、可测试的单元,同时通过管道行为实现了横切关注点的统一管理,大大提高了代码的可维护性和可扩展性。

CQRS模式在CleanArchitecture中的应用

CQRS(Command Query Responsibility Segregation)模式是Clean Architecture中至关重要的设计模式,它将数据操作明确划分为命令(Command)和查询(Query)两种不同的职责。在CleanArchitecture项目中,CQRS模式通过MediatR库得到了优雅的实现,为应用程序提供了清晰的架构边界和更好的可维护性。

CQRS架构设计原理

在CleanArchitecture中,CQRS模式的核心思想是将写入操作(命令)和读取操作(查询)分离到不同的模型中。这种分离带来了多个显著优势:

  • 关注点分离:命令处理业务逻辑变更,查询专注于数据检索
  • 性能优化:可以针对读写操作采用不同的优化策略
  • 可扩展性:读写操作可以独立扩展
  • 简化代码:每个处理程序只负责单一职责

mermaid

命令(Command)实现模式

命令代表对系统的写入操作,通常会导致系统状态的改变。在CleanArchitecture中,命令通过MediatR的IRequestIRequest<TResponse>接口定义:

public record CreateTodoItemCommand : IRequest<int>
{
    public int ListId { get; init; }
    public string? Title { get; init; }
}

public class CreateTodoItemCommandHandler : IRequestHandler<CreateTodoItemCommand, int>
{
    private readonly IApplicationDbContext _context;

    public async Task<int> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
    {
        var entity = new TodoItem
        {
            ListId = request.ListId,
            Title = request.Title,
            Done = false
        };

        entity.AddDomainEvent(new TodoItemCreatedEvent(entity));
        _context.TodoItems.Add(entity);
        await _context.SaveChangesAsync(cancellationToken);

        return entity.Id;
    }
}

命令处理器的典型特征包括:

特征描述示例
状态变更修改领域实体状态创建、更新、删除操作
事务性通常需要数据库事务SaveChangesAsync调用
领域事件可能触发领域事件AddDomainEvent方法
返回值可选,通常返回操作结果实体ID或操作状态

查询(Query)实现模式

查询代表对系统的读取操作,不会改变系统状态,只返回数据:

[Authorize]
public record GetTodosQuery : IRequest<TodosVm>;

public class GetTodosQueryHandler : IRequestHandler<GetTodosQuery, TodosVm>
{
    private readonly IApplicationDbContext _context;
    private readonly IMapper _mapper;

    public async Task<TodosVm> Handle(GetTodosQuery request, CancellationToken cancellationToken)
    {
        return new TodosVm
        {
            PriorityLevels = Enum.GetValues(typeof(PriorityLevel))
                .Cast<PriorityLevel>()
                .Select(p => new LookupDto { Id = (int)p, Title = p.ToString() })
                .ToList(),
            Lists = await _context.TodoLists
                .AsNoTracking()
                .ProjectTo<TodoListDto>(_mapper.ConfigurationProvider)
                .OrderBy(t => t.Title)
                .ToListAsync(cancellationToken)
        };
    }
}

查询处理器的关键特点:

特点优势实现方式
无状态不会修改系统状态只读数据库操作
性能优化可以使用专门优化AsNoTracking查询
DTO投影返回特定视图模型AutoMapper投影
缓存友好适合缓存策略纯数据检索

MediatR管道行为

CleanArchitecture利用MediatR的管道行为为CQRS操作添加横切关注点:

// 验证行为
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        var validator = _validators.FirstOrDefault();
        if (validator != null)
        {
            var context = new ValidationContext<TRequest>(request);
            var validationResults = await validator.ValidateAsync(context, cancellationToken);
            if (!validationResults.IsValid) throw new ValidationException(validationResults.Errors);
        }
        return await next();
    }
}

// 性能监控行为
public class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        var stopwatch = Stopwatch.StartNew();
        var response = await next();
        stopwatch.Stop();
        if (stopwatch.ElapsedMilliseconds > 500)
            _logger.LogWarning("Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds)", typeof(TRequest).Name, stopwatch.ElapsedMilliseconds);
        return response;
    }
}

管道行为为CQRS操作提供了统一的处理机制:

mermaid

用例模板生成

CleanArchitecture提供了专门的dotnet模板来快速生成CQRS用例:

# 创建命令用例
dotnet new ca-usecase --name CreateTodoList --feature-name TodoLists --usecase-type command --return-type int

# 创建查询用例  
dotnet new ca-usecase -n GetTodos -fn TodoLists -ut query -rt TodosVm

模板自动生成的标准结构包括:

  • 命令/查询定义类
  • 处理程序实现
  • 验证器(可选)
  • DTO类(查询需要)
  • 映射配置

领域事件集成

CQRS模式与领域事件完美集成,命令操作可以触发领域事件:

public class CreateTodoItemCommandHandler : IRequestHandler<CreateTodoItemCommand, int>
{
    public async Task<int> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
    {
        var entity = new TodoItem
        {
            ListId = request.ListId,
            Title = request.Title,
            Done = false
        };

        // 添加领域事件
        entity.AddDomainEvent(new TodoItemCreatedEvent(entity));
        
        _context.TodoItems.Add(entity);
        await _context.SaveChangesAsync(cancellationToken);

        return entity.Id;
    }
}

这种集成使得CQRS不仅是数据操作的分离,更是整个领域驱动设计架构的重要组成部分。

测试策略

CQRS模式为测试提供了清晰的边界:

// 命令处理器测试
[Test]
public async Task Handle_ValidCommand_ShouldCreateTodoItem()
{
    var context = CreateDbContext();
    var handler = new CreateTodoItemCommandHandler(context);
    
    var command = new CreateTodoItemCommand { ListId = 1, Title = "Test Item" };
    var result = await handler.Handle(command, CancellationToken.None);
    
    result.Should().BeGreaterThan(0);
    context.TodoItems.Should().ContainSingle(i => i.Title == "Test Item");
}

// 查询处理器测试  
[Test]
public async Task Handle_GetTodosQuery_ShouldReturnTodosVm()
{
    var context = CreateDbContextWithData();
    var mapper = CreateMapper();
    var handler = new GetTodosQueryHandler(context, mapper);
    
    var result = await handler.Handle(new GetTodosQuery(), CancellationToken.None);
    
    result.Should().NotBeNull();
    result.Lists.Should().HaveCount(1);
}

测试策略充分利用了CQRS的分离特性,使得单元测试更加专注和高效。

通过CQRS模式的实施,CleanArchitecture实现了业务逻辑的清晰分离、更好的性能优化空间以及更易于测试和维护的代码结构。这种模式与Clean Architecture的分层原则完美契合,为构建可扩展的企业级应用提供了坚实的基础。

领域事件与事件处理机制解析

在Clean Architecture中,领域事件是实现领域驱动设计(DDD)中重要模式的核心机制。它允许领域对象在状态发生变化时发布事件,其他组件可以订阅这些事件并执行相应的业务逻辑,从而实现松耦合的架构设计。

领域事件的核心组件

Clean Architecture模板提供了完整的领域事件实现框架,主要包括以下几个核心组件:

1. 基础事件抽象类 (BaseEvent)
public abstract class BaseEvent : INotification
{
}

所有领域事件都继承自BaseEvent基类,它实现了MediatR的INotification接口,这使得事件可以通过MediatR进行发布和订阅。

2. 实体基类中的事件管理 (BaseEntity)
public abstract class BaseEntity
{
    private readonly List<BaseEvent> _domainEvents = new();

    [NotMapped]
    public IReadOnlyCollection<BaseEvent> DomainEvents => _domainEvents.AsReadOnly();

    public void AddDomainEvent(BaseEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }

    public void RemoveDomainEvent(BaseEvent domainEvent)
    {
        _domainEvents.Remove(domainEvent);
    }

    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
}

每个实体都可以管理自己的领域事件集合,通过AddDomainEvent方法添加事件,这些事件将在事务提交时被统一发布。

3. 具体领域事件实现

模板中提供了几个典型的领域事件示例:

// TodoItem完成事件
public class TodoItemCompletedEvent : BaseEvent
{
    public TodoItemCompletedEvent(TodoItem item)
    {
        Item = item;
    }
    public TodoItem Item { get; }
}

// TodoItem创建事件  
public class TodoItemCreatedEvent : BaseEvent
{
    public TodoItemCreatedEvent(TodoItem item)
    {
        Item = item;
    }
    public TodoItem Item { get; }
}

// TodoItem删除事件
public class TodoItemDeletedEvent : BaseEvent
{
    public TodoItemDeletedEvent(TodoItem item)
    {
        Item = item;
    }
    public TodoItem Item { get; }
}

事件处理机制

领域事件的处理通过MediatR的INotificationHandler接口实现:

public class TodoItemCompletedEventHandler : INotificationHandler<TodoItemCompletedEvent>
{
    private readonly ILogger<TodoItemCompletedEventHandler> _logger;

    public TodoItemCompletedEventHandler(ILogger<TodoItemCompletedEventHandler> logger)
    {
        _logger = logger;
    }

    public Task Handle(TodoItemCompletedEvent notification, CancellationToken cancellationToken)
    {
        _logger.LogInformation("CleanArchitecture Domain Event: {DomainEvent}", 
            notification.GetType().Name);
        return Task.CompletedTask;
    }
}

事件分发拦截器

事件的分发通过EF Core的拦截器机制实现:

mermaid

DispatchDomainEventsInterceptor拦截器负责在数据库保存更改时自动发布领域事件:

public async Task DispatchDomainEvents(DbContext? context)
{
    if (context == null) return;

    // 获取所有包含领域事件的实体
    var entities = context.ChangeTracker
        .Entries<BaseEntity>()
        .Where(e => e.Entity.DomainEvents.Any())
        .Select(e => e.Entity);

    // 收集所有领域事件
    var domainEvents = entities
        .SelectMany(e => e.DomainEvents)
        .ToList();

    // 清空实体中的事件集合
    entities.ToList().ForEach(e => e.ClearDomainEvents());

    // 发布所有领域事件
    foreach (var domainEvent in domainEvents)
        await _mediator.Publish(domainEvent);
}

领域事件的使用场景

领域事件在Clean Architecture中主要用于以下场景:

  1. 跨聚合协调:当一个聚合的状态变化需要影响其他聚合时
  2. 最终一致性:实现跨边界的数据一致性
  3. 系统集成:发布事件到消息队列,集成外部系统
  4. 审计日志:记录重要的业务操作
  5. 通知机制:发送邮件、短信等通知

事件处理器的注册

事件处理器在依赖注入容器中自动注册:

// 在Application依赖注入配置中
services.AddMediatR(cfg => 
    cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));

最佳实践建议

实践要点说明示例
事件命名使用过去时态表示已发生的事实TodoItemCompletedEvent
事件数据包含足够的信息供处理器使用包含完整的TodoItem对象
处理器职责每个处理器只负责一个具体的任务日志记录、发送通知等
事务边界事件发布在事务提交后执行通过EF Core拦截器实现

性能考虑

领域事件机制虽然提供了很好的解耦能力,但也需要注意性能影响:

  1. 事件数量:避免在单个事务中发布过多事件
  2. 处理器复杂度:保持事件处理器的逻辑简单高效
  3. 异步处理:利用MediatR的异步处理能力
  4. 错误处理:实现适当的重试和错误处理机制

通过领域事件机制,Clean Architecture实现了真正的松耦合架构,各个层之间的依赖关系清晰明确,便于维护和扩展。

总结

Clean Architecture通过清晰的分层架构和设计模式实现了高度可维护和可扩展的系统设计。Domain层通过实体和值对象封装核心业务逻辑,支持领域事件驱动架构;Application层利用CQRS模式和MediatR管道处理业务用例,实现关注点分离。领域事件机制进一步增强了系统组件间的松耦合,通过事件分发拦截器确保事务一致性。这种架构为复杂企业级应用提供了坚实基础,结合性能优化、测试策略和最佳实践,显著提升了代码质量和系统可靠性。

【免费下载链接】CleanArchitecture Jason Taylor开发的Clean Architecture项目模板同样基于.NET Core,遵循干净架构原则。它提供了一种组织代码结构的良好起点,鼓励面向接口编程和关注点分离,适用于需要长期维护和易于扩展的企业级应用程序开发。 【免费下载链接】CleanArchitecture 项目地址: https://gitcode.com/GitHub_Trending/cle/CleanArchitecture

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值