You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于持久层抽象的技术咨询:构建与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类型即可,业务层和领域层完全不用动
  • 事务管理:如果需要统一事务管理,可以再抽象一个TransactionManager trait,封装不同框架的事务提交/回滚逻辑

内容的提问来源于stack exchange,提问作者David Geirola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:42:51