27 January, 2014

Scala Dependency Injection techniques

   Lately I was checking different solutions for dependency injection in Scala (code available here in pl.mariusz.marciniak.di package). Scala DI is vastly described in Real World Scala Dependency Injection artice, so I will just put short summary for each of techniques that I used:

Implicits
   The most obvious solution for injecting dependencies is to add parameters to object’s constructor (you can also do it by defining properties, but in that case your object becomes mutable). Additionally Scala provides you implicit parameters, which can be automatically supplied.
  class Calculator (implicit val dataProvider:DataProvider, implicit val operation:Operation) {
    def calculate(): Int = operation.exec(dataProvider.nextArgument, dataProvider.nextArgument)
  }
  val calc = new Calculator

Here Calculator’s constructor has two arguments marked as implicit. This means that they can be omitted if default objects of the same type are defined. Above implementation will generate compilation error, because these defaults are missing. To provide them we can for example define and import configuration object.
  object AdditionFromFakeDbDataConfig {
    implicit val operation: Operation = new Addition 
    implicit val dataProvider: DataProvider = new FakeDbDataProvider
  }
  import AdditionFromFakeDbDataConfig._
  val calc = new Calculator

Structural typing
   This is Scala feature to provide duck typing in a type-safe manner. Duck typing is a style of typing in which an object's methods and properties determine the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface. In Scala you can achieve that by replacing data type with code block containing all required methods and properties.
Calculator(dataProvider: { val x: Int; val y: Int}, operation: { def exec(a: Int, b: Int): Int}) { ... }
Calculator class requires two arguments to be passed to its constructor. First needs to define two values x and y, and the second one must contains function exec taking two integers and returns integer. For example you can pass two objects:
  object Data {
    val x = 5
    val y = 3
  }
  object Avg {
    def exec(a: Int, b: Int): Int = (a + b) / 2 
  }
This solution gives us opportunity to aggregate all required resources into one object and pass that object into constructor. It is good to remember that Scala's structural typing uses reflection, so one can face some performance problems when he is using this solution vastly. Additional information about Structural Typing can be found on this site.

Cake Pattern
   Cake Pattern is the last method that I want to mention about in this post. Cake Pattern combines few Scala features:
  1. Trait with abstract value
  2. First of all you need to create a namespace using trait, containing all classes that you need to inject e.g. Addition and Multiplication are containted in Operations namespace, additionally there is abstract value defined that need to be instantiate in non-abstract object.  
      trait Operations {
        val operation: Operation
        trait Operation {
          def exec(a: Int, b: Int): Int
        }
    
        class Addition extends Operation {
          def exec(a: Int, b: Int): Int = a + b
        }
    
        class Multiplication extends Operation {
          def exec(a: Int, b: Int): Int = a * b
        }
      }
    

  3. Self type
  4. Self type can be used to access fields or functions of other type which implementation will be mixed in later e.g. CalculatorComponent requires two traits DataProviders and Operations, both of them are in fact namespaces with abstract values: dataProvider and operation, that is why they can be accessed in Calculator class  
      trait CalculatorComponent { this: DataProviders with Operations =>
        val calculator = new Calculator
        class Calculator {
          def calculate(): Int = operation.exec(dataProvider.nextArgument, dataProvider.nextArgument)
        }
      }
    
  5. Mixins
  6. Last step is to combine all components in one object. 
      object AdditionFromFakeDbDataConfig extends CalculatorComponent with Operations with DataProviders {
          val operation = new Addition
          val dataProvider = new FakeDbDataProvider
      }
Additional links

No comments:

Post a Comment