Cách tốt nhất để tải cài đặt ứng dụng


24

Một cách đơn giản để giữ các cài đặt của ứng dụng Java được biểu thị bằng tệp văn bản có phần mở rộng ".properations" chứa định danh của từng cài đặt được liên kết với một giá trị cụ thể (giá trị này có thể là số, chuỗi, ngày, v.v.) . C # sử dụng một cách tiếp cận tương tự, nhưng tệp văn bản phải được đặt tên là "App.config". Trong cả hai trường hợp, trong mã nguồn, bạn phải khởi tạo một lớp cụ thể để đọc cài đặt: lớp này có một phương thức trả về giá trị (dưới dạng chuỗi) được liên kết với mã định danh cài đặt đã chỉ định.

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

Trong cả hai trường hợp, chúng ta nên phân tích các chuỗi được tải từ tệp cấu hình và gán các giá trị được chuyển đổi cho các đối tượng được nhập có liên quan (lỗi phân tích cú pháp có thể xảy ra trong giai đoạn này). Sau bước phân tích cú pháp, chúng tôi phải kiểm tra xem các giá trị cài đặt có thuộc về một miền hợp lệ cụ thể không: ví dụ: kích thước tối đa của hàng đợi phải là giá trị dương, một số giá trị có thể liên quan (ví dụ: min <max ), v.v.

Giả sử rằng ứng dụng sẽ tải các cài đặt ngay khi khởi động: nói cách khác, thao tác đầu tiên được thực hiện bởi ứng dụng là tải các cài đặt. Mọi giá trị không hợp lệ cho các cài đặt phải được thay thế tự động bằng các giá trị mặc định: nếu điều này xảy ra với một nhóm các cài đặt liên quan, các cài đặt đó đều được đặt với các giá trị mặc định.

Cách dễ nhất để thực hiện các thao tác này là tạo một phương thức trước tiên phân tích tất cả các cài đặt, sau đó kiểm tra các giá trị được tải và cuối cùng đặt bất kỳ giá trị mặc định nào. Tuy nhiên, việc bảo trì rất khó khăn nếu bạn sử dụng phương pháp này: khi số lượng cài đặt tăng lên trong khi phát triển ứng dụng, việc cập nhật mã ngày càng khó khăn hơn.

Để giải quyết vấn đề này, tôi đã nghĩ đến việc sử dụng mẫu Phương thức mẫu, như sau.

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

Vấn đề là theo cách này, chúng ta cần tạo một lớp mới cho mỗi cài đặt, thậm chí cho một giá trị duy nhất. Có giải pháp nào khác cho loại vấn đề này không?

Tóm tắt:

  1. Bảo trì dễ dàng: ví dụ: việc thêm một hoặc nhiều tham số.
  2. Khả năng mở rộng: phiên bản đầu tiên của ứng dụng có thể đọc một tệp cấu hình duy nhất, nhưng các phiên bản sau có thể cung cấp khả năng thiết lập nhiều người dùng (quản trị viên thiết lập cấu hình cơ bản, người dùng chỉ có thể đặt một số cài đặt nhất định, v.v.).
  3. Thiết kế hướng đối tượng.

Đối với những người đề xuất sử dụng tệp .properations, nơi bạn lưu trữ tệp đó trong quá trình phát triển, thử nghiệm và sau đó sản xuất vì nó sẽ không ở cùng một vị trí, hy vọng. Sau đó, ứng dụng sẽ cần được biên dịch lại với bất kỳ vị trí nào (dev, test hoặc prod) trừ khi bạn có thể phát hiện môi trường trong thời gian chạy và sau đó có các vị trí được mã hóa cứng trong ứng dụng của bạn.

Câu trả lời:


8

Về cơ bản, tệp cấu hình bên ngoài được mã hóa dưới dạng tài liệu YAML. Điều này sau đó được phân tích cú pháp trong quá trình khởi động ứng dụng và ánh xạ tới một đối tượng cấu hình.

Kết quả cuối cùng là mạnh mẽ và trên hết là đơn giản để quản lý.


7

Chúng ta hãy xem xét điều này từ hai quan điểm: API để có được các giá trị cấu hình và định dạng lưu trữ. Chúng thường liên quan, nhưng thật hữu ích khi xem xét chúng một cách riêng biệt.

API cấu hình

Mẫu Phương thức Mẫu rất chung chung, nhưng tôi đặt câu hỏi liệu bạn có thực sự cần tính tổng quát đó không. Bạn sẽ cần một lớp cho từng loại giá trị cấu hình. Bạn có thực sự có nhiều loại? Tôi đoán rằng bạn có thể nhận được chỉ với một số ít: chuỗi, ints, float, booleans và enums. Với những điều này, bạn có thể có một Configlớp có một số phương thức trên đó:

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)

(Tôi nghĩ rằng tôi đã nhận được thuốc generic trên cái cuối cùng bên phải.)

Về cơ bản mỗi phương thức biết cách xử lý phân tích giá trị chuỗi từ tệp cấu hình và xử lý lỗi và trả về giá trị mặc định nếu thích hợp. Kiểm tra phạm vi cho các giá trị số có lẽ là đủ. Bạn có thể muốn có quá tải mà bỏ qua các giá trị phạm vi, tương đương với việc cung cấp một phạm vi Integer.MIN_VALUE, Integer.MAX_VALUE. Enum là một cách an toàn để xác thực chuỗi đối với một chuỗi cố định.

Có một số điều không xử lý được, chẳng hạn như nhiều giá trị, giá trị có liên quan đến nhau, tra cứu bảng động, v.v. Bạn có thể viết các thói quen phân tích cú pháp và xác thực chuyên biệt cho những điều này, nhưng nếu điều này quá phức tạp, tôi bắt đầu đặt câu hỏi cho dù bạn đang cố gắng làm quá nhiều với một tập tin cấu hình.

Định dạng lưu trữ

Các tệp thuộc tính Java có vẻ tốt để lưu trữ các cặp khóa-giá trị riêng lẻ và chúng hỗ trợ các loại giá trị mà tôi đã mô tả ở trên khá tốt. Bạn cũng có thể xem xét các định dạng khác như XML hoặc JSON, nhưng những định dạng này có thể quá mức trừ khi bạn có dữ liệu lồng nhau hoặc lặp lại. Tại thời điểm đó, nó dường như vượt quá một tập tin cấu hình ....

Telastyn đề cập đến các đối tượng nối tiếp. Đây là một khả năng, mặc dù việc xê-ri hóa có những khó khăn của nó. Đó là nhị phân, không phải văn bản, vì vậy thật khó để xem và chỉnh sửa các giá trị. Bạn phải đối phó với khả năng tương thích nối tiếp. Nếu các giá trị bị thiếu trong đầu vào được tuần tự hóa (ví dụ: bạn đã thêm một trường vào lớp Cấu hình và bạn đang đọc một dạng tuần tự cũ của nó), các trường mới được khởi tạo thành null / zero. Bạn phải viết logic để xác định xem có nên điền vào một số giá trị mặc định khác hay không. Nhưng số 0 biểu thị sự vắng mặt của một giá trị cấu hình, hay nó được chỉ định là 0? Bây giờ bạn phải gỡ lỗi logic này. Cuối cùng (không chắc đây có phải là vấn đề đáng lo ngại không), bạn vẫn có thể cần xác thực các giá trị trong luồng đối tượng được tuần tự hóa. Người dùng độc hại có thể sửa đổi luồng đối tượng được tuần tự hóa không thể phát hiện được (mặc dù không thuận tiện).

Tôi muốn nói rằng hãy gắn bó với tài sản nếu có thể.


2
Này Stuart, rất vui được gặp bạn ở đây :-). Tôi sẽ thêm vào câu trả lời của Stuarts rằng tôi nghĩ ý tưởng tempalte của bạn sẽ hoạt động trong Java nếu bạn sử dụng Generics để gõ mạnh, do đó bạn cũng có thể có Cài đặt <T> làm tùy chọn.
Martijn Verburg

@StuartMarks: Vâng, ý tưởng đầu tiên của tôi là chỉ để viết một Configlớp và sử dụng cách tiếp cận của bạn đề xuất: getInt(), getByte(), getBoolean(), vv .. Tiếp tục với ý tưởng này, đầu tiên tôi đọc tất cả các giá trị và tôi có thể kết hợp mỗi giá trị cho một lá cờ (cờ này là sai nếu xảy ra sự cố trong quá trình khử lưu huỳnh, ví dụ như lỗi phân tích cú pháp). Sau đó, tôi có thể bắt đầu giai đoạn xác thực cho tất cả các giá trị được tải và đặt bất kỳ giá trị mặc định nào.
enzom83

2
Tôi muốn một số cách tiếp cận JAXB hoặc YAML để đơn giản hóa tất cả các chi tiết.
Gary Rowe

4

Làm thế nào tôi đã làm điều đó:

Khởi tạo mọi thứ về giá trị mặc định.

Phân tích tệp, lưu trữ các giá trị khi bạn đi. Các vị trí được đặt có trách nhiệm đảm bảo các giá trị được chấp nhận, các giá trị xấu bị bỏ qua (và do đó giữ lại giá trị mặc định.)


Đây cũng có thể là một ý tưởng hay: một lớp tải các giá trị của cài đặt có thể chỉ phải xử lý để tải các giá trị từ tệp cấu hình, nghĩa là, trách nhiệm của nó chỉ có thể là tải các giá trị Từ tập tin cấu hình; thay vào đó, mỗi mô-đun (sử dụng một số cài đặt) sẽ có trách nhiệm xác thực các giá trị.
enzom83

2

Có giải pháp nào khác cho loại vấn đề này không?

Nếu tất cả những gì bạn cần là một cấu hình đơn giản, tôi muốn tạo một lớp cũ đơn giản cho nó. Nó khởi tạo mặc định và có thể được ứng dụng tải từ tệp thông qua các lớp tuần tự hóa tích hợp. Các ứng dụng sau đó chuyển nó xung quanh để những thứ cần nó. Không nhầm lẫn với phân tích cú pháp hoặc chuyển đổi, không vặn vẹo xung quanh với chuỗi cấu hình, không đổ rác. Và nó làm cho cách cấu hình dễ sử dụng hơn cho các tình huống trong mã, nơi nó cần được lưu / tải từ máy chủ hoặc dưới dạng cài đặt sẵn, và cách dễ sử dụng hơn trong các bài kiểm tra đơn vị của bạn.


1
Không nhầm lẫn với phân tích cú pháp hoặc chuyển đổi, không vặn vẹo xung quanh với chuỗi cấu hình, không đổ rác. Ý anh là gì?
enzom83

1
Ý tôi là: 1. Bạn không cần lấy kết quả AppConfig (một chuỗi) và phân tích nó thành những gì bạn muốn. 2. Bạn không cần chỉ định bất kỳ loại chuỗi nào để chọn tham số cấu hình nào bạn muốn; đó là một trong những điều dễ xảy ra lỗi của con người và khó tái cấu trúc và 3. sau đó bạn không cần thực hiện các chuyển đổi loại khác khi đặt giá trị theo chương trình.
Telastyn

2

Ít nhất là trong .NET, bạn có thể dễ dàng tạo các đối tượng cấu hình được gõ mạnh của riêng mình - xem bài viết MSDN này để biết ví dụ nhanh.

Protip: bọc lớp cấu hình của bạn trong một giao diện và để ứng dụng của bạn nói chuyện với nó. Làm cho nó dễ dàng để tiêm cấu hình giả để thử nghiệm hoặc vì lợi nhuận.


Tôi đọc bài viết MSDN: thật thú vị, về cơ bản mỗi lớp con của ConfigurationElementlớp có thể đại diện cho một nhóm các giá trị và đối với bất kỳ giá trị nào bạn có thể chỉ định một trình xác nhận. Nhưng nếu ví dụ tôi muốn đại diện cho một yếu tố cấu hình bao gồm bốn xác suất, thì bốn giá trị xác suất tương quan với nhau, vì tổng của chúng phải bằng 1. Làm cách nào để xác thực thành phần cấu hình này?
enzom83

1
Nói chung, tôi cho rằng đó không phải là thứ để xác thực cấu hình mức thấp - Tôi sẽ thêm phương thức AssertConfigrationIsValid vào lớp cấu hình của mình để trình bày điều này trong mã. Nếu điều đó không phù hợp với bạn, tôi nghĩ bạn có thể tạo trình xác nhận cấu hình của riêng mình bằng cách mở rộng lớp cơ sở của thuộc tính. Họ có một trình xác nhận so sánh để rõ ràng họ có thể nói chuyện sở hữu chéo.
Wyatt Barnett
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.