Search code examples
scalaplayframework-2.0akkahocon

Configuring actor behavior using typesafe Config and HOCON


I am building a large agent-based / multi-agent model of a stock exchange using Akka/Play/Scala, etc and I am struggling a bit to understand how to configure my application. Below is a snippet of code that illustrates an example of the type of problem I face:

class Exchange extends Actor {

  val orderRoutingLogic = new OrderRoutingLogic()

  val router = {

    val marketsForSecurities = securities.foreach { security =>

      val marketForSecurity = context.actorOf(Props[DoubleAuctionMarket](
        new DoubleAuctionMarket(security) with BasicMatchingEngine), security.name
      )
      orderRoutingLogic.addMarket(security, marketForSecurity)

    }
    Router(orderRoutingLogic)

  }

In the snippet above I inject a BasicMatchingEngine into the DoubleAuctionMarket. However I have written a number of different matching engines and I would like to be able to configure the type of matching engine injected into DoubleAuctionMarket in the application configuration file.

Can this level of application configuration be done using typesafe Config and HOCON configuration files?


Solution

  • interesting case. If I understood you right, you want to configure Market actor mixing in some MatchingEngine type specified in config?

    Some clarification: you can't simply mix in dynamic type. I mean if you move MatchingEngine type to config - it will be known only at runtime, when config is parsed. And at that time you'll not be able to instantiate new DoubleAuctionMarket(security) with ???SomeClassInstance???. But maybe you could replace inheritance with aggregation. Maybe an instance of MatchingEngine can be passed to Market as parameter?

    Now, how to obtain an instance of MatchingEngine from config? In short - Typesafe Config has no parser for FQCN properties, but it's not hard to do it yourself using reflection. This technique is used in many places in Akka. Look here first. provider property set as fqcn string and can be changed to other provider (i.e. RemoteActorRefProvider) in other configurations. Now look at how it's processed to obtain Provider instance. First it's just being read as string here. Then ProviderClass is used to instantiate actual (runtime) provider here. DynamicAccess is a utility helping with reflexive calls. It's not publicly accessible via context.system, but just take a piece of it or instantiate yourself, I don't think it's a big issue.

    With some modifications, your code may look:

    class Exchange extends Actor {
    
      val orderRoutingLogic = new OrderRoutingLogic()
      val matchingEngineClass = context.system.settings.config.getString("stocks.matching-engine")
      val matchingEngine = DynamicAccess.createInstance[MatchingEngine](matchingEngineClass)
    
      val router = {
    
        val marketsForSecurities = securities.foreach { security =>
    
          val marketForSecurity = context.actorOf(DoubleAuctionMarket.props(security, matchingEngine))
          orderRoutingLogic.addMarket(security, marketForSecurity)
    
        }
        Router(orderRoutingLogic)
    
      }
    

    I've moved props to companion object of DoubleAuctionMarket as stated in Recommended Preactices of akka docs. Usage of Props(new Actor()) is dangerous practice.