1
votes


I have a question about persistence layer abstraction.

The goal

Create a structure not streactly Slick related, therefore Entities must be disjointed from Slick and Slick-repo plug-in.


Current situation

Environment

  • Scala % 2.12.4
  • com.typesafe.play % sbt-plugin % 2.6.12
  • com.typesafe.play % play-slick % 3.0.3
  • com.byteslounge % Slick-repo % 1.4.3

At the moment i have created abstraction with the following traits:

IRepository:

trait IRepository[E <: Entity[E, ID], ID] {

  type Transaction[T]

  val transactionManager: TransactionManager[Transaction]
  final type EntityType = E

  def findAll()(implicit ec: ExecutionContext): Transaction[Seq[E]]
  def findOne(id : ID)(implicit ec: ExecutionContext): Transaction[Option[E]]
  def lock(entity: E)(implicit ec: ExecutionContext): Transaction[E]
  def save(entity: E)(implicit ec: ExecutionContext): Transaction[E]
  def batchInsert(entities: Seq[E]): Transaction[Option[Int]]
  def update(entity: E)(implicit ec: ExecutionContext): Transaction[E]
  def delete(entity: E)(implicit ec: ExecutionContext): Transaction[E]
  def count(): Transaction[Int]
  def exclusiveLockStatement(sql: String): String
}

TransactionManager:

trait TransactionManager[T[_]] {

  final type Transaction[Entity] = T[Entity]

  implicit def executeAll[Entity](op: Seq[T[Entity]]): Future[Seq[Entity]]

  implicit def execute[Entity](op: T[Entity]): Future[Entity]
}

Entity:

trait Entity[T <: Entity[T, ID], ID] {

  final type IdType = ID

  val id: Option[ID]

  def withId(id: ID): T
}

In the services i'm using implicit conversion scala feature to execute Db actions and obtains Future instances

This is a simple example:

class UserServiceImpl @Inject()(private val repo: UserRepository)
                               (implicit ec: ExecutionContext) extends UserService {

  import repo.transactionManager._

  override def retrieve(id: Long): Future[Option[User]] =
    repo.findOne(id)

  override def save(user: User): Future[User] =
    repo.save(user)

  override def retrieve(loginInfo: LoginInfo): Future[Option[User]] =
    repo.findByLoginInfo(loginInfo)

  override def delete(user: User): Future[User] =
    repo.delete(user)
}

Problem

All these abstractions needs implementations and here the problems begin.

I think i have to give up on repositories abstraction because obliviously to use slick BDIO methods i need to have concrete access to that and this tie my code to Slick but i think it's ok.

Entities are not related to slick with my trait(Entity) but the troubles are on integration with repos.

My slick repos extends Repository class from Slick-repo plug-in, these class(like my IRepository trait) require two generic type, the first must extend a trait named Entity(Slick-repo's trait) while the second have no constraints and represent the id type of the entity.

In summary:

My classes

  • IRepository[Entity, ID]
  • Entity[Entity, ID]
  • SlickRepository[Entity, ID] extends IRepository

Slick-repo classes

  • Repository[Entity, ID]
  • Entity[Entity, ID]

SlickRepository extends/should use Repository(Slick-repo) but that class want that the first generic parameter exends Entity(Slick-repo) while in my case, for separation, it's extends Entity trait(my domain trait)

i hope it's clear, i know that the situation is a litle complex especially because some class have the same name

Maybe my concept of this problem is totally wrong i'don't know i hope someone can help me.

Thanks to all in advices.

1

1 Answers

1
votes

You can check Slick's Persistent Layer abstraction example here: https://github.com/gonmarques/slick-repo.

This repo provides common features like:

  • Provide common database operations like save, update, find, delete or count in a type-safe way

  • Other operations like Transactions, Batch Insert, Optimistic Locking (aka versioning),

  • Pessimistic Locking or custom query/statement execution is also supported

  • In order to maximize performance, all provided operations are backed by Slick compiled queries, as recommended in Slick Documentation