Slick 3:如何使用事务实现存储库模式?

时间:2017-01-12 16:22:49

标签: scala unit-testing playframework slick

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

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

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)操作。

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

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

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

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

如何通过光滑实现这一目标?

1 个答案:

答案 0 :(得分:4)

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

如何在交易中执行阻止

基本上阅读这个答案:How to use transaction in slick

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

如何避免在测试中使用Slick

这基本上是一个设计问题 - 如果您希望在Repository / DAO /之上设置业务层,而不是让此服务层处理事务。您不需要在此图层之外与Slick进行互动。

避免您的存储库界面依赖于Slick

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

如果您想避免依赖DBIO,您可能会创建自己的monadic类型,例如TransactionBoundary[T]TransactionContext[T]

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

恕我直言不值得努力,我只是简单地使用DBIO已经有一个很棒的名字(比如Haskell&#39; 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]

并在任何地方使用它。