Vert.x không có application.properties tự động như Spring Boot. Cấu hình là một module riêng, bạn khai rõ lấy từ đâu — đổi lại là kiểm soát hoàn toàn, và một cái bẫy im lặng.
Nhiều nguồn, gộp lại theo thứ tự khai báo
var opts = new ConfigRetrieverOptions()
.addStore(new ConfigStoreOptions().setType("file").setFormat("json")
.setConfig(new JsonObject().put("path", "ung-dung.json")))
.addStore(new ConfigStoreOptions().setType("sys")); // thuoc tinh he thong
Tệp khai ten = "tu-tep", thuộc tính hệ thống khai ten = "tu-thuoc-tinh-he-thong":
kết quả: ten = tu-thuoc-tinh-he-thong -> nguồn khai SAU thắng
chỉ-có-ở-tệp vẫn còn? true -> gộp chứ không thay thế
Đảo thứ tự hai addStore:
kết quả: ten = tu-tep
Hai luật, và cả hai đều đơn giản: nguồn khai sau ghi đè nguồn khai trước, và những khoá chỉ có ở một nguồn thì vẫn còn nguyên — đây là phép gộp, không phải phép thay thế.
Thứ tự thực dụng: tệp mặc định trước, rồi tệp theo môi trường, rồi biến môi trường, rồi thuộc tính hệ thống. Cái nào cụ thể hơn thì khai sau.
Nạp nóng chạy tốt — nếu bạn khai một dòng
var opts = new ConfigRetrieverOptions()
.setScanPeriod(1000) // DONG NAY
.addStore(...);
cr.listen(ch -> capNhat(ch.getNewConfiguration()));
scanPeriod = 1 000 ms -> phát hiện thay đổi sau 499 ms
giá trị mới: ten = da-doi
Sửa tệp, và trong vòng nửa giây listen chạy với cấu hình mới. Chính xác và nhanh hơn tôi tưởng — 499 ms với chu kỳ quét 1 giây, tức nó bắt được ngay ở lần quét kế tiếp.
Và đây là cái bẫy
Bỏ đúng một dòng setScanPeriod, giữ nguyên mọi thứ khác:
sau 3 giây, số lần listen được gọi: 0
giá trị trong bộ nhớ: (vẫn là giá trị cũ)
scanPeriod, ConfigRetriever không quét lại bao giờ. listen() vẫn nhận đăng ký của bạn, không ném lỗi, không cảnh báo — nó chỉ không bao giờ chạy. Mã biên dịch được, chạy được, và tính năng "cấu hình nóng" của bạn đơn giản là không tồn tại.
Đây là kiểu lỗi tệ nhất vì nó âm thầm và trông giống như đã xong việc. Cách duy nhất phát hiện là thử sửa tệp và xem listener có chạy không — đúng phép thử ở cuối bài.
Chọn nguồn nào
| Kiểu store | Dùng khi |
|---|---|
file (json, yaml, properties) |
Giá trị mặc định đi cùng ứng dụng |
env |
Biến môi trường — chuẩn de facto cho container |
sys |
Thuộc tính hệ thống, tiện để ghi đè lúc chạy thử |
http |
Máy chủ cấu hình tập trung |
kubernetes |
ConfigMap và Secret, đọc thẳng từ API |
Với http và kubernetes thì scanPeriod mới thật sự đáng giá: cấu hình đổi ở nơi khác và ứng dụng tự nhận, không cần khởi động lại.
Nhưng cân nhắc kỹ cái gì nên nạp nóng được. Đổi ngưỡng cảnh báo hay mức log lúc chạy thì tốt; đổi chuỗi kết nối CSDL lúc chạy thì bạn phải tự lo chuyện đóng pool cũ, và không có gì trong Vert.x làm hộ. Nạp nóng cho bạn giá trị mới, không cho bạn cách áp dụng nó an toàn.
Ba điều nên làm
Khai scanPeriod nếu bạn dùng listen. Không có nó thì listen là mã chết.
Đặt nguồn cụ thể nhất ở cuối. Bảng đầu bài là luật, và nó ngược với trực giác của một số người quen với thứ tự ưu tiên "cái đầu tiên thắng".
Kiểm tra cấu hình lúc khởi động. Vert.x Config trả về JsonObject trần — không có kiểu, không có validation, không báo lỗi khi thiếu khoá. config.getString("db.url") trả null và ứng dụng cứ thế chạy tiếp.
Bài sau: health check và readiness — hai thứ hay bị khai nhầm cho nhau, và cái giá lúc triển khai.
Thử ba mươi giây
cr.listen(ch -> System.out.println("cau hinh doi luc " + System.currentTimeMillis()));
// roi sua tep cau hinh
Nếu sửa tệp mà không thấy dòng nào in ra, bạn đang ở đúng cái bẫy ở trên — và tính năng nạp nóng mà bạn tưởng đã có thật ra chưa bao giờ chạy.