在服务层管理事务 + 清洁架构/DDD 原则

问题描述 投票:0回答:2

我知道在服务层或存储库层管理事务有利有弊。有人会说在服务层管理它们,因为在存储库层管理事务可能会导致多个业务规则耦合到存储库层。另一方面,在存储库层管理事务使服务层与正在使用的数据库无关(如果我弄错了请纠正我)。我很想在服务层管理它们,例如,如果我的邮件服务失败,我可以回滚。

在服务层管理事务时,我必须找到一种方法在我的服务使用的存储库上传播事务,这就是我苦苦挣扎的部分。 Clean Architecture + DDD 原则说要创建一个与正在使用的数据库无关的存储库接口,但是如果我需要传播事务,那么接口中的方法签名不需要将事务作为参数接收吗?如果是这样,我将违反 CA+DDD 的原则,因为我的存储库接口应该是不可知的,现在我在我的存储库方法中引用数据库。

在存储库层管理事务时,事情更简单,因为我可以有这样的接口:

type AddItem interface {
    Handle(ctx context.Context, item *domain.Item) (id uint, err error)
}

我的

AddItem
界面隐藏了我使用的是哪个数据库,实现起来就简单多了。但是,如果服务邮件失败,我无法回滚。

如果我像这样在签名中添加交易,我将在我的域层中引用数据库:

type AddItem interface {
    Handle(ctx context.Context, tx *gorm.DB, item *domain.Item) (id uint, err error)
}

那么问题来了:如何在不违背CA+DDD原则的情况下,妥善管理服务层的事务?随意使用您选择的任何编程语言编写示例(如有必要)。作为参考,我正在使用 GoLang。

architecture repository domain-driven-design repository-pattern clean-architecture
2个回答
0
投票

有不同的方法来做到这一点。有些依赖于语言特征。例如。在 Java 中,您通常会使用

ThreadLocal
来传播事务。

一般的方法是创建一个有状态的存储库。这意味着存储库将在构建时接受交易并绑定到此交易。

结果,用例也将绑定到事务,因为它使用绑定的存储库。但这意味着您不能再使用单例用例。您必须为每笔交易创建一个。

我会使用模板方法模式:

 public class TransactionAwareAddItemUseCase implements AddItemUseCase {


      public ResponseModel execute(RequestModel request){
           Transaction tx = beginTransaction();

           try {
              AddItemRepository repo = new AddItemRepository(tx);
              AddItemUseCase useCase = new AddItemUseCase(repo);
              return useCase.execute(request);
              tx.commit();
           } catch(Exception e){
              tx.rollback();
           }
      }

 }

也许你找到了一种方法让它更通用,这样你就不必为每个用例编写自己的模板方法。

但就像我之前说的,这是一个常见的解决方案,通常会有更好的解决方案,具体取决于您使用的语言。


0
投票

我过去通过抽象事务并将其隐藏在使用 saga 模式 的接口后面来完成此操作。回购可以将他们的连接/交易添加到传奇,非回购可以创建自己的回滚策略,并且所有这些都可以从服务层隐藏,服务层可以调用 saga.commit() / 回滚,这反过来会调用所有数据库和非数据库两阶段提交/回滚。

© www.soinside.com 2019 - 2024. All rights reserved.