SCALA : CONFIG

So we progress with the series of posts for .NET devs that may want to try their luck with Scala. This time we will be talking about configuration.

As I have stated quite a lot already I am a seasoned .NET developer that is getting into Scala.

Those of you that have worked in the .NET space will know that you can use the CofigurationManager class to help you read App.Config, or Web.Config file. Where the XXX.Config files are stored as XML a typical example being something like the ones shown below

 

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%4.4thread] %-5level %20.20logger{1} - %message%newline" />
      </layout>
    </appender>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="Client.log" />
      <appendToFile value="true" />
      <rollingStyle value="Composite" />
      <datePattern value="yyyyMMdd" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="1MB" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%4.4thread] %-5level %20.20logger{1} - %message%newline" />
      </layout>
    </appender>
    <root>
      <level value="INFO" />
      <appender-ref ref="ConsoleAppender" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>
  </log4net>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <runtime>
     <runtime>
      <loadFromRemoteSources enabled="true" />
   </runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Cors" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.2.0" newVersion="5.2.2.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

The .NET configuration system is also quite flexible in that is allows you to create custom sections, but this requires a lot of code.

Here is an example : https://msdn.microsoft.com/en-us/library/2tw134k3.aspx up on date 12/11/15

We want to add a custom section which contains 2 properties

  • font
  • color

So we would have to add code similar to this to the actual XXXX.config file

 

<configuration>
<!-- Configuration section-handler declaration area. -->
  <configSections>
    <sectionGroup name="pageAppearanceGroup">
      <section 
        name="pageAppearance" 
        type="Samples.AspNet.PageAppearanceSection" 
        allowLocation="true" 
        allowDefinition="Everywhere"
      />
    </sectionGroup>
      <!-- Other <section> and <sectionGroup> elements. -->
  </configSections>

 <!-- Configuration section settings area. -->
  <pageAppearanceGroup>
    <pageAppearance remoteOnly="true">
      <font name="TimesNewRoman" size="18"/>
      <color background="000000" foreground="FFFFFF"/>
    </pageAppearance>
  </pageAppearanceGroup>


</configuration>

 

And also create the following C# code to create this custom set of XML tags

using System;
using System.Collections;
using System.Text;
using System.Configuration;
using System.Xml;

namespace Samples.AspNet
{
    public class PageAppearanceSection : ConfigurationSection
    {
        // Create a "remoteOnly" attribute.
        [ConfigurationProperty("remoteOnly", DefaultValue = "false", IsRequired = false)]
        public Boolean RemoteOnly
        {
            get
            { 
                return (Boolean)this["remoteOnly"]; 
            }
            set
            { 
                this["remoteOnly"] = value; 
            }
        }

        // Create a "font" element.
        [ConfigurationProperty("font")]
        public FontElement Font
        {
            get
            { 
                return (FontElement)this["font"]; }
            set
            { this["font"] = value; }
        }

        // Create a "color element."
        [ConfigurationProperty("color")]
        public ColorElement Color
        {
            get
            {
                return (ColorElement)this["color"];
            }
            set
            { this["color"] = value; }
        }
    }

    // Define the "font" element
    // with "name" and "size" attributes.
    public class FontElement : ConfigurationElement
    {
        [ConfigurationProperty("name", DefaultValue="Arial", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\", MinLength = 1, MaxLength = 60)]
        public String Name
        {
            get
            {
                return (String)this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }

        [ConfigurationProperty("size", DefaultValue = "12", IsRequired = false)]
        [IntegerValidator(ExcludeRange = false, MaxValue = 24, MinValue = 6)]
        public int Size
        {
            get
            { return (int)this["size"]; }
            set
            { this["size"] = value; }
        }
    }

    // Define the "color" element 
    // with "background" and "foreground" attributes.
    public class ColorElement : ConfigurationElement
    {
        [ConfigurationProperty("background", DefaultValue = "FFFFFF", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\GHIJKLMNOPQRSTUVWXYZ", MinLength = 6, MaxLength = 6)]
        public String Background
        {
            get
            {
                return (String)this["background"];
            }
            set
            {
                this["background"] = value;
            }
        }

        [ConfigurationProperty("foreground", DefaultValue = "000000", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\GHIJKLMNOPQRSTUVWXYZ", MinLength = 6, MaxLength = 6)]
        public String Foreground
        {
            get
            {
                return (String)this["foreground"];
            }
            set
            {
                this["foreground"] = value;
            }
        }

    }

}

Which we can then access from code like this

Samples.AspNet.PageAppearanceSection config =
        (Samples.AspNet.PageAppearanceSection)System.Configuration.ConfigurationManager.GetSection(
        "pageAppearanceGroup/pageAppearance");
var cfgFont = config.Font.Name

Phew, that’s a lot of work.

There are other ways to do this in C#. I am thinking of the awesome SimpleConfig GitHub repo, which in my opinion is well underrated and something that we should all use in our .NET projects.

https://github.com/mikeobrien/SimpleConfig up on date 12/11/15

Using this we can now write code like this (instead of the above, which is a BIG improvement if you ask me)

First create your configuration types:

public class MyApplication
{
    public Build Build { get; set; }
}

public enum Target { Dev, CI }

public class Build
{
    public string Version { get; set; }
    public DateTime Date { get; set; }
    public Target DeployTarget { get; set; }
}

 

Next you need to register the SimpleConfig section handler in your web/app.config and create your configuration section as shown below. The default convention for the section name is the camel cased name of the root configuration type (Although you can override this as we’ll see later). The section name under configSections must match the section element name. All other element and attribute names in the configuration section are case insensitive but must otherwise match the property names of your configuration types (You can override this as well).

<configuration>
  <configSections>
    <section name="myApplication" type="SimpleConfig.Section, SimpleConfig"/>
  </configSections>
  <myApplication>
    <build version="0.0.0.0" date="10/25/1985" deployTarget="Dev"/>
  </myApplication>
</configuration>

Now you can load the section either by calling the convenience static method or newing up a new instance:

ar config = Configuration.Load<MyApplication>();
// or
var config = new Configuration().LoadSection<MyApplication>();

config.Build.Date.ShouldEqual(DateTime.Parse("10/25/1985"));
config.Build.DeployTarget.ShouldEqual(Target.Dev);
config.Build.Version.ShouldEqual("0.0.0.0");

 

This is cool, however Scala does it even better. The rest of this post will be about the awesome Typesafe Config library (Typesafe are the people behind Akka (I like Akka)

 

Typesafe Config Library

The guys from Typesafe have an awesome config library (https://github.com/typesafehub/config) that you may use with either Java/Scala, and it supports the following 3 formats

  • JSON
  • Java properties
  • HOCON (Human-Optimized Config Object Notation)

For everything I demonstrate here I will be using the following HOCON file

sachas.business {
  owner {
    name = "sacha barber"
    description = ${sachas.business.owner.name} "is the owner"
  }
  team {
    members = [
      "sacha barber"
      "chris baker"
      "paul freeman"
      "ryan the mad one"
    ]
  }
}
sachas.business.team.avgAge = 35

If you want to try this out in your own scala project you will need to add it as a SBT library dependency, using this (the version shown here was right at time of this post being published)

libraryDependencies ++= Seq(
  "com.typesafe" % "config" % "0.4.0"
)

So what can this Typesafe library do?

Well it essentially reads configuration information from file(s). This would typically be done using a application.conf file, which would be placed in your resources folder.

image

After you have a file, we can proceed to load it using the ConfigFactory, which you can use like this:

import com.typesafe.config.ConfigFactory


object ClassesDemo {

  def main(args: Array[String]) : Unit =
  {

    val config = ConfigFactory.load("application.conf")
    .....
    .....
    .....
    System.in.read()
  }
}


Well let’s start simple by using the HOCON file we outlined above:



import com.typesafe.config.ConfigFactory


object ClassesDemo {


  def main(args: Array[String]) : Unit =
  {

    val config = ConfigFactory.load("application.conf")
    val ownerName = config.getString("sachas.business.owner.name")  // => sacha barber
    val desc = config.getString("sachas.business.owner.description") // => sacha barber is the owner
    val age = config.getInt("sachas.business.team.avgAge ") // => 35
    val members = config.getStringList("sachas.business.team.members") // => [sacha barber,chris baker,paul freeman,an the mad one


    System.in.read()
  }
}


It can be seen that we can easily drill into the tree of properties, and use the getXXX methods to grab strings, list and all sorts of goodness

The above code gives this result

image

Pretty simple huh

The Config object has these helper methods to enable you to read configuration values:

  • getAnyRef
  • getAnyRefList
  • getBoolean
  • getBooleanList
  • getByte
  • getByteList
  • getConfig
  • getConfigList
  • getDouble
  • getDoubleList
  • getInt
  • getIntList
  • getList
  • getLong
  • getLongList
  • getMilliSeconds
  • getMilliSecondsList
  • getNanoSeconds
  • getNanoSecondsList
  • getNumber
  • getNumberList
  • getObject
  • getObjectList
  • getString
  • getStringList
  • getValue

I think most of these are quite obvious, perhaps the only one that I personally feel may need a bit more of an explanation are the getObject/getObjectList methods. So let’s have a look a specific example for this.

Say we have this HOCON file

decoders = [ { a : "socket://1.2.3.4:9000" },
  { b : "socket://1.2.3.4:8080" },
  { c : "socket://9.9.9.9:9001" },
  { d : "socket://9.9.8.8:9000" },
  { e : "socket://4.3.2.1:8081" } ]

Which we then read in like this



import com.typesafe.config.{ConfigObject, ConfigValue, ConfigFactory, Config}
import scala.collection.JavaConverters._
import java.net.URI
import java.util.Map.Entry



case class Settings(config: Config) {
  lazy val decoders : Map[String, URI] = {
    val list : Iterable[ConfigObject] = config.getObjectList("decoders").asScala
    (for {
      item : ConfigObject <- list
      entry : Entry[String, ConfigValue] <- item.entrySet().asScala
      key = entry.getKey
      uri = new URI(entry.getValue.unwrapped().toString)
    } yield (key, uri)).toMap
  }
}


object ClassesDemo {


  def main(args: Array[String]) : Unit =
  {

    val config = ConfigFactory.load("smallerList.conf")
    val decoders = new Settings(config).decoders

    System.in.read()
  }
}


Which give us the following results

 image

 

 

I have shamelessly stolen this example from this blog, which is a very nice example in my opinion

http://deploymentzone.com/2013/07/25/typesafe-config-and-maps-in-scala/ up on date 16/11/15

 

 

 

Further Readings

I came across a couple of god blogs on this whilst writing my own post. These are outline here:

 

 

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: