Slick 3:如何使用事务实现存储库模式?
Slick 3: How to implement repository pattern with transactions?
在我的 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,因为它会使我的测试复杂化。
此外,我不想在我的单元测试中使用嵌入式数据库,在其他地方这将是一个 NOT 单元测试,我需要完全隔离。
我根本不希望我的存储库接口依赖于 slick,但需要这样的东西:
trait UserRepository {
findById(id: Long): Future[Option[User]]
save(user: User): Future[Unit]
}
如何使用 slick 实现此目的?
好的 - 让我们将您的问题分解为三个部分。
如何在交易中执行区块
基本上看了这个回答:
一旦将 DBIO
转换为 Future
,您就完成了。没有机会在单个事务中组合多个操作。故事结束。
如何避免在测试中使用Slick
这基本上是一个设计问题 - 如果您想在 Repository
/ DAO
/ 任何东西之上有一个业务层 - 而不是让这个服务层处理事务。您不需要与该层外的 Slick
交互。
避免您的存储库接口依赖于 Slick
以最直接的方式 - 您需要依靠 Slick DBIO
来组合事务中的操作(并且在事务中组合 Repository
方法是在任何严肃的应用程序中都无法避免的事情)。
如果你想避免依赖于 DBIO
你可能会创建你自己的 monadic 类型,比如 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]
并在任何地方使用它。
在我的 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,因为它会使我的测试复杂化。
此外,我不想在我的单元测试中使用嵌入式数据库,在其他地方这将是一个 NOT 单元测试,我需要完全隔离。
我根本不希望我的存储库接口依赖于 slick,但需要这样的东西:
trait UserRepository {
findById(id: Long): Future[Option[User]]
save(user: User): Future[Unit]
}
如何使用 slick 实现此目的?
好的 - 让我们将您的问题分解为三个部分。
如何在交易中执行区块
基本上看了这个回答:
一旦将 DBIO
转换为 Future
,您就完成了。没有机会在单个事务中组合多个操作。故事结束。
如何避免在测试中使用Slick
这基本上是一个设计问题 - 如果您想在 Repository
/ DAO
/ 任何东西之上有一个业务层 - 而不是让这个服务层处理事务。您不需要与该层外的 Slick
交互。
避免您的存储库接口依赖于 Slick
以最直接的方式 - 您需要依靠 Slick DBIO
来组合事务中的操作(并且在事务中组合 Repository
方法是在任何严肃的应用程序中都无法避免的事情)。
如果你想避免依赖于 DBIO
你可能会创建你自己的 monadic 类型,比如 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]
并在任何地方使用它。