アプリケーション設定を読み込む最良の方法


24

Javaアプリケーションの設定を保持する簡単な方法は、特定の値(この値は数字、文字列、日付など)に関連付けられた各設定の識別子を含む「.properties」拡張子を持つテキストファイルで表されます。 。C#は同様のアプローチを使用しますが、テキストファイルには「App.config」という名前を付ける必要があります。どちらの場合も、ソースコードでは、設定を読み取るために特定のクラスを初期化する必要があります。このクラスには、指定された設定識別子に関連付けられた値を(文字列として)返すメソッドがあります。

// Java example
Properties config = new Properties();
config.load(...);
String valueStr = config.getProperty("listening-port");
// ...

// C# example
NameValueCollection setting = ConfigurationManager.AppSettings;
string valueStr = setting["listening-port"];
// ...

どちらの場合も、構成ファイルから読み込まれた文字列を解析し、変換された値を関連する型付きオブジェクトに割り当てる必要があります(この段階で解析エラーが発生する可能性があります)。解析ステップの後、設定値が特定の有効領域に属していることを確認する必要があります。たとえば、キュ​​ーの最大サイズは正の値である必要があり、いくつかの値は関連している場合があります(例:min <max )、 等々。

アプリケーションが起動するとすぐに設定をロードする必要があると仮定します。つまり、アプリケーションによって実行される最初の操作は設定をロードすることです。設定の無効な値は、自動的にデフォルト値に置き換えられる必要があります:これが関連する設定のグループに発生した場合、それらの設定はすべてデフォルト値で設定されます。

これらの操作を実行する最も簡単な方法は、最初にすべての設定を解析し、次に読み込まれた値をチェックし、最後にデフォルト値を設定するメソッドを作成することです。ただし、このアプローチを使用する場合、メンテナンスは困難です。アプリケーションの開発中に設定の数が増えると、コードの更新がますます難しくなります。

この問題を解決するために、次のようにTemplate Methodパターンを使用することを考えていました。

public abstract class Setting
{
    protected abstract bool TryParseValues();

    protected abstract bool CheckValues();

    public abstract void SetDefaultValues();

    /// <summary>
    /// Template Method
    /// </summary>
    public bool TrySetValuesOrDefault()
    {
        if (!TryParseValues() || !CheckValues())
        {
            // parsing error or domain error
            SetDefaultValues();
            return false;
        }
        return true;
    }
}

public class RangeSetting : Setting
{
    private string minStr, maxStr;
    private byte min, max;

    public RangeSetting(string minStr, maxStr)
    {
        this.minStr = minStr;
        this.maxStr = maxStr;
    }

    protected override bool TryParseValues()
    {
        return (byte.TryParse(minStr, out min)
            && byte.TryParse(maxStr, out max));
    }

    protected override bool CheckValues()
    {
        return (0 < min && min < max);
    }

    public override void SetDefaultValues()
    {
        min = 5;
        max = 10;
    }
}

問題は、この方法では、単一の値であっても、設定ごとに新しいクラスを作成する必要があるということです。この種の問題に対する他の解決策はありますか?

要約すれば:

  1. 簡単なメンテナンス:たとえば、1つ以上のパラメーターの追加。
  2. 拡張性:アプリケーションの最初のバージョンは単一の構成ファイルを読み取ることができますが、それ以降のバージョンではマルチユーザーセットアップの可能性があります(管理者は基本構成をセットアップし、ユーザーは特定の設定のみを設定できます)。
  3. オブジェクト指向設計。

.propertiesファイルの使用を提案している場合、ファイルが同じ場所にないことが望ましいので、開発、テスト、および実稼働中にファイル自体を保存します。その後、実行時に環境を検出して、アプリ内に場所をハードコーディングしない限り、アプリケーションを任意の場所(dev、test、またはprod)で再コンパイルする必要があります。

回答:


8

基本的に、外部設定ファイルはYAMLドキュメントとしてエンコードされます。これは、アプリケーションの起動時に解析され、構成オブジェクトにマッピングされます。

最終結果は堅牢で、とりわけ管理が簡単です。


7

これを2つの観点から考えてみましょう。構成値を取得するAPIとストレージ形式です。多くの場合、それらは関連していますが、それらを個別に検討すると役立ちます。

構成API

Template Methodパターンは非常に一般的ですが、その一般性が本当に必要かどうかは疑問です。構成値のタイプごとにクラスが必要です。あなたは本当にそんなに多くのタイプを持っていますか?文字列、int、float、boolean、enumなど、ほんの一握りでうまくいくと思います。これらを考慮するとConfig、いくつかのメソッドを持つクラスを作成できます。

int getInt(name, default, min, max)
float getFloat(name, default, min, max)
boolean getBoolean(name, default)
String getString(name, default)
<T extends Enum<T>> T getEnum(name, Class<T> enumClass, T default)

(最後の1つでジェネリック医薬品を入手したと思います。)

基本的に、各メソッドは、構成ファイルからの文字列値の解析を処理し、エラーを処理し、必要に応じてデフォルト値を返す方法を知っています。数値の範囲チェックでおそらく十分です。範囲値を省略するオーバーロードが必要な場合があります。これは、Integer.MIN_VALUE、Integer.MAX_VALUEの範囲を提供することと同等です。Enumは、文字列の固定セットに対して文字列を検証するタイプセーフな方法です。

複数の値、相互に関連する値、動的なテーブルルックアップなど、これが処理しないものがいくつかあります。これらの専用の解析および検証ルーチンを書くことができますが、これが複雑になりすぎると、疑問になります設定ファイルでやりすぎているかどうか。

ストレージ形式

Javaのプロパティファイルは、個々のキーと値のペアを保存するのに適しているように見え、上記で説明した値のタイプをかなりうまくサポートしています。また、XMLやJSONなどの他の形式を検討することもできますが、データをネストしたり繰り返したりしない限り、これらはおそらく過剰です。その時点では、設定ファイルを超えているように見えます。

Telastynは、シリアル化されたオブジェクトについて言及しました。これは可能ですが、シリアル化には困難が伴います。テキストではなくバイナリであるため、値を表示および編集することは困難です。シリアル化の互換性に対処する必要があります。シリアル化された入力に値がない場合(たとえば、Configクラスにフィールドを追加し、古いシリアル化された形式を読み込んでいる場合)、新しいフィールドはnull /ゼロに初期化されます。他のデフォルト値を入力するかどうかを決定するロジックを記述する必要があります。しかし、ゼロは構成値がないことを示していますか、それともゼロに指定されていますか?次に、このロジックをデバッグする必要があります。最後に(これが懸念事項かどうかはわかりません)、シリアル化されたオブジェクトストリームの値を検証する必要があります。悪意のあるユーザーが、シリアル化されたオブジェクトストリームを検出できないほど変更する可能性はありますが(不便ですが)

可能な限りプロパティに固執することをお勧めします。


2
こんにちはスチュアート、ここでお会いできてうれしいです:-)。Genericsを使用して強く入力すると、Javaでtempalteのアイデアが機能すると思うので、Stuartsの回答に追加するので、Setting <T>もオプションとして使用できます。
マルタインVerburg

@StuartMarks:まあ、私の最初のアイデアは、単に書くことだったConfigクラスを、あなたが提案したアプローチを使用しますgetInt()getByte()getBoolean()、など。継続はこの考えと、私が最初にすべての値を読んで、私はこのフラグへの各値を関連付けることができ(解析エラーなど、逆シリアル化中に問題が発生した場合、このフラグはfalseです)。その後、ロードされたすべての値の検証フェーズを開始し、デフォルト値を設定できます。
enzom83

2
すべての詳細を簡素化するために、何らかの種類のJAXBまたはYAMLアプローチをお勧めします。
ゲイリーロウ

4

私がそれをやった方法:

すべてをデフォルト値に初期化します。

ファイルを解析し、値を保存します。設定される場所は、値が受け入れ可能であること、不正な値が無視されることを保証する責任があります(したがって、デフォルト値を保持します)。


これは良いアイデアかもしれません:設定の値をロードするクラスは、設定ファイルから値をロードするためだけに対処する必要があるかもしれません、つまり、その責任は値をロードするためだけのものです構成ファイルから; 代わりに、各モジュール(いくつかの設定を使用)が値を検証する責任を負います。
enzom83

2

この種の問題に対する他の解決策はありますか?

必要なのが単純な構成だけである場合、単純な古いクラスを作成するのが好きです。デフォルトを初期化し、組み込みのシリアル化クラスを介してアプリからファイルからロードできます。その後、アプリはそれを必要なものに渡します。解析や変換に煩わされたり、設定文字列に手を加えたり、ゴミをキャストしたりする必要はありません。そしてそれは、構成可能な方法でコードそれがプリセットサーバーまたはとしてロード/保存する必要があるシナリオ、および使用のために簡単に方法あなたのユニットテストでの使用に容易になります。


1
解析や変換に煩わされたり、設定文字列に手を加えたり、ゴミをキャストしたりする必要はありません。どういう意味ですか?
enzom83

1
1. AppConfigの結果(文字列)を取得して、必要なものに解析する必要はありません。2.必要な設定パラメータを選択するために、文字列を指定する必要はありません。それは人的エラーが発生しやすく、リファクタリングが難しいものの1つです。3。プログラムで値を設定する場合、他の型変換を行う必要はありません。
テラスティン

2

少なくとも.NETでは、独自の厳密に型指定された構成オブジェクトを簡単に作成できます。これを参照してください 。簡単な例については、MSDNの記事を。

ヒント:構成クラスをインターフェースでラップし、アプリケーションがそれと対話できるようにします。テストや利益のために偽の設定を簡単に挿入できます。


私はMSDNの記事を読みました。興味深いことに、ConfigurationElementクラスの各サブクラスは本質的に値のグループを表すことができ、どの値に対してもバリデーターを指定できます。しかし、たとえば、4つの確率で構成される構成要素を表す場合、合計は1でなければならないため、4つの確率値は相関しています。この構成要素を検証するにはどうすればよいですか?
enzom83

1
私は一般に、これは低レベルの構成検証のためのものではないと主張します-AssertConfigrationIsValidメソッドを構成クラスに追加して、これをコードでカバーします。それがうまくいかない場合は、属性の基本クラスを拡張することで、独自の構成バリデーターを作成できると思います。比較バリデーターがあるので、明らかにクロスプロパティを話すことができます。
ワイアットバーネット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.