Java アプリケーションのための設定ファイル形式の比較

アプリケーションを作る時に毎回のように設定ファイル形式を何にするか迷うので、自分の頭の中を整理する意味で書いてみる。

各設定値へアクセスするコードも書かないと片手落ち感あるけれど、長くなるのでやめた。

実行環境は普段使っている Oracle JDK を想定している。

XML
------

実行環境が XML パーサを持っているので、多少の処理を書けば外部ライブラリを使わずに設定ファイルをとして使える。

人間が書くには少し冗長なせいか、最近は敬遠されがちな印象がある。

今回の挙げた中だと唯一下記の性質をすべて持ち合わせている（処理系次第だけど）:

* 元あったコメントを残し、
* プロパティの順番も保持しくてれ、
* 人間もシステムも読み書きできる

なので、個人的にはまだ使い道はあると思っている。

~~~~~~~~~~~~~~~例.xml
<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <server>
    <address>localhost</address>
    <port>8081</port>
  </server>

  <!-- システム foo へのクライアントの設定 -->
  <foo>
    <servers>
      <server>192.169.10.11:9999</server>
      <server>192.169.10.12:9999</server>
      <server>192.169.10.13:9999</server>
    </servers>
    <connectionTimeoutMillis>3000</connectionTimeoutMillis>
    <retry>3</retry>
  </foo>
</root>
~~~~~~~~~~~~~~~

代表的なライブラリ: [Apache Commons Configuration](https://commons.apache.org/proper/commons-configuration/), [Properties](https://docs.oracle.com/javase/jp/8/api/java/util/Properties.html#loadFromXML-java.io.InputStream-), [DocumentBuilder](https://docs.oracle.com/javase/jp/8/api/index.html?javax/xml/parsers/DocumentBuilder.html)


JSON
-------

コメントが書けないのであんまり使う理由がない。

RFCぶっ千切ってコメント書けるようにするくらいなら他の設定ファイル形式を採用したい。

Nashorn に `JSON.parse` があるみたいなので外部ライブラリ使わなくても設定ファイルとして使うことができるのかな？やったことないので試してみたい。

文法は今回挙げた中でもシンプルな部類に入る。そのため書く人間によってゆらぎが出にくい。

個人的には設定ファイルというよりは、人間が読みやすいデータシリアライズのための形式でしかないと思っている。

~~~~~~~~~~~~~~~~~~~~~~例.json
{
  "server": {
    "address": "localhost",
    "port": 8081
  },
  "foo": {
    "servers": [
      "192.169.10.11:9999",
      "192.169.10.12:9999",
      "192.169.10.13:9999"
    ],
    "connectionTimeoutMillis": 3000,
    "retry": 3
  }
}
~~~~~~~~~~~~~~~~~~~~~~

代表的なライブラリ: [Jackson](http://wiki.fasterxml.com/JacksonHome)


.properties
----------------

XML 同様、実行環境がパーサを持っているので外部ライブラリ無しで使える。

今回挙げた中で最もシンプルな仕様で、書く人によるゆらぎがもっとも起きにくい。

ネスト表記ができないので、同じプレフィックスが続いてしまうのが若干ダルい。

シンプルすぎて配列・連想配列などには対応していないので、設定ファイルを読み取ったあとにパースする必要がある。（例: 下記の例の`foo.servers`）

~~~~~~~~~~~~~~~~~~~~~~例.properties
server.address=0.0.0.0
server.port=8081

# システム foo へのクライアントの設定
foo.servers=192.169.10.11:9999,192.169.10.12:9999,192.169.10.13:9999
foo.connectionTimeoutMillis=3000
foo.retry=3
~~~~~~~~~~~~~~~~~~~~~~

代表的なライブラリ: [Properties (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/api/java/util/Properties.html#load-java.io.InputStream-), [Apache Commons Configuration](https://commons.apache.org/proper/commons-configuration/)


YAML
-------

近年では言語を問わず使われることの多い形式。Java だと dropwizard や spring-boot といったアプリケーションフレームワークが採用している。アプリケーションだと ElasticSearch かな。

インデントでネストが切れるため、閉じるための括弧が不要でシンプルに書ける。

仕様が意外と多く表現が柔軟すぎて、書き手によってゆらぎが出やすいのが少し辛い。

タグが使われているのを見たことがないけどどうなんだろう。

~~~~~~~~~~~~~~~~~~~~~~例.yaml
server:
  address: "0.0.0.0"
  port: 8081

# システム foo へのクライアントの設定
foo:
  servers:
  - "192.169.10.11:9999"
  - "192.169.10.12:9999"
  - "192.169.10.13:9999"
  connectionTimeoutMillis: 3000
  retry: 3
~~~~~~~~~~~~~~~~~~~~~~

~~~~~~~~~~~~~~~~~~~~~~例2.yaml
server: { address: "0.0.0.0", port: 8081 }

# システム foo へのクライアントの設定
foo:
  servers: ["192.169.10.11:9999", "192.169.10.12:9999", "192.169.10.13:9999"]
  connectionTimeoutMillis: 3000
  retry: 3
~~~~~~~~~~~~~~~~~~~~~~

代表的なライブラリ: [SnakeYAML](https://bitbucket.org/asomov/snakeyaml)


HOCON
--------

typesafe が提案・実装している形式。akka やそれを採用したフレームワーク play や spray などが採用している。というより typesafe 製のプロダクト全般かな？

Human-Optimized Config Object Notation の頭文字。

`foo { bar = "qux" }` と `foo.bar = "qux"` に区別がない。

~~~~~~~~~~~~~~~~~~~~~~~~~~~例.conf
server {
  address = "localhost"
  port = 8081
}

# システム foo へのクライアントの設定
foo {
  servers = [
    "192.169.10.11",
    "192.169.10.12",
    "192.169.10.13"
  ]
  connectionTimeoutMillis = 3000
  retry = 3
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~

代表的なライブラリ: [typesafehub/config](https://github.com/typesafehub/config)


Groovy の ConfigSlurper
-----------------------

Groovy の文法に設定ファイルのための仕様をいくつか追加し、内部DSLとして利用する。

Groovy の文法のスーパーセットになる仕様なので、ロジックも書けてしまうが・・・設定ファイルの域を越えてしまうのでおすすめしない。せいぜいシステムプロパティを読むのに使ったりするくらいにしておいたほうが良いかも。

パーサは Groovy 処理をすることなるので今回挙げた中で一番大きな jar が必要になる。

環境ごとに適用するプロパティを切り替えられる仕組みが備わっている。

HOCON 同様 `foo { bar = "qux" }` と `foo.bar = "qux"` に区別がない。

最近自分が担当したプロダクトで採用してみたが:

* `foo.bar { ... }` というようにドット繋ぎしネストを書くことができない
* `foo = "somePrefix-$bar"` のように書くと String が返ってこない

というところが辛かったけれど、それ以外は特に問題なく使えている。

ただし、resource ディレクトリにあっても eclipse がコンパイルしてしまってうまく動かないことがあってちょっと辛かった。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~例.groovy
server {
  address = "localhost"
  port = 8081
}

// システム foo へのクライアントの設定
foo {
  servers = [ /* 下記で上書きする */ ]
  connectionTimeoutMillis = 3000
  retry = 3
}

// 環境ごとにシステム foo の接続先を変える
environments {
  local {
    foo.servers = ["192.168.1.10"]
  }
  stagging {
    foo.servers = ["192.168.1.10"]
  }
  production {
    foo.servers = [
      "192.169.10.11",
      "192.169.10.12",
      "192.169.10.13"
    ]
  }
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

代表的なライブラリ: [ConfigSlurper (groovy)](http://docs.groovy-lang.org/latest/html/gapi/groovy/util/ConfigSlurper.html)


TOML
---------

ini ファイル（Windows や MySQL で使われる設定ファイル形式）を拡張した文法。

インデントが深くならないのが特徴。

Java における実装は複数あるがどれも出来たてで枯れている印象がないのが不安な点。参考: [JavaのTOMLライブラリ比較 - Qiita](http://qiita.com/eiryu/items/98910ba487b5ec37a859)

採用している Java プロダクトはあるのかな？Java じゃないけど Rust の Cargo が採用していたのに驚いた記憶がある。

~~~~~~~~~~~~~~~~~~~~~~~~例.toml
[server]
address = "localhost"
port = 8081

# システム foo へのクライアントの設定
[foo]
servers = [
  "192.169.10.11",
  "192.169.10.12",
  "192.169.10.13"
]
connectionTimeoutMillis = 3000
retry = 3
~~~~~~~~~~~~~~~~~~~~~~~~

代表的なライブラリ: [agrison/jtoml](https://github.com/agrison/jtoml), [mwanji/toml4j](https://github.com/mwanji/toml4j)


まとめ
---------

コメントも書けて、冗長な文法でもなく、Java 実装が十分に安定していて、採用している有名プロダクトがありメンテナンスも十分に行われそうなファイル形式は、__YAML, HOCON, Groovy ConfigSlurper__ あたりかな。

外部ライブラリを使いたくない場合は、XML or properties になる。

人間とシステム両方が読み書きする必要があるなら XML 択一になる。この性質は処理系次第でどうにかできると思うのでもっと増えてほしい。

C と Go にしか実装がないが ucl/hcl が気になる。