关于持久层抽象的技术咨询:构建与Slick松耦合的架构
构建与Slick解耦的Scala持久层抽象方案
我来分享一套完全可行的解耦方案,帮你实现实体与Slick、slick-repo彻底隔离的持久层架构,结合你的技术栈(Scala 2.12.4 + Play 2.6 + play-slick 3.0.3 + slick-repo 1.4.3)拆解步骤:
第一步:定义纯领域实体接口(完全无持久化依赖)
首先要把领域实体从任何持久化框架中抽离出来,只保留业务相关的属性和行为。我们先定义一个通用的Entity trait,作为所有实体的基础:
// 纯领域层代码,无任何Slick/slick-repo依赖 trait Entity[E, ID] { def id: Option[ID] def withId(id: ID): E // 用于生成带ID的实体副本(比如插入后返回自增ID) }
然后具体的业务实体(比如User)只需要实现这个trait,完全干净:
case class User(id: Option[Long], name: String, email: String) extends Entity[User, Long] { override def withId(id: Long): User = this.copy(id = Some(id)) }
第二步:抽象持久层核心接口(与具体框架解耦)
接下来定义通用的IRepository接口,这里关键是用类型成员抽象事务类型,不绑定Slick的DBIO,让后续可以灵活切换持久化框架:
// 持久层抽象接口,业务层仅依赖这个接口 trait IRepository[E <: Entity[E, ID], ID] { type Transaction[T] // 抽象事务类型,由具体实现定义 // 通用CRUD方法 def findById(id: ID): Transaction[Option[E]] def save(entity: E): Transaction[E] def update(entity: E): Transaction[Option[E]] def delete(id: ID): Transaction[Boolean] }
第三步:实现Slick适配层(仅这里依赖Slick/slick-repo)
这一层是唯一依赖Slick和slick-repo的部分,我们通过适配器模式把抽象接口和具体框架绑定,隔离领域层与持久化框架:
首先实现通用的Slick仓库适配器:
// Slick适配层,仅此处引入Slick/slick-repo依赖 import com.byteslounge.slickrepo.repository.Repository import com.byteslounge.slickrepo.scalaversion.JdbcProfile import slick.dbio.DBIO import slick.lifted.O class SlickRepositoryAdapter[E <: Entity[E, ID], ID]( override val profile: JdbcProfile, val tableQuery: profile.api.TableQuery[_ <: profile.api.Table[E]] ) extends Repository[E, ID](profile) with IRepository[E, ID] { // 绑定事务类型为Slick的DBIO override type Transaction[T] = DBIO[T] override def findById(id: ID): Transaction[Option[E]] = { tableQuery.filter(_.id === id).result.headOption } override def save(entity: E): Transaction[E] = { entity.id match { case None => (tableQuery returning tableQuery) += entity // 插入自增ID case Some(_) => update(entity).map(_.getOrElse(entity)) // 已有ID则更新 } } override def update(entity: E): Transaction[Option[E]] = { entity.id match { case Some(id) => tableQuery.filter(_.id === id).update(entity).map { case 0 => None // 无匹配记录 case _ => Some(entity) // 更新成功返回实体 } case None => DBIO.successful(None) // 无ID无法更新 } } override def delete(id: ID): Transaction[Boolean] = { tableQuery.filter(_.id === id).delete.map(_ > 0) // 返回是否删除成功 } // 实现slick-repo要求的抽象方法 override def persist(entity: E): DBIO[E] = save(entity) override def update(entity: E, id: ID): DBIO[E] = update(entity).map(_.get) override def delete(id: ID): DBIO[Unit] = tableQuery.filter(_.id === id).delete }
然后针对具体实体(比如User)定义Slick表结构,这也属于适配层:
// Slick表定义,仅属于适配层 import com.byteslounge.slickrepo.scalaversion.JdbcProfile import slick.lifted.ProvenShape class UserTable(tag: slick.lifted.Tag)(implicit profile: JdbcProfile) extends profile.api.Table[User](tag, "users") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def email = column[String]("email") override def * : ProvenShape[User] = (id.?, name, email) <> (User.tupled, User.unapply) }
第四步:业务层依赖抽象接口(完全隔离Slick)
业务服务层只需要依赖抽象的IRepository,完全不知道底层用的是Slick还是其他持久化框架:
// 业务服务层,仅依赖IRepository抽象 class UserService(repo: IRepository[User, Long]) { def getUserById(id: Long): repo.Transaction[Option[User]] = repo.findById(id) def createUser(name: String, email: String): repo.Transaction[User] = { repo.save(User(None, name, email)) } def updateUser(user: User): repo.Transaction[Option[User]] = repo.update(user) def deleteUser(id: Long): repo.Transaction[Boolean] = repo.delete(id) }
第五步:依赖注入绑定(Play框架中)
在Play应用的模块配置中,把抽象的IRepository绑定到具体的Slick实现,完成依赖注入:
// Play Guice模块配置 import com.google.inject.AbstractModule import play.api.db.slick.DatabaseConfigProvider import slick.jdbc.JdbcProfile import javax.inject.Provider class RepositoryModule extends AbstractModule { override def configure(): Unit = { bind(classOf[IRepository[User, Long]]).toProvider(classOf[SlickUserRepositoryProvider]) } } class SlickUserRepositoryProvider @Inject()(dbConfigProvider: DatabaseConfigProvider) extends Provider[IRepository[User, Long]] { private val dbConfig = dbConfigProvider.get[JdbcProfile] import dbConfig.profile.api._ val userTable = TableQuery[UserTable] override def get(): IRepository[User, Long] = new SlickRepositoryAdapter[User, Long](dbConfig.profile, userTable) }
关键注意点
- 彻底隔离:领域实体完全没有Slick的注解、依赖,只专注业务逻辑
- 灵活切换:如果以后要替换Slick(比如换成Doobie),只需要实现对应的
DoobieRepositoryAdapter,修改Transaction类型即可,业务层和领域层完全不用动 - 事务管理:如果需要统一事务管理,可以再抽象一个
TransactionManagertrait,封装不同框架的事务提交/回滚逻辑
内容的提问来源于stack exchange,提问作者David Geirola
相关产品推荐
相关产品推荐

