【问题标题】:Slick 3: How to implement repository pattern with transactions?Slick 3:如何使用事务实现存储库模式?
【发布时间】:2017-01-12 16:22:48
【问题描述】:

在我的 play framework (2.5) 应用中,我需要为服务编写单元测试。

我需要隔离数据访问逻辑以便能够单独测试服务层, 为此,我想创建存储库接口并在我的单元测试中模拟它们:

class UserService {
   def signUpNewUser(username: String, memberName: String): Future[Unit] {
      val userId = 1 // Set to 1 for demo
      val user = User(userId, username)
      val member = Member(memberName, userId) 
      // ---- I NEED TO EXECUTE THIS BLOCK WITHIN TRANSACTION ----
      for {
        userResult <- userRepository.save(user)
        memberRepository.save(member)
      } yield ()     
      // ---- END OF TRANSACTION ----
   }
}

在上面的例子中,userRepository.save(User)memberRepository.save(member) 操作应该在事务中执行。

我不想直接在我的服务层中使用 slick,因为它会使我的测试复杂化。

另外,我不想在单元测试中使用嵌入式数据库,在其他地方它不是单元测试,我需要完全隔离。

我不希望我的存储库接口完全依赖于 slick,但需要这样的东西:

trait UserRepository {
   findById(id: Long): Future[Option[User]]
   save(user: User): Future[Unit] 
}

我怎样才能用 slick 做到这一点?

【问题讨论】:

    标签: scala unit-testing playframework slick


    【解决方案1】:

    好的 - 让我们将您的问题分解为三个部分。

    如何在交易中执行区块

    基本看这个答案:How to use transaction in slick

    只要将DBIO 转换为Future,就完成了。没有机会在单个事务中组合多个操作。故事结束。

    如何避免在测试中使用Slick

    这基本上是一个设计问题——如果你想在Repository/DAO/任何东西之上有一个业务层——而不是让这个服务层处理事务。您无需在该层之外与Slick 进行交互。

    避免您的存储库接口依赖于Slick

    以最直接的方式 - 您需要依赖 Slick DBIO 在事务中组合操作(并且在事务中组合 Repository 方法是在任何严肃的应用程序中都无法避免的事情)。

    如果你想避免依赖DBIO,你可能会创建自己的一元类型,比如TransactionBoundary[T]TransactionContext[T]

    然后你会有类似TransactionManager 的东西来执行这个TransactionContext[T]

    恕我直言,不值得付出努力,我会简单地使用 DBIO,它已经有了一个很好的名字(就像 Haskell 的 IO monad - DBIO 通知你,你有一个描述 IO 在你的存储上执行的操作)。但是让我们假设你仍然想避免它。

    也许你可以这样做:

    package transaction {
    
      object Transactions {
        implicit class TransactionBoundary[T](private[transaction] val dbio: DBIO[T]) {
          // ...
        }
      }
    
      class TransactionManager {
        def execute[T](boundary: TransactionBoundary[T]): Future[T] = db.run(boundary.dbio)
      }
    }
    

    你的特质应该是这样的:

    trait UserRepository {
       findById(id: Long): TransactionBoundary[Option[User]]
       save(user: User): TransactionBoundary[Unit] 
    }
    

    在你的代码中的某个地方你会这样做:

    transactionManager.execute(
        for {
            userResult <- userRepository.save(user)
            memberRepository.save(member)
        } yield ()  
    )
    

    通过使用隐式转换,您可以将Repository 中的方法结果自动转换为TransactionBoundary

    但同样 - 恕我直言,以上所有内容并没有比使用 DBIO 带来任何实际优势(除了审美品味)。如果你想避免在特定层之外使用Slick 相关类,只需像这样创建一个类型别名:

    type TransactionBoundary[T] = DBIO[T]
    

    并在任何地方使用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-03
      • 2011-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-08
      • 1970-01-01
      • 2013-05-23
      相关资源
      最近更新 更多