Killyz
Killyz

Reputation: 109

Scala Cake Pattern & Self Type Annotations

I'm trying to follow the example from this blog. I understand the example but having trouble implementing it.

trait Database {
  // ...
}

trait UserDb {
  this: Database =>
    // ...
}

trait EmailService {
  this: UserDb =>
    // Can only access UserDb methods, cannot touch Database methods
}

The example mentions that the full Database functionality will be hidden from the EmailService - this is what i'm after but don't know how to implement these traits correctly.

This is what i tried to implement:

trait Database {
    def find(query: String): String
  }

  trait UserDb {
    this: Database =>
  }

  trait EmailService {
    this: UserDb =>
  }

  trait MongoDatabase extends Database {

  }

  trait MongoUserDb extends UserDb with MongoDatabase{

  }

  class EmailServiceImpl extends EmailService with MongoUserDb {
    override def find(query: String): String = {
      "result"
    }
  }

It looks weird to me becasue MongoDatabase trait didn't asked for find implementation and when i implemented EmailService i was then prompted for find implementation,although the example mentioned this will be hidden from the EmailService. What am i missing here?

After reading your comments, I'm trying to implement what i'm being trying to understand on an example that is closer to what i'm actually trying to do.

The first snippet won't compile, but the second one will... At the end of the day i want to have different Repository implementations where i can switch between the Databases they rely on, am i close with one of the snippets below?

trait Database {
    def find(s: String): String
  }

  trait Repository {
    this: Database =>
  }

  class UserRepository extends Repository {
    def database = new MongoDB

    class MongoDB extends Database {
      def find(s: String): String = {
        "res"
      }
    }
  }


trait Repository {
    def database: Database

    trait Database {
      def find(s: String): String
    }
  }

  trait UserRepository extends Repository {
    def database = new MongoDB

    class MongoDB extends Database {
      def find(s: String): String = {
        "res"
      }
    }
  }

Upvotes: 2

Views: 257

Answers (2)

Som Bhattacharyya
Som Bhattacharyya

Reputation: 4112

As mentioned MongoUserDB will not ask for an implementation as its a trait. However since EmailServiceImpl extends the trait it needs to provide an implementation. What you are looking for could be done by adding another abstraction. I do it using the service and DAO architecture. Below is a working example that you may use to see if it suits you.

//All future versions of DAO will extend this
trait AbstractDAO{
  def getRecords:String
  def updateRecords(records:String):Unit
}
//One concrete version
trait concreteDAO extends AbstractDAO{
  override def getRecords={"Here are DB records"}
  override def updateRecords(records:String){
    //Actual DB calls and operations
    println("Updated "+records)
  }
}
//Second concrete version
trait concreteDAO1 extends AbstractDAO{
  override def getRecords={"DB Records returned from DAO2"}
  override def updateRecords(records:String){
    //Actual DB calls and operations
    println("Updated via DAO2"+records)
  }
}
//This trait just defines dependencies (in this case an instance of AbstractDAO) and defines operations based over that
trait service{
  this:AbstractDAO =>

  def updateRecordsViaDAO(record:String)={  
  updateRecords(record) 
  }
  def getRecordsViaDAO={
  getRecords
  }
}

//Test Stub
object DI extends App{
  val wiredObject = new service with concreteDAO //injecting concrete DAO to the service and calling methods
  wiredObject.updateRecords("RECORD1")
  println(wiredObject.getRecords)

  val wiredObject1 = new service with concreteDAO1
  wiredObject1.updateRecords("RECORD2")
  println(wiredObject1.getRecords)

}

EDIT ---

Here is the code you might want to implement,

    trait Database {
    def find(s: String): String
  }

trait MongoDB extends Database{
  def find(s:String):String = { "Test String" }
}
trait SQLServerDB extends Database{
  def find(s:String):String = { "Test String2" }
}

  trait Repository {
    this: Database =>
  }

  class UserRepository extends Repository with MongoDB{  //  UserRepository is injected with MongoDB here
    find("call MongoDB") //This call will go to the find method in MongoDB trait
  }

  class UserRepository1 extends Repository with SQLServerDB{  //  UserRepository is injected with SQLServerDB here
    find("call SQLServerDB") //This call will go to the find method in SQLServerDB trait
  }

Upvotes: 1

Dima
Dima

Reputation: 40510

Database is hidden from EnailService, but not from EmailServiceImpl. The latter is a subclass of MongoUserDB, obviously, it has access to it. MongoUserDB does not "ask" for find implementation, because it is a trait, and traits can have abstract methods. You should still implement it there, even without being asked ;)

Upvotes: 1

Related Questions