Wednesday, October 3, 2007

Executable Configuration Files

Ever since I got my start in Delphi with ye olde ini file, I've had this feeling that there must be a better way of storing application configuration information. Even with a good API, there is just too much boilerplate code involved in parsing the file, loading it into a data structure so it can be operated on, and then converting it back to text so it can be saved. Writing boilerplate code makes me feel braindead, and I don't like feeling braindead.

My company's move from Delphi to .Net a few years ago brought incremental improvement: a built-in api for reading/writing custom settings in app.config. In .Net 2 you can even have a config class auto-generated for you, which provides type-safe and intellisensy access to your configuration settings. And, of course, there's the .Net support for serializing objects to Xml. I confess I don't have much experience with that.

Life was easier, but there were still significant restraints: you need to design your configuration in just such a way in order to get the boilerplate auto-generated. For an example of the issues, see this blog post about storing configuration in .Net, and read the caveats pointed out by the commenters. The solution presented is a pretty good one, but there's still bookkeeping: copy this here, add that there, store in the app.config and hope you have read access, use public fields in your settings class.

It wasn't until I learned Ruby that I realized these restrictions are largely inherent in the strongly-typed, compiled languages I've been using. I had two configuration epiphanies using Ruby:

  1. The Yaml parser in Ruby blew me away. Yaml is a lightweight configuration language meant to be easier on the eyes than Xml. Here's a snippet:
    development:
      adapter: mysql
      database: members
      username: root
      password: pwd
      host: localhost

    With the ruby Yaml parser this can be loaded with one line of code, and accessed as a hash table: config["development"]["adapter"] would return "mysql".

  2. Because ruby is an interpreted language, you may be able to write all of your configuration in ruby itself. One of the main reasons for using a config file (to update settings without having to recompile) doesn't exist in ruby. This is done a lot in Ruby on Rails, for instance. Here's a short snippet of production.rb:
    # Code is not reloaded between requests
    config.cache_classes = true
     
    # Use a different logger for distributed setups
    config.logger = SyslogLogger.new

    beautifully simple, isn't it?

Just recently I came across an idea that pushes the executable-configuration envelope even further: using Lisp as a data representation. Lisp is the ideal format: hierarchical like Xml, cleaner than Xml, executable. As Steve Yegge points out, if Xml was really sufficient why would we keep adding extensions to make it more programmable? Might as well start off with a real language, like Lisp.

This is really only a proof-of-concept, of course. You can't use Lisp in a real project because nobody knows Lisp. And besides, I'm not sure that communicating between Java and Lisp would be any easier than communicating between Java and Xml. What you can do is use that example to show you how easy configuration ought to be. It prevents you from being really satisfied with a suboptimal solution, which is good even if all of your options are suboptimal. You at least know what you're aiming at.

So, where does that leave someone using a statically type language? What lessons can you take away from Lisp and Ruby that will make life easier in C#?

To be honest, I'm really not sure yet. Nothing in C# prevents you from writing an Xml or Yaml parser that loads the data into nested hashes/arrays. That's one option. And the support for storing settings in the app.config really is pretty good. But I'm on the lookout for a better way, and that is the point.



[1] Of course there's no reason that Xml couldn't be interpreted in the same way, and there is a Ruby library, XmlSimple, which does exactly this.

1 comment:

Anonymous said...

"This is really only a proof-of-concept, of course. You can't use Lisp in a real project because nobody knows Lisp. "

I know Lisp and use it on real projects.