首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【转】发件箱模式(The Outbox Pattern)

【转】发件箱模式(The Outbox Pattern)

作者头像
保持热爱奔赴山海
发布于 2025-08-13 16:24:27
发布于 2025-08-13 16:24:27
6630
举报
文章被收录于专栏:DevOpsDevOps

介绍

有时,在处理业务操作时,您需要以“发后不理”模式与外部组件进行通信。例如,该组件可以是:

  • 外部服务
  • 消息总线
  • 邮件服务器
  • 相同的数据库但不同的数据库事务
  • 另一个数据库

这种与外部组件集成的示例:

  • 下订单后发送电子邮件
  • 向消息系统发送有关新客户注册的事件
  • 在不同的数据库事务中处理另一个DDD 聚合- 例如下订单后减少库存产品数量

随之而来的问题是,从技术角度来看,我们能否保证业务操作的原子性?很遗憾,并非总是如此,即使我们能够做到(使用2PC 协议),从延迟、吞吐量、可扩展性和可用性的角度来看,这也会限制我们的系统。有关这些限制的详细信息,我建议您阅读题为《是时候从两阶段提交迈进了》的文章。

代码语言:txt
复制
// RegisterCustomerCommandHandler without outbox
public class RegisterCustomerCommandHandler : IRequestHandler<RegisterCustomerCommand, CustomerDto>
{
    private readonly ICustomerRepository _customerRepository;
    private readonly ICustomerUniquenessChecker _customerUniquenessChecker;
    private readonly IEventBus _eventBus;


    public RegisterCustomerCommandHandler(
        ICustomerRepository customerRepository, 
        ICustomerUniquenessChecker customerUniquenessChecker, 
        IEventBus eventBus)
    {
        this._customerRepository = customerRepository;
        _customerUniquenessChecker = customerUniquenessChecker;
        _eventBus = eventBus;
    }

    public async Task<CustomerDto> Handle(RegisterCustomerCommand request, CancellationToken cancellationToken)
    {
        var customer = new Customer(request.Email, request.Name, this._customerUniquenessChecker);

        await this._customerRepository.AddAsync(customer);

        await this._customerRepository.UnitOfWork.CommitAsync(cancellationToken); // commit transaction

        // End of transaction--------------------------------------------------------

        this._eventBus.Send(new CustomerRegisteredIntegrationEvent(customer.Id)); // send an event

        return new CustomerDto { Id = customer.Id };
    }
}

第 24 行执行完成后,事务提交。在第 28 行,我们想将事件发送到事件总线,但不幸的是,可能会发生两件糟糕的事情:

  • 我们的系统可能会在事务提交后、发送事件之前崩溃
  • 事件总线此时可能不可用,因此无法发送事件

如果我们不能提供原子性,或者由于上述原因我们不想这样做,那么我们可以做些什么来提高系统的可靠性呢?我们应该实现发件箱模式。

发件箱模式

发件箱模式基于保证交付模式,如下所示:

当您将数据保存为一个事务的一部分时,您也会将稍后要处理的邮件保存为同一事务的一部分。待处理的邮件列表称为“发件箱”,就像在电子邮件客户端中一样。

难题的第二个要素是一个单独的进程,它会定期检查发件箱的内容并处理邮件。处理完每条邮件后,应将其标记为已处理,以避免重复发送。但是,由于与发件箱的通信错误,我们可能无法将邮件标记为已处理:

在这种情况下,当与发件箱的连接恢复后,同一条消息将再次发送。这对我们意味着什么?发件箱模式提供至少一次的传递。我们可以确保消息只会发送一次,但也可以发送多次!这就是为什么这种方法的另一个名称是“一次或多次传递”。我们应该记住这一点,并尝试将消息的接收者设计为幂等的,这意味着:

在消息传递中,此概念指的是无论接收一次还是多次,消息的效果都相同。这意味着即使接收方收到同一条消息的副本,也可以安全地重新发送该消息,而不会造成任何问题。

参考:

https://microservices.io/patterns/data/transactional-outbox.html

https://www.kamilgrzybek.com/blog/posts/the-outbox-pattern

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 介绍
  • 发件箱模式
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档