Dependency Injection
Beyond the Cake Pattern
Debasish Ghosh
@debasishg on twitter
Code @ http://github.com/debasishg
Blog @ Ruminations of a Programmerhttp://debasishg.blogspot.com
Open Source Footprints
• Committer in Akka (http://akka.io)
• Owner/Founder of :– sjson (JSON serialization library for Scala
objects – http://github.com/debasishg/sjson)
– scala-redis (Redis client for Scala – http://github.com/debasishg/scala-redis)
– scouchdb (Scala driver for Couchdb – http://github.com/debasishg/scouchdb)
import TradeModel._
// a Repository abstractiontrait TradeRepository { def fetch(refNo: String): Trade def update(trade: Trade): Trade}
// a Trading servicetrait TradeService {
// fetches a trade based on the reference no val fetchTrade: TradeRepository => String => Trade = {repo => refNo => repo.fetch(refNo)}
// updates a trade with the given values val updateTrade: TradeRepository => Trade => Trade = {repo => trade => repo.update(trade)}
}
// a Trading servicetrait TradeService {
// fetches a trade based on the reference no val fetchTrade: TradeRepository => String => Trade = {repo => refNo => repo.fetch(refNo)}
// updates a trade with the given values val updateTrade: TradeRepository => Trade => Trade = {repo => trade => repo.update(trade)}
} Repository is still abstract
suppose we would like to use a Redis basedRepository ..
class RedisTradeRepository extends TradeRepository { def fetch(refNo: String): Trade = //.. Redis based implementation
def update(trade: Trade): Trade = //.. Redis based implementation}
need to indicate that to the service class ..
define partial application of the service methods using the Redis based repository implementation in a separate module ..
object TradeServiceWithRedisRepo extends TradeService {
// partially applied functions val fetchTrade_c = fetchTrade(new RedisTradeRepository)
val updateTrade_c = updateTrade(new RedisTradeRepository)}
define partial application of the service methods using the Redis based repository implementation in a separate module ..
object TradeServiceWithRedisRepo extends TradeService {
// partially applied functions val fetchTrade_c = fetchTrade(new RedisTradeRepository)
val updateTrade_c = updateTrade(new RedisTradeRepository)}
Concrete implementation injected
val fetchTrade: TradeRepository => String => Trade
val fetchTrade_c: String => Trade
import TradeServiceWithRedisRepo._val t = fetchTrade_c("ref-123")
by using the appropriate module, we can switch Repositoryimplementations ..
instead of currying individual functions, we can curry
a composed function ..
// warning: needs scalaz!val withTrade = for { t <- fetchTrade n <- updateTrade}yield(t map n)
val withTrade_c = withTrade(new RedisTradeRepository)
Monadic bindingOf functions
domain rule ..
enrichment of trade is done in steps:
1. get the tax/fee ids for a trade2. calculate tax/fee values3. enrich trade with net cash amount
// enrichment of trade// implementation follows problem domain modelval enrich = for { // get the tax/fee ids for a trade taxFeeIds <- forTrade
// calculate tax fee values taxFeeValues <- taxFeeCalculate
// enrich trade with net amount netAmount <- enrichTradeWith}yield((taxFeeIds map taxFeeValues) map netAmount)
// get the list of tax/fees for this tradeval forTrade: Trade => Option[List[TaxFeeId]] ={trade => // .. implementation}
// all tax/fees for a specific tradeval taxFeeCalculate: Trade => List[TaxFeeId] => List[(TaxFeeId, BigDecimal)] = {t => tids => //.. implementation}
val enrichTradeWith: Trade => List[(TaxFeeId, BigDecimal)] => BigDecimal = {trade => taxes => //.. implementation}
val enrich = for { // get the tax/fee ids for a trade taxFeeIds <- forTrade
// calculate tax fee values taxFeeValues <- taxFeeCalculate
// enrich trade with net amount netAmount <- enrichTradeWith}yield((taxFeeIds map taxFeeValues) map netAmount)
(TradeModel.Trade) => Option[BigDecimal]
:type enrich
The Reader Monad
• Note how the Trade is being delayed in injection
• How we don’t have to repeat the Trade argument in each invocation of the functions
• This is an implementation of the Reader monad – Trade is only being read to compute all calculations
• Partial Application is the key
trait TradeService { def fetchTrade(refNo: String)(implicit repo: TradeRepository) = repo.fetch(refNo)
def updateTrade(trade: Trade)(implicit repo: TradeRepository) = repo.update(trade)}
object TradeService extends TradeService
typeclass based dependency injection ..
implicit object RedisTradeRepository extends TradeRepository {
def fetch(refNo: String): Trade = //.. Redis based implementation
def update(trade: Trade): Trade = //.. Redis based implementation}
typeclass instance for Redis based repository ..
import TradeService._import Repositories.RedisTradeRepository
def run = { updateTrade(fetchTrade("r-123")) //..}
Top Related