Il modo migliore per caricare le impostazioni dell'applicazione


24

Un modo semplice per mantenere le impostazioni di un'applicazione Java è rappresentato da un file di testo con estensione ".properties" contenente l'identificatore di ciascuna impostazione associata a un valore specifico (questo valore può essere un numero, una stringa, una data, ecc.) . C # usa un approccio simile, ma il file di testo deve essere chiamato "App.config". In entrambi i casi, nel codice sorgente è necessario inizializzare una classe specifica per la lettura delle impostazioni: questa classe ha un metodo che restituisce il valore (come stringa) associato all'identificatore di impostazione specificato.

// 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 entrambi i casi dovremmo analizzare le stringhe caricate dal file di configurazione e assegnare i valori convertiti ai relativi oggetti digitati (durante questa fase potrebbero verificarsi errori di analisi). Dopo la fase di analisi, dobbiamo verificare che i valori di impostazione appartengano a un dominio specifico di validità: ad esempio, la dimensione massima di una coda deve essere un valore positivo, alcuni valori potrebbero essere correlati (esempio: min <max ), e così via.

Supponiamo che l'applicazione dovrebbe caricare le impostazioni non appena si avvia: in altre parole, la prima operazione eseguita dall'applicazione è caricare le impostazioni. Eventuali valori non validi per le impostazioni devono essere sostituiti automaticamente con valori predefiniti: se ciò accade a un gruppo di impostazioni correlate, tali impostazioni vengono tutte impostate con valori predefiniti.

Il modo più semplice per eseguire queste operazioni è creare un metodo che prima analizza tutte le impostazioni, quindi controlla i valori caricati e infine imposta tutti i valori predefiniti. Tuttavia, la manutenzione è difficile se si utilizza questo approccio: poiché il numero di impostazioni aumenta durante lo sviluppo dell'applicazione, diventa sempre più difficile aggiornare il codice.

Per risolvere questo problema, avevo pensato di utilizzare il modello Metodo modello, come segue.

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

Il problema è che in questo modo dobbiamo creare una nuova classe per ogni impostazione, anche per un singolo valore. Esistono altre soluzioni a questo tipo di problema?

In sintesi:

  1. Facile manutenzione: ad esempio l'aggiunta di uno o più parametri.
  2. Estensibilità: una prima versione dell'applicazione potrebbe leggere un singolo file di configurazione, ma le versioni successive potrebbero dare la possibilità di una configurazione multiutente (admin imposta una configurazione di base, gli utenti possono impostare solo determinate impostazioni, ecc.).
  3. Design orientato agli oggetti.

Per coloro che propongono l'uso di un file .properties in cui è possibile archiviare il file stesso durante lo sviluppo, il test e quindi la produzione perché, si spera, non si troverà nella stessa posizione. Quindi l'applicazione dovrà essere ricompilata con qualsiasi posizione (sviluppo, test o prod) a meno che non sia possibile rilevare l'ambiente in fase di esecuzione e quindi disporre di posizioni codificate all'interno dell'app.

Risposte:


8

Essenzialmente il file di configurazione esterno è codificato come documento YAML. Questo viene quindi analizzato durante l'avvio dell'applicazione e mappato su un oggetto di configurazione.

Il risultato finale è robusto e soprattutto semplice da gestire.


7

Consideriamo questo da due punti di vista: l'API per ottenere i valori di configurazione e il formato di archiviazione. Sono spesso correlati, ma è utile considerarli separatamente.

API di configurazione

Il modello di modello è molto generale, ma mi chiedo se hai davvero bisogno di quella generalità. Avresti bisogno di una classe per ogni tipo di valore di configurazione. Ne hai davvero tanti tipi? Immagino che potresti cavartela con solo una manciata: stringhe, ints, float, booleani ed enumerazioni. Dati questi, potresti avere una Configclasse che ha una manciata di metodi su di essa:

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)

(Penso di aver ottenuto i generici su quell'ultimo a destra.)

Fondamentalmente ogni metodo sa come gestire l'analisi del valore di stringa dal file di configurazione e gestire gli errori e, se appropriato, restituire il valore predefinito. Il controllo dell'intervallo per i valori numerici è probabilmente sufficiente. Potresti voler avere sovraccarichi che omettono i valori dell'intervallo, il che equivarrebbe a fornire un intervallo di Integer.MIN_VALUE, Integer.MAX_VALUE. Un Enum è un modo sicuro per la convalida di una stringa rispetto a un set fisso di stringhe.

Ci sono alcune cose che questo non gestisce, come valori multipli, valori correlati, ricerche di tabelle dinamiche, ecc. Potresti scrivere routine di analisi e convalida specializzate per questi, ma se questo diventa troppo complicato, vorrei iniziare a mettere in discussione se stai provando a fare troppo con un file di configurazione.

Formato di archiviazione

I file delle proprietà Java sembrano andare bene per la memorizzazione di singole coppie chiave-valore e supportano abbastanza bene i tipi di valori che ho descritto sopra. Potresti anche considerare altri formati come XML o JSON, ma questi sono probabilmente eccessivi a meno che tu non abbia dati nidificati o ripetuti. A quel punto sembra ben oltre un file di configurazione ....

Telastyn menzionava oggetti serializzati. Questa è una possibilità, sebbene la serializzazione abbia le sue difficoltà. È binario, non testo, quindi è difficile vedere e modificare i valori. Devi gestire la compatibilità con la serializzazione. Se nell'input serializzato mancano dei valori (ad esempio, è stato aggiunto un campo alla classe Config e ne si sta leggendo un vecchio modulo serializzato), i nuovi campi vengono inizializzati su null / zero. Devi scrivere la logica per determinare se inserire un altro valore predefinito. Ma uno zero indica l'assenza di un valore di configurazione o è stato specificato che è zero? Ora devi eseguire il debug di questa logica. Infine (non sono sicuro che si tratti di un problema) potrebbe essere ancora necessario convalidare i valori nel flusso di oggetti serializzato. È possibile (anche se scomodo) per un utente malintenzionato modificare un flusso di oggetti serializzato in modo non rilevabile.

Direi di attenersi alle proprietà se possibile.


2
Ehi Stuart, piacere di vederti qui :-). Aggiungerò alla risposta di Stuarts che penso che la tua idea di tempalte funzionerà in Java se usi Generics per scrivere fortemente, quindi potresti avere anche l'opzione <T>.
Martijn Verburg,

@StuartMarks: Beh, la mia prima idea era semplicemente di scrivere una Configclasse e di utilizzare l'approccio proposto da te: getInt(), getByte(), getBoolean(), ecc .. Continuando con questa idea, ho letto tutti i valori e ho potuto associare ogni valore ad una bandiera (questo flag è falso se si è verificato un problema durante la deserializzazione, ad esempio l'analisi degli errori). Successivamente, ho potuto avviare una fase di convalida per tutti i valori caricati e impostare qualsiasi valore predefinito.
enzom83

2
Preferirei una sorta di approccio JAXB o YAML per semplificare tutti i dettagli.
Gary Rowe,

4

Come l'ho fatto:

Inizializza tutto sui valori predefiniti.

Analizza il file, memorizzando i valori mentre procedi. Le posizioni impostate sono responsabili di assicurare che i valori siano accettabili, i valori errati vengono ignorati (e quindi mantengono il valore predefinito).


Potrebbe anche essere una buona idea: una classe che carica i valori delle impostazioni potrebbe dover occuparsi solo di caricare i valori dal file di configurazione, ovvero la sua responsabilità potrebbe essere solo quella di caricare i valori Dal file di configurazione; invece ogni modulo (che utilizza alcune impostazioni) avrà la responsabilità di validare i valori.
enzom83

2

Esistono altre soluzioni a questo tipo di problema?

Se tutto ciò che serve è una semplice configurazione, mi piace creare una vecchia classe per questo. Inizializza i valori predefiniti e può essere caricato da file dall'app tramite le classi di serializzazione integrate. L'app quindi lo passa alle cose che ne hanno bisogno. Nessuna confusione con analisi o conversioni, nessuna confusione con stringhe di configurazione, nessuna immondizia di lancio. E rende la configurazione modo più semplice da usare per gli scenari in-codice in cui ha bisogno di essere salvato / caricato dal server o come preset e modo più semplice da utilizzare per le prove di unità.


1
Nessuna confusione con analisi o conversioni, nessuna confusione con stringhe di configurazione, nessuna immondizia di lancio. Cosa intendi?
enzom83

1
Voglio dire che: 1. Non è necessario prendere il risultato AppConfig (una stringa) e analizzarlo in quello che si desidera. 2. Non è necessario specificare alcun tipo di stringa per selezionare il parametro di configurazione desiderato; questa è una di quelle cose che sono inclini a errori umani e difficili da refactoring e 3. non è necessario quindi fare altre conversioni di tipo quando si imposta il valore a livello di codice.
Telastyn,

2

Almeno in .NET, puoi creare facilmente i tuoi oggetti di configurazione fortemente tipizzati: vedi questo articolo MSDN per un rapido esempio.

Protip: avvolgi la tua classe config in un'interfaccia e lascia che l'applicazione parli con quello. Semplifica l'iniezione di configurazioni false per test o a scopo di lucro.


Ho letto l'articolo MSDN: è interessante, essenzialmente ogni sottoclasse di ConfigurationElementclasse potrebbe rappresentare un gruppo di valori e per qualsiasi valore è possibile specificare un validatore. Ma se ad esempio volessi rappresentare un elemento di configurazione che consiste di quattro probabilità, i quattro valori di probabilità sono correlati, poiché la loro somma deve essere uguale a 1. Come convalidare questo elemento di configurazione?
enzom83

1
Direi generalmente che non è qualcosa per la convalida della configurazione di basso livello - aggiungerei un metodo AssertConfigrationIsValid alla mia classe di configurazione per coprire questo nel codice. Se per te non funziona, penso che puoi creare i tuoi validatori di configurazione estendendo la classe base dell'attributo. Hanno un validatore di confronto, quindi ovviamente possono parlare di proprietà incrociata.
Wyatt Barnett,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.