Improve Scala23 juni 2014

Creating a SettingsActor  for configuration properties using Akka Extensions

Recently, I followed a Typesafe Akka training and one of the little gems that I took home from the training was how to create a SettingsActor using the Akka Extensions mechanism. Using a SettingsActor gives one easy access to configuration properties but it also gives you a single point of maintenance for the configuration. In the past, if I wanted to use configuration values, I would “clutter” my code using code that resembles the following snippet:

val port = context.system.settings.config getInt "my-test-app.mail.port”

If the name of a configuration property changes, it is easier to refactor this using a SettingsActor than having to scan your source code for a string that matches the previous property name.

Using a small example, I will try to explain how to create such a SettingsActor. In this example we will create the following components:

  • A class which will extend Extension
  • A companion object to the class which extends ExtensionKey
  • A trait which we will call SettingsActor*
  • A configuration file
  • An example actor MyTestActor which uses the SettingsActor.

*Note: name can be whatever you want it to be

Creating the Extension class, ExtensionKey object and SettingsActor trait

First of all, we start by creating the class, companion object and the trait. As you can see from the code below, it really is quite simple.

import akka.actor.{Actor, ExtensionKey, Extension, ExtendedActorSystem}
import scala.concurrent.duration.{Duration, FiniteDuration}
import java.util.concurrent.TimeUnit

// companion object
object Settings extends ExtensionKey[Settings]

// extension class implementation for getting the desired configuration properties from the application.conf
class Settings (system: ExtendedActorSystem) extends Extension {
  // get the my-test-app configuration from the
  val myTestAppConfig = system.settings.config getConfig("my-test-app")

  // get defaultRequestTimeout configuration property
  val defaultRequestTimeout: FiniteDuration = Duration(myTestAppConfig getDuration("defaultRequestTimeout", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)

  // get the mail configuration properties
  object mail {
      private val mailConfig = myTestAppConfig getConfig "mail"
      val host: String     = mailConfig getString "host"
      val port: Int        = mailConfig getInt    "port"
      val user: String     = mailConfig getString "user"
      val password: String = mailConfig getString "password"
    }
}

/*
 * Create a trait using a so called self-type annotation that this is an Actor.
 * This allows us to get access to the context and therefore the system to allow
 * us to construct the Settings Extension.
 */
trait SettingsActor { this: Actor =>
  val settings = Settings(context.system)
}

The configuration file

The following shows part of the application.conf containing the properties which are being used by the Settings class:

my-test-app {
    defaultTimeout = 10 seconds

    mail {
        host=mailtrap.io
        port=2525
        user=myuser
        password=secret
        }
    }
}

Using the SettingsActor

Now that we have the configuration and the SettingsActor, we can mixin the SettingsActor using the keyword with. Notice how much nicer/cleaner the code is than having to look up the property value:

import akka.actor._
import MyTestActor.SomeMessage

object MyTestActor {
  case class SomeMessage(msg: String)

  // factory method for creating the actor
  def props: Props =
    Props(new MyTestActor())
}

class MyTestActor extends Actor with SettingsActor {
  // because we mixin the SettingsActor, we have gained access to the settings
  val hostname = settings.mail.host
  val port = settings.mail.port
  val username = settings.mail.user
  val password = settings.mail.password
  val mailActor = context.actorOf(MailActor.props(hostname, port, username, password),"My-Test-Actor")

  override def receive: Receive = {
    case SomeMessage(msg) => mailActor ! MailActor.MailMessage(msg)
  }
}

Summing up

Although this is not the only way to gain access to your configuration, I personally find it a nice solution that helps to keep my code less cluttered and more maintainable.