Beste Möglichkeit, Anwendungseinstellungen zu laden

24

Eine einfache Möglichkeit, die Einstellungen einer Java-Anwendung beizubehalten, ist eine Textdatei mit der Erweiterung ".properties", die die Kennung jeder Einstellung enthält, die einem bestimmten Wert zugeordnet ist (dieser Wert kann eine Zahl, eine Zeichenfolge, ein Datum usw. sein). . C # verwendet einen ähnlichen Ansatz, aber die Textdatei muss den Namen "App.config" haben. In beiden Fällen müssen Sie im Quellcode eine bestimmte Klasse zum Lesen von Einstellungen initialisieren: Diese Klasse verfügt über eine Methode, die den Wert (als Zeichenfolge) zurückgibt, der dem angegebenen Einstellungsbezeichner zugeordnet ist.

// 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"];
// ...

In beiden Fällen sollten wir die aus der Konfigurationsdatei geladenen Zeichenfolgen analysieren und die konvertierten Werte den zugehörigen typisierten Objekten zuweisen (während dieser Phase können Analysefehler auftreten). Nach dem Analyseschritt müssen wir überprüfen, ob die Einstellungswerte zu einem bestimmten Gültigkeitsbereich gehören. Beispielsweise sollte die maximale Größe einer Warteschlange ein positiver Wert sein. Einige Werte können in Beziehung stehen (Beispiel: min <max ), und so weiter.

Angenommen, die Anwendung sollte die Einstellungen sofort nach dem Start laden. Mit anderen Worten, der erste Vorgang, den die Anwendung ausführt, ist das Laden der Einstellungen. Ungültige Werte für die Einstellungen müssen automatisch durch Standardwerte ersetzt werden. Wenn dies bei einer Gruppe von verwandten Einstellungen der Fall ist, werden diese Einstellungen alle mit Standardwerten festgelegt.

Am einfachsten können Sie diese Vorgänge ausführen, indem Sie eine Methode erstellen, die zuerst alle Einstellungen analysiert, dann die geladenen Werte überprüft und schließlich die Standardwerte festlegt. Bei dieser Vorgehensweise ist die Wartung jedoch schwierig: Da die Anzahl der Einstellungen während der Entwicklung der Anwendung zunimmt, wird es immer schwieriger, den Code zu aktualisieren.

Um dieses Problem zu lösen, hatte ich mir überlegt, das Muster der Template-Methode wie folgt zu verwenden.

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;
    }
}

Das Problem ist, dass wir auf diese Weise für jede Einstellung eine neue Klasse erstellen müssen, auch für einen einzelnen Wert. Gibt es andere Lösungen für diese Art von Problem?

Zusammenfassend:

  1. Einfache Wartung: Zum Beispiel durch Hinzufügen eines oder mehrerer Parameter.
  2. Erweiterbarkeit: Eine erste Version der Anwendung kann eine einzelne Konfigurationsdatei lesen, spätere Versionen bieten jedoch möglicherweise die Möglichkeit eines Mehrbenutzer-Setups (der Administrator richtet eine Grundkonfiguration ein, Benutzer können nur bestimmte Einstellungen vornehmen usw.).
  3. Objektorientiertes Design.
enzom83
quelle
Wenn Sie die Verwendung einer .properties-Datei vorschlagen, wo speichern Sie die Datei während der Entwicklung, des Testens und der anschließenden Produktion, da sie sich hoffentlich nicht am selben Speicherort befindet. Dann muss die Anwendung mit einem beliebigen Speicherort (dev, test oder prod) neu kompiliert werden, es sei denn, Sie können die Umgebung zur Laufzeit erkennen und haben dann fest codierte Speicherorte in Ihrer App.

Antworten:

8

Im Wesentlichen wird die externe Konfigurationsdatei als YAML-Dokument codiert. Diese werden dann beim Start der Anwendung analysiert und einem Konfigurationsobjekt zugeordnet.

Das Endergebnis ist robust und vor allem einfach zu verwalten.

Gary Rowe
quelle
7

Betrachten wir dies unter zwei Gesichtspunkten: der API zum Abrufen der Konfigurationswerte und des Speicherformats. Sie sind oft verwandt, aber es ist hilfreich, sie separat zu betrachten.

Konfigurations-API

Das Muster der Template-Methode ist sehr allgemein, aber ich frage mich, ob Sie diese Allgemeinheit wirklich brauchen. Sie benötigen eine Klasse für jeden Typ von Konfigurationswert. Hast du wirklich so viele Typen? Ich würde vermuten, dass Sie mit nur einer Handvoll auskommen können: Zeichenketten, Ints, Floats, Booleans und Enums. In Anbetracht dessen könnten Sie eine ConfigKlasse haben, die eine Handvoll Methoden enthält:

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)

(Ich glaube, ich habe die Generika für das Letzte richtig verstanden.)

Grundsätzlich weiß jede Methode, wie das Parsen des Zeichenfolgenwerts aus der Konfigurationsdatei und das Behandeln von Fehlern behandelt werden und gegebenenfalls der Standardwert zurückgegeben wird. Die Bereichsprüfung für die numerischen Werte ist wahrscheinlich ausreichend. Möglicherweise möchten Sie Überladungen haben, bei denen die Bereichswerte weggelassen werden. Dies entspricht der Angabe eines Bereichs von Integer.MIN_VALUE, Integer.MAX_VALUE. Eine Enumeration ist eine typsichere Methode zum Überprüfen eines Strings anhand einer festgelegten Menge von Strings.

Es gibt einige Dinge, die nicht behandelt werden, wie z. B. mehrere Werte, Werte, die miteinander in Beziehung stehen, dynamische Tabellensuchen usw. Sie könnten spezielle Parsing- und Validierungsroutinen für diese schreiben, aber wenn dies zu kompliziert wird, würde ich anfangen zu hinterfragen ob Sie versuchen, zu viel mit einer Konfigurationsdatei zu tun.

Speicherformat

Java-Eigenschaftendateien eignen sich gut zum Speichern einzelner Schlüssel-Wert-Paare und unterstützen die oben beschriebenen Wertetypen ziemlich gut. Sie könnten auch andere Formate wie XML oder JSON in Betracht ziehen, aber diese sind wahrscheinlich zu teuer, es sei denn, Sie haben Daten verschachtelt oder wiederholt. An diesem Punkt scheint es weit über eine Konfigurationsdatei hinauszugehen ....

Telastyn erwähnte serialisierte Objekte. Dies ist eine Möglichkeit, obwohl die Serialisierung ihre Schwierigkeiten hat. Es ist eine Binärdatei, kein Text, daher ist es schwierig, die Werte zu sehen und zu bearbeiten. Sie müssen sich mit der Serialisierungskompatibilität befassen. Wenn in der serialisierten Eingabe Werte fehlen (z. B. Sie haben der Config-Klasse ein Feld hinzugefügt und lesen eine alte serialisierte Form davon), werden die neuen Felder mit null / null initialisiert. Sie müssen eine Logik schreiben, um zu bestimmen, ob ein anderer Standardwert eingegeben werden soll. Aber zeigt eine Null das Fehlen eines Konfigurationswerts an, oder wurde der Wert als Null angegeben? Jetzt müssen Sie diese Logik debuggen. Schließlich (nicht sicher, ob dies ein Problem ist) müssen Sie möglicherweise noch Werte im serialisierten Objektstrom überprüfen. Es ist möglich (wenn auch unpraktisch), dass ein böswilliger Benutzer einen serialisierten Objektstrom unerkennbar ändert.

Ich würde sagen, ich halte mich an die Eigenschaften, wenn es überhaupt möglich ist.

Stuart Marks
quelle
2
Hey Stuart, schön dich hier zu sehen :-). Ich werde zu Stuarts Antwort hinzufügen, dass ich denke, dass Ihre tempalte Idee in Java funktionieren wird, wenn Sie Generics verwenden, um stark zu tippen, so dass Sie das Setzen von <T> auch als Option haben könnten.
Martijn Verburg
@StuartMarks: Nun, das war meine erste Idee nur zu schreiben , ConfigKlasse und den Ansatz von Ihnen vorgeschlagenen verwenden: getInt(), getByte(), getBoolean(), etc .. Continuing mit dieser Idee, ich alle zum ersten Mal liest die Werte und ich konnte jeden Wert auf ein Flag assoziieren (Dieses Flag ist falsch, wenn während der Deserialisierung ein Problem aufgetreten ist, z. B. Analysefehler.) Danach konnte ich eine Validierungsphase für alle geladenen Werte starten und Standardwerte festlegen.
Enzom83
2
Ich würde eine Art JAXB- oder YAML-Ansatz bevorzugen, um alle Details zu vereinfachen.
Gary Rowe
4

Wie ich es gemacht habe:

Initialisieren Sie alles auf die Standardwerte.

Analysieren Sie die Datei und speichern Sie die Werte, während Sie fortfahren. Die gesetzten Stellen sind dafür verantwortlich, dass die Werte akzeptabel sind. Schlechte Werte werden ignoriert (und behalten daher den Standardwert bei.)

Loren Pechtel
quelle
Dies könnte auch eine gute Idee sein: Eine Klasse, die die Werte der Einstellungen lädt, muss möglicherweise nur handeln, um die Werte aus der Konfigurationsdatei zu laden, d. H., Sie ist möglicherweise nur für das Laden der Werte verantwortlich Aus der Konfigurationsdatei; Stattdessen ist jedes Modul (das einige Einstellungen verwendet) dafür verantwortlich, die Werte zu validieren.
Enzom83
2

Gibt es andere Lösungen für diese Art von Problem?

Wenn alles, was Sie brauchen, eine einfache Konfiguration ist, mache ich gerne eine einfache alte Klasse dafür. Es initialisiert die Standardeinstellungen und kann von der App über die eingebauten Serialisierungsklassen aus der Datei geladen werden. Die App gibt es dann an Dinge weiter, die es brauchen. Kein Hin und Her mit Parsing oder Konvertierungen, kein Herumdrehen mit Konfigurations-Strings, kein Casting-Müll. Und es macht die Konfiguration Art und Weise leichten Einsatz für Szenarien , in-Code , wo sie benötigt werden , gespeichert / geladen vom Server oder als Voreinstellungen und Weise leichten Einsatz in Ihren Unit - Tests.

Telastyn
quelle
1
Kein Hin und Her mit Parsing oder Konvertierungen, kein Herumdrehen mit Konfigurations-Strings, kein Casting-Müll. Was meinst du?
Enzom83
1
Ich meine Folgendes: 1. Sie müssen das AppConfig-Ergebnis (eine Zeichenfolge) nicht in die gewünschten Werte zerlegen. 2. Sie müssen keine Art von Zeichenfolge angeben, um den gewünschten Konfigurationsparameter auszuwählen. Dies ist eines der Dinge, die für menschliches Versagen anfällig und schwer umzugestalten sind, und 3. Sie müssen keine weiteren Typkonvertierungen vornehmen, wenn Sie den Wert programmgesteuert festlegen möchten.
Telastyn
2

Zumindest in .NET können Sie ziemlich einfach Ihre eigenen stark typisierten Konfigurationsobjekte erstellen. In diesem MSDN-Artikel finden Sie ein kurzes Beispiel.

Protip: Hüllen Sie Ihre Konfigurationsklasse in eine Schnittstelle und lassen Sie Ihre Anwendung damit sprechen. Macht es einfach, gefälschte Konfigurationen für Tests oder für den Profit zu erstellen.

Wyatt Barnett
quelle
Ich habe den MSDN-Artikel gelesen: Interessanterweise kann jede Unterklasse einer ConfigurationElementKlasse eine Gruppe von Werten darstellen, und für jeden Wert kann ein Validator angegeben werden. Wenn ich zum Beispiel ein Konfigurationselement darstellen wollte, das aus vier Wahrscheinlichkeitswerten besteht, werden die vier Wahrscheinlichkeitswerte korreliert, da ihre Summe gleich 1 sein muss. Wie validiere ich dieses Konfigurationselement?
Enzom83
1
Im Allgemeinen würde ich argumentieren, dass dies nichts für die Konfigurationsvalidierung auf niedriger Ebene ist - ich würde meiner Konfigurationsklasse eine AssertConfigrationIsValid-Methode hinzufügen, um dies im Code abzudecken. Wenn das bei Ihnen nicht funktioniert, können Sie, glaube ich, Ihre eigenen Konfigurationsprüfprogramme erstellen, indem Sie die Basisklasse des Attributs erweitern. Sie haben einen Vergleichsprüfer, damit sie offenbar eigenschaftsübergreifend sprechen können.
Wyatt Barnett