Scala Cake Pattern: Splitting large components into separate files
Asked Answered
F

1

6

I'd like to use the Cake Pattern for splitting parts of some software system into components to make it completely modular as proposed in this article. In the simplest case I'd like to have some mockable components, let's say Logging, Config, Database, Scripts etc which could potentially use each other. The code might look like

trait AbstractConfig {
  def config: AbstractConfigInterface
  trait AbstractConfigInterface {
    def test: Unit
  }
}

trait SomeConfig extends AbstractConfig {
  this: Core =>  
  def config = SomeConfigImplementation
  object SomeConfigImplementation extends AbstractConfigInterface {
    def test = println("conf.test method called")
  }
}

trait AbstractDatabase {
  def database: AbstractDatabaseInterface
  trait AbstractDatabaseInterface {
    def connect: Unit
  }
}

trait SomeDatabase extends AbstractDatabase {
  this: Core =>
  def database = SomeDatabaseImplementation
  object SomeDatabaseImplementation extends AbstractDatabaseInterface {
    def connect = {
      println("connect method called")
      core.conf.test
    }
  }
}

trait Core {
  this: AbstractDatabase with AbstractConfig =>
  def core = CoreInterface
  object CoreInterface {
    def db = database
    def conf = config
  }
}

object app extends Core with SomeDatabase with SomeConfig

object Run {
  def main(args: Array[String]) = {
    app.core.db.connect
  }
}

Here the database and config components (SomeConfig and SomeDatabase traits) are pluggable and can be changed to some other implementations if ever needed. Their implementations have access to core object which holds both database and config, so the database can access config if needed and vice versa.

So the question is: If ever some trait like SomeDatabase becomes large and doesn't fit into a single file how to split it into separate classes retaining access to the core object? To be more specific, let's say I need to move some code out of connect method in SomeDatabase to another file:

// SomeDatabase.scala
trait SomeDatabase extends AbstractDatabase {
  this: Core =>
  def database = SomeDatabaseImplementation
  object SomeDatabaseImplementation extends AbstractDatabaseInterface {
    def connect = {
      val obj = new SomeClass()
    }
  }
}

// SomeClass.scala in the same package
class SomeClass {
    core.conf.test // Does not compile - how to make it work??
}

SomeClass is implementation details of how SomeDatabase works, so I obviously wouldn't like to make it a trait and mix it in to application. Is there any way to provide access to core object for SomeClass?


Some related links:

  1. Dependency Injection vs Cake Pattern by Jan Machacek
  2. Real World Scala: Dependency Injection by Jonas Boner
  3. Dependency Injection in Scala: Extending the Cake Pattern by Adam Warsky
  4. Scalable Component Abstractions by Martin Odersky & Matthias Zenger
Farnsworth answered 20/4, 2012 at 18:51 Comment(0)
B
2

The simplest thing to do would be to pass Core in as a constructor parameter to SomeClass.

// SomeDatabase.scala
trait SomeDatabase extends AbstractDatabase {
  this: Core =>
  def database = SomeDatabaseImplementation
  object SomeDatabaseImplementation extends AbstractDatabaseInterface {
    def connect = {
      val obj = new SomeClass(SomeDatabase.this) // pass it here
    }
  }
}

// SomeClass.scala in the same package
class SomeClass(coreComp: Core) { // use it here
    coreComp.core.conf.test
}

Interestingly, I really just wanted to pass CoreInterface or AbstractConfigInterface, but the fact that they are inner types really made that difficult.

Basildon answered 20/4, 2012 at 20:11 Comment(2)
Dave, thanks for the answer. So far it seems to be the only reasonable way. The only thing I don't dig about it is the necessity to type coreComp. in each invocation of any core method. Unfortunately there doesn't seem to be an option to work with CoreInterface directly, does it?Farnsworth
You can import coreComp._ to reduce the typing.Basildon

© 2022 - 2024 — McMap. All rights reserved.