You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Clean Architecture下拆分Use Case的最优方案探讨

基于Clean Architecture的异步操作拆分方案

针对你的场景,核心目标是将**同步核心操作(创建并保存Todo)与异步非核心操作(通知调度、短链生成)**解耦,让主流程快速返回结果,同时遵循Clean Architecture的分层原则。以下是两种落地性强且复杂度可控的方案:


1. 领域事件驱动(最贴合Clean Architecture设计)

利用领域事件将Todo创建的状态变化与后续操作解耦,符合领域建模的单一职责原则:

步骤1:Core层定义领域事件

在Project.Core中添加领域事件实体和触发逻辑,让Todo实体自身发布状态变化事件:

// 通用领域事件标记接口
public interface IDomainEvent { }

// Todo创建完成的领域事件
public class TodoCreatedDomainEvent : IDomainEvent
{
    public Guid TodoId { get; }
    public string Title { get; }

    public TodoCreatedDomainEvent(Guid todoId, string title)
    {
        TodoId = todoId;
        Title = title;
    }
}

// 修改Todo实体,添加事件存储逻辑
public class Todo
{
    private readonly List<IDomainEvent> _domainEvents = new();

    public Guid Id { get; private set; }
    public string Title { get; private set; }
    public string? ShortLink { get; private set; }

    public Todo(Guid id, string title)
    {
        Id = id;
        Title = title;
        // 创建时自动触发领域事件
        _domainEvents.Add(new TodoCreatedDomainEvent(id, title));
    }

    // 供外部调用更新短链的方法
    public void UpdateShortLink(string shortLink)
    {
        ShortLink = shortLink;
    }

    // 暴露领域事件集合
    public IReadOnlyCollection<IDomainEvent> GetDomainEvents() => _domainEvents.AsReadOnly();
    public void ClearDomainEvents() => _domainEvents.Clear();
}

步骤2:Application层实现事件处理器

将后续操作封装为独立的事件处理器,各自处理异常、重试逻辑:

// 处理通知调度的处理器
public class TodoCreatedNotificationHandler : IDomainEventHandler<TodoCreatedDomainEvent>
{
    private readonly INotificationScheduler _notificationScheduler;

    public TodoCreatedNotificationHandler(INotificationScheduler notificationScheduler)
    {
        _notificationScheduler = notificationScheduler;
    }

    public async Task Handle(TodoCreatedDomainEvent @event, CancellationToken cancellationToken)
    {
        // 独立实现异常捕获与重试
        try
        {
            await _notificationScheduler.ScheduleTodoAddedNotification(@event.TodoId, cancellationToken);
        }
        catch (Exception ex)
        {
            // 记录日志或加入重试队列
            // 不影响主流程
        }
    }
}

// 处理短链生成的处理器
public class TodoCreatedShortLinkHandler : IDomainEventHandler<TodoCreatedDomainEvent>
{
    private readonly IShortLinkGenerator _shortLinkGenerator;
    private readonly ITodoRepository _todoRepository;

    public TodoCreatedShortLinkHandler(IShortLinkGenerator shortLinkGenerator, ITodoRepository todoRepository)
    {
        _shortLinkGenerator = shortLinkGenerator;
        _todoRepository = todoRepository;
    }

    public async Task Handle(TodoCreatedDomainEvent @event, CancellationToken cancellationToken)
    {
        try
        {
            var shortLink = await _shortLinkGenerator.GenerateForTodo(@event.TodoId, cancellationToken);
            var todo = await _todoRepository.GetById(@event.TodoId, cancellationToken);
            todo.UpdateShortLink(shortLink);
            await _todoRepository.Update(todo, cancellationToken);
        }
        catch (Exception ex)
        {
            // 独立处理异常逻辑
        }
    }
}

步骤3:修改CreateTodo命令主流程

主命令只负责核心的Todo创建与保存,异步发布领域事件:

public class CreateTodoCommandHandler : ICommandHandler<CreateTodoCommand, Guid>
{
    private readonly ITodoRepository _todoRepository;
    private readonly IDomainEventBus _domainEventBus;

    public CreateTodoCommandHandler(ITodoRepository todoRepository, IDomainEventBus domainEventBus)
    {
        _todoRepository = todoRepository;
        _domainEventBus = domainEventBus;
    }

    public async Task<Guid> Handle(CreateTodoCommand request, CancellationToken cancellationToken)
    {
        var todo = new Todo(Guid.NewGuid(), request.Title);
        await _todoRepository.Add(todo, cancellationToken);

        // 异步发布领域事件,不等待后续操作完成
        foreach (var domainEvent in todo.GetDomainEvents())
        {
            _ = _domainEventBus.PublishAsync(domainEvent, cancellationToken);
        }
        todo.ClearDomainEvents();

        return todo.Id;
    }
}

步骤4:基础设施层实现轻量事件总线

可以先基于内存实现简单版本,后续可扩展为消息队列(如RabbitMQ):

public interface IDomainEventBus
{
    Task PublishAsync<TEvent>(TEvent domainEvent, CancellationToken cancellationToken) where TEvent : IDomainEvent;
}

public class InMemoryDomainEventBus : IDomainEventBus
{
    private readonly IServiceProvider _serviceProvider;

    public InMemoryDomainEventBus(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task PublishAsync<TEvent>(TEvent domainEvent, CancellationToken cancellationToken) where TEvent : IDomainEvent
    {
        // 异步执行处理器,不阻塞主流程
        _ = Task.Run(async () =>
        {
            using var scope = _serviceProvider.CreateScope();
            var handlers = scope.ServiceProvider.GetServices<IDomainEventHandler<TEvent>>();
            foreach (var handler in handlers)
            {
                await handler.Handle(domainEvent, cancellationToken);
            }
        }, cancellationToken);
    }
}

2. 核心命令+后台任务队列(极简实现)

如果不想引入领域事件,可直接将后续操作拆分为独立命令,通过轻量后台队列执行:

步骤1:定义后台命令与处理器

// 通知调度命令
public record ScheduleTodoNotificationCommand(Guid TodoId);

public class ScheduleTodoNotificationHandler : ICommandHandler<ScheduleTodoNotificationCommand>
{
    private readonly INotificationScheduler _notificationScheduler;

    public ScheduleTodoNotificationHandler(INotificationScheduler notificationScheduler)
    {
        _notificationScheduler = notificationScheduler;
    }

    public async Task Handle(ScheduleTodoNotificationCommand request, CancellationToken cancellationToken)
    {
        // 独立处理异常与重试
        await _notificationScheduler.ScheduleTodoAddedNotification(request.TodoId, cancellationToken);
    }
}

// 短链生成命令类似,此处省略

步骤2:实现后台命令队列与处理器

用IHostedService实现轻量后台消费服务,替代复杂的Worker Service:

public interface IBackgroundCommandQueue
{
    Task EnqueueAsync<TCommand>(TCommand command, CancellationToken cancellationToken) where TCommand : ICommand;
    Task<TCommand> DequeueAsync<TCommand>(CancellationToken cancellationToken) where TCommand : ICommand;
}

public class BackgroundCommandProcessor : BackgroundService
{
    private readonly IBackgroundCommandQueue _queue;
    private readonly IServiceProvider _serviceProvider;

    public BackgroundCommandProcessor(IBackgroundCommandQueue queue, IServiceProvider serviceProvider)
    {
        _queue = queue;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // 消费队列中的命令
            var notificationCmd = await _queue.DequeueAsync<ScheduleTodoNotificationCommand>(stoppingToken);
            using var scope = _serviceProvider.CreateScope();
            var handler = scope.ServiceProvider.GetRequiredService<ICommandHandler<ScheduleTodoNotificationCommand>>();
            await handler.Handle(notificationCmd, stoppingToken);

            // 同理处理短链生成命令
            var shortLinkCmd = await _queue.DequeueAsync<GenerateTodoShortLinkCommand>(stoppingToken);
            var shortLinkHandler = scope.ServiceProvider.GetRequiredService<ICommandHandler<GenerateTodoShortLinkCommand>>();
            await shortLinkHandler.Handle(shortLinkCmd, stoppingToken);
        }
    }
}

步骤3:修改CreateTodo命令

public class CreateTodoCommandHandler : ICommandHandler<CreateTodoCommand, Guid>
{
    private readonly ITodoRepository _todoRepository;
    private readonly IBackgroundCommandQueue _backgroundQueue;

    public CreateTodoCommandHandler(ITodoRepository todoRepository, IBackgroundCommandQueue backgroundQueue)
    {
        _todoRepository = todoRepository;
        _backgroundQueue = backgroundQueue;
    }

    public async Task<Guid> Handle(CreateTodoCommand request, CancellationToken cancellationToken)
    {
        var todo = new Todo(Guid.NewGuid(), request.Title);
        await _todoRepository.Add(todo, cancellationToken);

        // 将后台命令加入队列,异步执行
        await _backgroundQueue.EnqueueAsync(new ScheduleTodoNotificationCommand(todo.Id), cancellationToken);
        await _backgroundQueue.EnqueueAsync(new GenerateTodoShortLinkCommand(todo.Id), cancellationToken);

        return todo.Id;
    }
}

方案选择建议

方案优势适用场景
领域事件驱动完全符合Clean Architecture领域建模,解耦性极强,便于后续扩展业务逻辑后续操作与领域状态强关联的场景
后台任务队列实现极简,逻辑直观后续操作无强领域关联性的场景

两种方案都能实现主流程快速返回,异步操作独立处理异常、重试等逻辑,避免主命令逻辑臃肿。

内容的提问来源于stack exchange,提问作者RussianPupok Pupok

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 03:45:32