Call it DI or not, the Cake pattern is one of the helpful techniques to implement modular abstractions in Scala. You weave your abstract components (aka traits), layering on the dependencies and commit to implementations only at the end of the world. I was trying to come up with an implementation that does not use self type annotations. It's not that I think self type annotations are kludgy or anything but I don't find them used elsewhere much besides the Cake pattern. And of course mutually recursive self annotations are a code smell that makes your system anti-modular.
In the following implementation I use path dependent types, which have become a regular feature in Scala 2.10. Incidentally it was there since long back under the blessings of an experimental feature, but has come out in public only in 2.10. The consequence is that instead of self type annotations or inheritance I will be configuring my dependencies using composition.
Let me start with some basic abstractions of a very simple domain model. The core component that I will build is a service that reports the portfolio of clients as a balance. The example has been simplified for illustration purposes - the actual real life model has a much more complex implementation.
A Portfolio is a collection of Balances. A Balance is a position of an Account in a specific Currency as on a particular Date. Expressing this in simple terms, we have the following traits ..
// currency sealed trait Currency case object USD extends Currency case object EUR extends Currency case object AUD extends Currency //account case class Account(no: String, name: String, openedOn: Date, status: String) trait BalanceComponent { type Balance def balance(amount: Double, currency: Currency, asOf: Date): Balance def inBaseCurrency(b: Balance): Balance }
The interesting point to note is that the actual type of
Balance
has been abstracted in BalanceComponent
, since various services may choose to use various representations of a Balance. And this is one of the layers of the Cake that we will mix finally ..Just a note for the uninitiated, a base currency is typically considered the domestic currency or accounting currency. For accounting purposes, a firm may use the base currency to represent all profits and losses. So we may have some service or component that would like to have the balances reported in base currency.
trait Portfolio { val bal: BalanceComponent import bal._ def currentPortfolio(account: Account): List[Balance] }
Portfolio uses the abstract
BalanceComponent
and does not commit to any specific implementation. And the Balance
in the return type of the method currentPortfolio
is actually a path dependent type, made to look nice through the object import syntax.Now let's have some standalone implementations of the above components .. we are still not there yet to mix the cake ..
// report balance as a TUPLE3 - simple trait SimpleBalanceComponent extends BalanceComponent { type Balance = (Double, Currency, Date) override def balance(amount: Double, currency: Currency, asOf: Date) = (amount, currency, asOf) override def inBaseCurrency(b: Balance) = ((b._1) * baseCurrencyFactor.get(b._2).get, baseCurrency, b._3) } // report balance as an ADT trait CustomBalanceComponent extends BalanceComponent { type Balance = BalanceRep // balance representation case class BalanceRep(amount: Double, currency: Currency, asOf: Date) override def balance(amount: Double, currency: Currency, asOf: Date) = BalanceRep(amount, currency, asOf) override def inBaseCurrency(b: Balance) = BalanceRep((b.amount) * baseCurrencyFactor.get(b.currency).get, baseCurrency, b.asOf) }
And a sample implementation of
ClientPortfolio
that adds logic without yet commiting to any concrete type for the BalanceComponent
.trait ClientPortfolio extends Portfolio { val bal: BalanceComponent import bal._ override def currentPortfolio(account: Account) = { //.. actual impl will fetch from database List( balance(1000, EUR, Calendar.getInstance.getTime), balance(1500, AUD, Calendar.getInstance.getTime) ) } }
Similar to
ClientPortfolio
, we can have multiple implementations of Portfolio reporting that reports balances in various forms. So our cake has started taking shape. We have the Portfolio
component and the BalanceComponent already weaved in without any implementation. Let's add yet another layer to the mix, maybe for fun - a decorator for the Portfolio
.We add
Auditing
as a component which can decorate *any* Portfolio
component and report the balance of an account in base currency. Note that Auditing
needs to abstract implementations of BalanceComponent
as well as Portfolio
since the idea is to decorate any Portfolio
component using any of the underlying BalanceComponent
implementations.Many cake implementations use self type annotations (or inheritance) for this. I will be using composition and path dependent types.
trait Auditing extends Portfolio { val semantics: Portfolio val bal: semantics.bal.type import bal._ override def currentPortfolio(account: Account) = { semantics.currentPortfolio(account) map inBaseCurrency } }
Note how the
Auditing
component uses the same Balance
implementation as the underlying decorated Portfolio
component, enforced through path dependent types.And we have reached the end of the world without yet committing to any implementation of our components .. But now let's do that and get a concrete service instantiated ..
object SimpleBalanceComponent extends SimpleBalanceComponent object CustomBalanceComponent extends CustomBalanceComponent object ClientPortfolioAuditService1 extends Auditing { val semantics = new ClientPortfolio { val bal = SimpleBalanceComponent } val bal: semantics.bal.type = semantics.bal } object ClientPortfolioAuditService2 extends Auditing { val semantics = new ClientPortfolio { val bal = CustomBalanceComponent } val bal: semantics.bal.type = semantics.bal }
Try out in your Repl and see how the two services behave the same way abstracting away all implementations of components from the user ..
scala> ClientPortfolioAuditService1.currentPortfolio(Account("100", "dg", java.util.Calendar.getInstance.getTime, "a")) res0: List[(Double, com.redis.cake.Currency, java.util.Date)] = List((1300.0,USD,Thu Jan 31 12:58:35 IST 2013), (1800.0,USD,Thu Jan 31 12:58:35 IST 2013)) scala> ClientPortfolioAuditService2.currentPortfolio(Account("100", "dg", java.util.Calendar.getInstance.getTime, "a")) res1: List[com.redis.cake.ClientPortfolioAuditService2.bal.Balance] = List(BalanceRep(1300.0,USD,Thu Jan 31 12:58:46 IST 2013), BalanceRep(1800.0,USD,Thu Jan 31 12:58:46 IST 2013))
The technique discussed above is inspired from the paper Polymoprhic Embedding of DSLs. I have been using this technique for quite some time and I have discussed a somewhat similar implementation in my book DSLs In Action while discussing internal DSL design in Scala.
And in case you are interested in the full code, I have uploaded it on my Github.