I am reading Functional And Reactive Domain Modeling by Debasish Ghosh and i want to refactor a CRUD application that it's actually on production. I'm focusing on a first approach with Reader monad for DI, Repository pattern and ADT's for an app that manages equivalences values between applications (think of a value, understood in a certain way by some application and then query it's equivalent value for another system).
Debasish refers to repository pattern as a way for decoupling, so, in my case i need concrete implementations for Oracle and Postgresql and H2 for testing. I have the following and very straightforward implementation (based on the book):
Base trait
for repositories:
trait Repository[A, Id] {
def query(id: Id): Try[Option[A]]
def insert(a: A): Try[A]
def update(a: A): Try[A]
def delete(a: A): Try[A]
}
Module for Equivalence Repository:
trait EquivalenceRepository extends Repository[Equivalence, Long]{
def query(id: Long): Try[Option[Equivalence]]
def insert(a: Equivalence): Try[Equivalence]
def update(a: Equivalence): Try[Equivalence]
def delete(a: Equivalence): Try[Equivalence]
}
And the mid-way concrete implementation with Slick:
class EquivalenceOracleRepository extends EquivalenceRepository {
def query(id: Long): Try[Option[Equivalence]] = {
???
}
def insert(a: Equivalence): Try[Equivalence] = {
???
}
def update(a: Equivalence): Try[Equivalence] = {
???
}
def delete(a: Equivalence): Try[Equivalence] = {
???
}
}
private[repository] trait EquivalenceOracleDB{
this: DBComponent =>
import jdbcProfile.api._
final case class EquivalenceDTO(
originId: Int,
equivalenceId: Int,
creator: String,
creationDate: Timestamp,
isActive: Boolean
)
final class EquivalenceTable(tag: Tag) extends Table[Equivalence](tag, "Equivalence"){
def originId: Rep[Int] = column[Int]("ORIGIN_ID", O.SqlType("NUMBER(10)"))
def equivalenceId: Rep[Int] = column[Int]("EQUIVALENCE_ID", O.SqlType("NUMBER(10)"))
def creator: Rep[String] = column[String]("CREATOR", O.SqlType("NUMBER(10)"))
def creationDate: Rep[Timestamp] = column[Timestamp]("CREATION_DATE", O.SqlType("TIMESTAMP(6)"))
def isActive: Rep[Boolean] = column[Boolean]("IS_ACTIVE", O.SqlType("VARCHAR2(1)"))
def pk: PrimaryKey = primaryKey("EQUIVALENCES_PK", (originId, equivalenceId))
def * : ProvenShape[EquivalenceDTO] =
(originId, equivalenceId, creator, creationDate, isActive) <> (EquivalenceDTO.tupled, EquivalenceDTO.unapply)
}
val table = TableQuery[EquivalenceTable]
}
Being the last one a concrete implementation for Oracle, you can see that the trait is expecting a DBComponent. This is a trait whose code it's inherited from the actual production application and tries to define the concrete Slick profiles for every DBMS:
This is the profiling for every DBMS:
trait Profile {
val jdbcProfile: JdbcProfile
}
object OracleProfile extends Profile {
override val jdbcProfile: JdbcProfile = OracleDriver
}
object H2Profile extends Profile {
override val jdbcProfile: JdbcProfile = H2Driver
}
object PostgreSQLProfile extends Profile {
override val jdbcProfile: JdbcProfile = PostgreSQLProfile.jdbcProfile
}
And this is the Database definition:
trait DBComponent {
val jdbcProfile: JdbcProfile
import jdbcProfile.api._
val db: Database
}
trait OracleDB extends DBComponent {
val logger: Logger = LoggerFactory.getLogger(this.getClass)
val jdbcProfile: JdbcProfile = OracleProfile.jdbcProfile
}
trait H2DB extends DBComponent {
val logger: Logger = LoggerFactory.getLogger(this.getClass)
val jdbcProfile: JdbcProfile = H2Profile.jdbcProfile
}
trait PostgreSQLDB extends DBComponent {
val logger: Logger = LoggerFactory.getLogger(this.getClass)
val jdbcProfile: JdbcProfile = PostgreSQLProfile.jdbcProfile
}
But my doubts came here: if i try to mixin the EquivalenceOracleDB
trait that contains the Slick basics into EquivalenceOracleRepository
also i need to mixin the component and actually i'm getting an error:
Mixin:
class EquivalenceOracleRepository extends EquivalenceRepository with EquivalenceOracleDB{
And the error: Illegal inheritance, self-type EquivalenceOracleRepository does not conform to DBComponent
because of the mismatch of the interfaces. So, i need some light:
I have seen the Multi-DB example that Lightbend has, but, apart of unsolved dependency problems it relies heavily on the Cake Pattern, which is very verbose. I'm trying to stick to the book.
Any help will be appreciated.
Thanks
The reason that you're getting such error is that you're not mixing OracleDB
. You should replace:
class EquivalenceOracleRepository extends EquivalenceRepository with EquivalenceOracleDB
with
class EquivalenceOracleRepository extends EquivalenceRepository with EquivalenceOracleDB with OracleDB
Now you'll get an error that db
is not defined, so you should provide its implementation somewhere (because it's defined as abstract in DBComponent
). I think this could be done in OracleDB
, which will make that trait concrete and not abstract as it is now. I think this way you'll be more or less compliant with the design presented in the book (you have an abstract interface describing your Repository, and multiple concrete/production implementations).