光滑3:如何通过事务实现存储库模式? [英] Slick 3: How to implement repository pattern with transactions?

查看:87
本文介绍了光滑3:如何通过事务实现存储库模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In my play framework (2.5) app, I need to write unit tests for services.

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

I need to isolate data access logic to able to test service layer in isolation, for this I want to create repository interfaces and MOCK them in my unit tests:

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

In the above example, userRepository.save(User) and memberRepository.save(member) operations should be performed within transaction.

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

I don't want to use slick directly in my service layer because it will complicate my tests.

此外,我不想在单元测试中使用嵌入式数据库,在其他情况下这不是NOT单元测试,我需要完全隔离.

Also, I don't want to use embedded database for my unit tests, elsewhere it would be a NOT unit test, I need complete isolation.

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

I do not want my repository interfaces to be depended on slick at all, but need something like this:

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

如何用光滑的纸巾来做到这一点?

how can I achieve this with slick?

推荐答案

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

OK - let's decompose your question into three parts.

如何在交易中执行冻结

基本阅读此答案:如何在光滑的环境中使用交易

DBIO转换为Future后,就可以完成.没有机会在单笔交易中进行多项操作.故事结束.

As soon as you convert DBIO to Future you are done. No chances to compose multiple operation within single transaction. End of story.

如何避免在测试中使用Slick

How to avoid using Slick in tests

从根本上说,这是一个设计问题-如果您想在Repository/DAO/其他任何东西上都有一个业务层-而不是让该服务层处理事务.您无需在此层之外与Slick进行交互.

This is basically a design question - if you want to have a business layer on top of Repository / DAO / whatever - than let this service layer deal with transactions. You won't need to interact with Slick outside of this layer.

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

Avoiding your repository interfaces to depend on Slick

以最直接的方式-您需要依靠Slick DBIO在事务内编写操作(在事务内编写Repository方法在任何严肃的应用程序中都是无法避免的).

In most straightforward way - you need to depend on Slick DBIO to compose operations within transaction (and composing Repository methods within transaction is something that you cannot avoid in any serious application).

如果要避免依赖DBIO,则可以创建自己的单子类型,例如TransactionBoundary[T]TransactionContext[T].

If you want to avoid depending on DBIO you would perhaps create you own monadic type, say TransactionBoundary[T] or TransactionContext[T].

然后您将执行TransactionContext[T]之类的与TransactionManager类似的东西.

Then you would have something like TransactionManager that would execute this TransactionContext[T].

恕我直言,我不值得付出努力,我只使用已经具有好名字的DBIO(例如Haskell的IO monad-DBIO会通知您,您已经对在存储上执行的IO操作进行了说明).但让我们假设您仍然想要避免这种情况.

IMHO not worth the effort, I'd simply use DBIO which has a brilliant name already (like Haskell's IO monad - DBIO informs you that you have a description of IO operations performed on your storage). But let's assume that you still want to avoid it.

您也许可以做类似的事情:

You could do something like that perhaps:

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)
  }
}

您的特质看起来像这样:

Your trait would look like this:

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

在代码中的某个位置,您会这样:

and somewhere in your code you would do like this:

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

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

By using implicit conversion you would have your results of methods in Repository be automatically converted to your TransactionBoundary.

但是,再次强调-恕我直言,以上所有内容都没有比使用DBIO带来任何实际的优势(也许是审美的味道).如果要避免在特定层之外使用Slick相关类,只需创建一个类型别名:

But again - IMHO all of the above doesn't bring any actual advantage over using DBIO (except perhaps taste of esthetics). If you want to avoid using Slick related classes outside of certain layer, just make a type alias like this:

type TransactionBoundary[T] = DBIO[T]

并在任何地方使用它.

and use it everywhere.

这篇关于光滑3:如何通过事务实现存储库模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆