Cara terbaik untuk memuat pengaturan aplikasi

24

Cara sederhana untuk menjaga pengaturan aplikasi Java diwakili oleh file teks dengan ekstensi ".properties" yang berisi pengidentifikasi setiap pengaturan yang terkait dengan nilai tertentu (nilai ini mungkin berupa angka, string, tanggal, dll.) . C # menggunakan pendekatan yang serupa, tetapi file teks harus dinamai "App.config". Dalam kedua kasus, dalam kode sumber Anda harus menginisialisasi kelas tertentu untuk pengaturan membaca: kelas ini memiliki metode yang mengembalikan nilai (sebagai string) yang terkait dengan pengidentifikasi pengaturan yang ditentukan.

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

Dalam kedua kasus kita harus mengurai string yang dimuat dari file konfigurasi dan menetapkan nilai yang dikonversi ke objek yang diketik terkait (kesalahan penguraian dapat terjadi selama fase ini). Setelah langkah penguraian, kita harus memeriksa bahwa nilai pengaturan milik domain validitas tertentu: misalnya, ukuran maksimum antrian harus menjadi nilai positif, beberapa nilai mungkin terkait (misalnya: min <maks ), dan seterusnya.

Misalkan aplikasi harus memuat pengaturan segera setelah dimulai: dengan kata lain, operasi pertama yang dilakukan oleh aplikasi adalah memuat pengaturan. Setiap nilai yang tidak valid untuk pengaturan harus diganti secara otomatis dengan nilai default: jika ini terjadi pada sekelompok pengaturan terkait, semua pengaturan tersebut diatur dengan nilai default.

Cara termudah untuk melakukan operasi ini adalah dengan membuat metode yang mem-parsing semua pengaturan, kemudian memeriksa nilai yang dimuat dan akhirnya menetapkan nilai default apa pun. Namun pemeliharaan sulit jika Anda menggunakan pendekatan ini: karena jumlah pengaturan meningkat saat mengembangkan aplikasi, semakin sulit untuk memperbarui kode.

Untuk mengatasi masalah ini, saya berpikir untuk menggunakan pola Metode Templat , sebagai berikut.

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

Masalahnya adalah bahwa dengan cara ini kita perlu membuat kelas baru untuk setiap pengaturan, bahkan untuk nilai tunggal. Apakah ada solusi lain untuk masalah seperti ini?

Singkatnya:

  1. Perawatan yang mudah: misalnya, penambahan satu atau lebih parameter.
  2. Extensibility: versi pertama aplikasi dapat membaca file konfigurasi tunggal, tetapi versi yang lebih baru dapat memberikan kemungkinan pengaturan multi-pengguna (admin mengatur konfigurasi dasar, pengguna hanya dapat mengatur pengaturan tertentu, dll.).
  3. Desain berorientasi objek.
enzom83
sumber
Bagi mereka yang mengusulkan penggunaan file properti. Di mana Anda menyimpan file itu sendiri selama pengembangan, pengujian dan kemudian produksi karena tidak akan berada di lokasi yang sama, semoga. Maka aplikasi tersebut harus dikompilasi ulang dengan lokasi apa pun (dev, test atau prod) kecuali Anda dapat mendeteksi lingkungan saat runtime dan kemudian memiliki lokasi hardcoded di dalam aplikasi Anda.

Jawaban:

8

Pada dasarnya file konfigurasi eksternal dikodekan sebagai dokumen YAML. Ini kemudian diuraikan selama aplikasi start up dan dipetakan ke objek konfigurasi.

Hasil akhirnya kuat dan terutama mudah dikelola.

Gary Rowe
sumber
7

Mari kita pertimbangkan ini dari dua sudut pandang: API untuk mendapatkan nilai konfigurasi, dan format penyimpanan. Mereka sering terkait, tetapi sangat membantu untuk mempertimbangkannya secara terpisah.

API konfigurasi

Pola Metode Templat sangat umum, tetapi saya mempertanyakan apakah Anda benar-benar membutuhkan generalisasi itu. Anda membutuhkan kelas untuk setiap jenis nilai konfigurasi. Apakah Anda benar-benar memiliki banyak tipe? Saya kira Anda bisa bertahan hanya dengan segelintir: string, int, float, boolean, dan enum. Dengan ini, Anda dapat memiliki Configkelas yang memiliki beberapa metode di atasnya:

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)

(Saya pikir saya mendapatkan obat generik yang terakhir benar.)

Pada dasarnya setiap metode tahu bagaimana menangani parsing nilai string dari file konfigurasi dan untuk menangani kesalahan dan untuk mengembalikan nilai default jika sesuai. Pengecekan rentang untuk nilai numerik mungkin cukup. Anda mungkin ingin memiliki kelebihan yang menghilangkan nilai rentang, yang akan setara dengan menyediakan rentang Integer.MIN_VALUE, Integer.MAX_VALUE. Enum adalah cara yang aman untuk memvalidasi string terhadap serangkaian string yang ditetapkan.

Ada beberapa hal yang tidak ditangani, seperti beberapa nilai, nilai yang saling terkait, pencarian tabel dinamis, dll. Anda bisa menulis rutin parsing dan validasi untuk ini, tetapi jika ini menjadi terlalu rumit, saya akan mulai mempertanyakan apakah Anda mencoba melakukan terlalu banyak dengan file konfigurasi.

Format Penyimpanan

File properti Java tampak baik untuk menyimpan pasangan nilai kunci individual, dan mereka mendukung jenis nilai yang saya jelaskan di atas dengan cukup baik. Anda juga dapat mempertimbangkan format lain seperti XML atau JSON, tetapi ini mungkin berlebihan kecuali jika Anda memiliki data bersarang atau berulang. Pada titik itu tampaknya jauh melampaui file konfigurasi ....

Telastyn menyebutkan objek berseri. Ini adalah suatu kemungkinan, meskipun serialisasi memang memiliki kesulitannya. Ini biner, bukan teks, jadi sulit untuk melihat dan mengedit nilai. Anda harus berurusan dengan kompatibilitas serialisasi. Jika nilai tidak ada dari input serial (mis., Anda menambahkan bidang ke kelas Config dan Anda membaca bentuk serial lama dari itu), bidang baru diinisialisasi ke nol / nol. Anda harus menulis logika untuk menentukan apakah akan mengisi beberapa nilai default lainnya. Tetapi apakah nol menunjukkan tidak adanya nilai konfigurasi, atau apakah ditetapkan sebagai nol? Sekarang Anda harus men-debug logika ini. Akhirnya (tidak yakin apakah ini masalah) Anda masih mungkin perlu memvalidasi nilai dalam aliran objek serial. Itu mungkin (meskipun tidak nyaman) bagi pengguna jahat untuk memodifikasi aliran objek berseri tidak terdeteksi.

Saya akan mengatakan untuk tetap dengan properti jika memungkinkan.

Stuart Marks
sumber
2
Hei Stuart, senang bertemu dengan Anda di sini :-). Saya akan menambahkan ke jawaban Stuarts bahwa saya pikir ide tempalte Anda akan berfungsi di Jawa jika Anda menggunakan Generik untuk mengetik dengan kuat, sehingga Anda dapat memiliki Pengaturan <T> sebagai opsi juga.
Martijn Verburg
@StuartMarks: Nah, ide pertama saya adalah hanya untuk menulis Configkelas dan menggunakan pendekatan yang diusulkan oleh Anda: getInt(), getByte(), getBoolean(), dll .. Melanjutkan dengan ide ini, saya pertama kali membaca semua nilai-nilai dan saya bisa mengasosiasikan setiap nilai untuk bendera (flag ini salah jika masalah terjadi selama deserialization, misalnya kesalahan penguraian). Setelah itu, saya bisa memulai fase validasi untuk semua nilai yang dimuat dan mengatur nilai default apa pun.
enzom83
2
Saya lebih suka semacam pendekatan JAXB atau YAML untuk menyederhanakan semua detail.
Gary Rowe
4

Bagaimana saya melakukannya:

Inisialisasi semuanya ke nilai default.

Mengurai file, menyimpan nilai saat Anda pergi. Tempat yang ditetapkan bertanggung jawab untuk memastikan nilai-nilai dapat diterima, nilai-nilai buruk diabaikan (dan dengan demikian mempertahankan nilai default.)

Loren Pechtel
sumber
Ini juga bisa menjadi ide yang baik: kelas yang memuat nilai-nilai pengaturan mungkin harus berurusan hanya untuk memuat nilai-nilai dari file konfigurasi, yaitu, tanggung jawabnya hanya bisa menjadi orang yang memuat nilai-nilai Dari file konfigurasi; alih-alih setiap modul (yang menggunakan beberapa pengaturan) akan memiliki tanggung jawab untuk memvalidasi nilai-nilai.
enzom83
2

Apakah ada solusi lain untuk masalah seperti ini?

Jika yang Anda butuhkan adalah konfigurasi sederhana, saya ingin membuat kelas lama yang sederhana untuk itu. Ini menginisialisasi default, dan dapat diambil dari file oleh aplikasi melalui kelas serialisasi bawaan. Aplikasi kemudian membagikannya ke hal-hal yang membutuhkannya. Tidak ada keributan dengan parsing atau konversi, tidak main-main dengan string konfigurasi, tidak ada sampah casting. Dan itu membuat cara konfigurasi lebih mudah digunakan untuk skenario dalam kode di mana ia perlu disimpan / dimuat dari server atau sebagai preset, dan cara lebih mudah untuk digunakan dalam pengujian unit Anda.

Telastyn
sumber
1
Tidak ada keributan dengan parsing atau konversi, tidak main-main dengan string konfigurasi, tidak ada sampah casting. Apa maksudmu?
enzom83
1
Maksud saya: 1. Anda tidak perlu mengambil hasil AppConfig (string) dan menguraikannya menjadi apa yang Anda inginkan. 2. Anda tidak perlu menentukan string apa pun untuk memilih parameter konfigurasi mana yang Anda inginkan; itu salah satu hal yang rentan terhadap kesalahan manusia dan sulit untuk refactor dan 3. Anda tidak perlu kemudian melakukan konversi jenis lain ketika akan menetapkan nilai secara pemrograman.
Telastyn
2

Setidaknya di .NET, Anda dapat dengan mudah membuat objek konfigurasi yang sangat diketik sendiri - lihat artikel MSDN ini untuk contoh cepat.

Protip: bungkus kelas konfigurasi Anda dalam sebuah antarmuka dan biarkan aplikasi Anda membicarakannya. Memudahkan untuk menyuntikkan konfigurasi palsu untuk pengujian atau untuk keuntungan.

Wyatt Barnett
sumber
Saya membaca artikel MSDN: ini menarik, pada dasarnya setiap subkelas ConfigurationElementkelas dapat mewakili sekelompok nilai, dan untuk nilai apa pun Anda dapat menentukan validator. Tetapi jika misalnya saya ingin mewakili elemen konfigurasi yang terdiri dari empat probabilitas, empat nilai probabilitas berkorelasi, karena jumlahnya harus sama dengan 1. Bagaimana cara memvalidasi elemen konfigurasi ini?
enzom83
1
Saya biasanya berpendapat bahwa itu bukan sesuatu untuk validasi konfigurasi tingkat rendah - saya akan menambahkan metode AssertConfigrationIsValid ke kelas konfigurasi saya untuk membahas hal ini dalam kode. Jika itu tidak berhasil untuk Anda, saya pikir Anda dapat membuat validator konfigurasi Anda sendiri dengan memperluas kelas dasar atribut. Mereka memiliki validator pembanding sehingga mereka jelas dapat berbicara lintas properti.
Wyatt Barnett