DMN Decision Service — đặc tả
Trạng thái: POC · Ngày: 22/09/2026 · Engine: Drools/Kogito DMN 10.2.0 (Apache 2.0, DMN 1.3 Level 3)
Luật nghiệp vụ nằm trong file
.dmntrên đĩa, không nằm trong mã. Người phụ trách nghiệp vụ mở bằng Camunda Modeler, sửa bảng, lưu, gọi/reload. Không rebuild, không deploy, không cần lập trình viên.
1. Ranh giới
Entitlement Service ──▶ DMN Service
ra quyết định đánh giá luật
cấp entitlement không lưu gì
gắn TTL và nguồn không biết session là gì
DMN trả lời "luật nói gì về những dữ kiện này". Entitlement biến câu trả lời đó thành một bản ghi quyền lợi có id, TTL và nguồn gốc.
Đổi engine luật không được làm đổi hợp đồng entitlement. Đây là lý do DMN không tự phát entitlementId.
DMN Service không có trạng thái nghiệp vụ. Restart không mất gì.
2. Vì sao là DMN chứ không phải mã
Trong sơ đồ Luồng 1, Business owner nối thẳng vào Decision / Rule Engine. Nghĩa là người không phải lập trình viên phải sửa được luật.
| Luật trong mã | Luật trong DMN | |
|---|---|---|
| Ai sửa được | Lập trình viên | Người phụ trách nghiệp vụ |
| Sửa xong cần gì | Build, test, deploy | Lưu file, gọi /reload |
| Nghiệp vụ đọc được không | Không | Có — bảng quyết định |
| Duyệt trước khi áp | Qua code review | ⚠️ Chưa có — xem §7 |
3. Bảng quyết định WifiEntitlement
File: dmn-service/dmn/wifi-entitlement.dmn · Hit policy FIRST — luật có thứ tự, khớp đầu tiên thắng.
Đầu vào
| Tên | Kiểu | Nguồn |
|---|---|---|
userType |
string | claim user_type đã verify chữ ký |
employeeVerified |
boolean | claim employee_verified |
company |
string | claim company |
Cả ba lấy từ token đã kiểm chữ ký, issuer, audience và hạn. Không lấy từ body request — nếu portal tự khai được thì Entitlement chỉ là trang trí.
Đầu ra
| Tên | Kiểu |
|---|---|
eligible |
boolean |
entitlementType |
string |
tier · qosClass |
string |
deviceSlots · ttlSeconds |
number |
sourceType |
string |
reason |
string — chỉ khi từ chối |
Năm luật
| # | userType | verified | company | Kết quả |
|---|---|---|---|---|
| 1 | EMPLOYEE |
true |
VIETJET, GALAXY, SOVICO | ✅ WIFI_EMPLOYEE_PACKAGE · BOOST · HIGH · 2 thiết bị · 4h |
| 2 | EMPLOYEE |
true |
(khác) | ❌ COMPANY_NOT_ELIGIBLE |
| 3 | EMPLOYEE |
(bất kỳ) | (bất kỳ) | ❌ EMPLOYEE_NOT_VERIFIED |
| 4 | CUSTOMER, rỗng, null |
— | — | ✅ WIFI_CUSTOMER_BASIC · BASIC · STANDARD · 1 thiết bị · 1h |
| 5 | (còn lại) | — | — | ❌ UNKNOWN_USER_TYPE |
Luật 2 là luật đáng demo nhất. HR xác nhận nhân viên thật, đang làm việc, đủ điều kiện — Entitlement vẫn từ chối. Xác thực và cấp quyền là hai câu hỏi khác nhau, và đây là chỗ nhìn thấy điều đó.
Luật 4 nhận cả userType rỗng — người vừa tự đăng ký Galaxy ID chưa có attribute. Họ đã xác thực nên được mức nền, không bị từ chối.
Luật 3 lẽ ra không bao giờ chạy. Nếu nó chạy thì ánh xạ claim trong Keycloak hỏng. Giữ lại để hỏng thì thấy, chứ không im lặng.
Luật 5 fail closed.
4. API
Base: http://localhost:3005
POST /api/v1/decisions/{model}/evaluate
{
"inputs": {
"userType": "EMPLOYEE",
"employeeVerified": true,
"company": "VIETJET"
}
}
{
"model": "WifiEntitlement",
"results": {
"WifiEntitlement": {
"eligible": true,
"entitlementType": "WIFI_EMPLOYEE_PACKAGE",
"tier": "BOOST",
"qosClass": "HIGH",
"deviceSlots": 2,
"sourceType": "EMPLOYEE_VERIFICATION",
"ttlSeconds": 14400,
"reason": null
}
},
"correlationId": "corr-...",
"timestamp": "2026-09-22T08:00:00.000Z"
}
Thêm "decision": "TênQuyếtĐịnh" để chỉ chạy một quyết định.
| Mã | HTTP | Khi nào |
|---|---|---|
INVALID_REQUEST |
400 | Thiếu inputs, hoặc body không phải JSON |
MODEL_NOT_FOUND |
404 | Không có model tên đó |
DMN_EVALUATION_ERROR |
422 | Model lỗi khi chạy |
DMN_UNAVAILABLE |
503 | Không nạp được model nào |
422 không phải là "từ chối". Bảng lỗi khác hoàn toàn với bảng nói "không". Trộn hai thứ này là che mất lỗi mô hình.
GET /api/v1/decisions
Liệt kê model đang nạp, kèm tên file nguồn — để biết luật đang chạy đến từ đâu.
POST /api/v1/decisions/reload
Đọc lại toàn bộ .dmn từ đĩa. Runtime đổi nguyên khối: request đang chạy giữ model cũ, không bao giờ thấy bảng nạp dở.
Không nạp được model nào thì báo lỗi to, không phục vụ bảng rỗng — bảng rỗng sẽ từ chối tất cả mọi người mà trông như đang hoạt động bình thường.
GET /health
5. Quy trình sửa luật
# 1. Mở trong Camunda Modeler (app Mac, miễn phí)
open -a "Camunda Modeler" poc/identity/dmn-service/dmn/wifi-entitlement.dmn
# 2. Sửa bảng, lưu
# 3. Nạp lại — không rebuild
curl -X POST localhost:3005/api/v1/decisions/reload
# 4. Kiểm tra
curl -s -X POST localhost:3005/api/v1/decisions/WifiEntitlement/evaluate \
-H 'Content-Type: application/json' \
-d '{"inputs":{"userType":"EMPLOYEE","employeeVerified":true,"company":"HDBANK"}}'
Thư mục dmn/ mount read-only vào container. Sửa trên máy host.
Sau khi sửa phải chạy lại test.
tests/e2e.mjscó bài đối chiếu DMN với bảng dự phòng trongpolicy.js— sửa một bên mà quên bên kia thì test fail. Đó là thứ duy nhất ngăn hai bản luật trôi khỏi nhau.
6. Khi DMN sập
Mặc định fail closed. Entitlement trả 503 DMN_UNAVAILABLE, không ai được vào mạng.
POLICY_FALLBACK=local cho phép bảng dự phòng trong policy.js tiếp quản.
| Fail closed (mặc định) | Fallback | |
|---|---|---|
| DMN sập | Không ai vào được | Cả khoang vẫn online |
| Luật đang áp | — | Bản trong mã, không phải bản nghiệp vụ vừa sửa |
| Audit | Rõ ràng | Lúc đó không ai biết luật nào đang chạy |
Đây là quyết định nghiệp vụ, không phải kỹ thuật — ghi là T-DMN-FALLBACK. Mặc định fail closed vì nhất quán với mọi guardrail khác trong nền tảng này.
7. Còn thiếu
| # | Thiếu gì | Mức |
|---|---|---|
| 1 | Không có quy trình duyệt. Ai cũng sửa .dmn rồi /reload được. Một luật sai là cả khoang mất Wi-Fi giữa trời |
🔴 Cao |
| 2 | /reload không có xác thực |
🔴 Cao |
| 3 | Không có lịch sử phiên bản luật — không biết ai sửa gì lúc nào | 🟠 |
| 4 | Không lưu lại từng lần đánh giá để đối soát sau | 🟠 |
| 5 | Không có môi trường staging để thử luật trước | 🟠 |
| 6 | Chỉ một bảng quyết định | 🟡 Đủ cho POC |
Mục 1 và 2 phải xong trước production. Cách làm tối thiểu: .dmn nằm trong git, sửa qua pull request, CI đẩy vào và gọi /reload — người nghiệp vụ vẫn sửa bằng Modeler, nhưng có vết và có người duyệt.
8. Vì sao Kogito/Drools chứ không phải Camunda
| Đánh giá | |
|---|---|
| Camunda 7 CE | ❌ Hết vòng đời 10/2025, không còn vá bảo mật |
| Camunda 8 Run | ⚠️ Khuyến nghị 4 core + 8 GB RAM. Là nền BPMN đầy đủ trong khi ta chỉ cần DMN |
| Kogito/Drools DMN | ✅ Apache 2.0, ~250 MB, DMN 1.3 Level 3, đang được bảo trì |
Camunda Modeler vẫn dùng để soạn bảng — file DMN 1.3 là chuẩn, engine nào đọc cũng được. Nên lựa chọn engine không khoá công cụ của người nghiệp vụ.
Nếu sau này tập đoàn chuẩn hoá trên Camunda, file .dmn chuyển thẳng sang được; chỉ thay service này.
9. Câu hỏi còn mở
| Ref | Câu hỏi |
|---|---|
| T-DMN-FALLBACK | DMN sập thì fail closed hay chạy bảng dự phòng? |
| T-DMN-GOVERN | Ai được sửa luật? Ai duyệt? Lưu vết ở đâu? |
| T-TIER | member_tier đã có trong token nhưng chưa luật nào đọc. Loyalty có vào bảng quyết định không? |
| T1 / ADR-011 | BASIC/BOOST có phải tier thật? Đòn bẩy là thời lượng hay tốc độ? |
API-OVERVIEW.md · KEYCLOAK-INTEGRATION-SPEC.md · API-MATRIX.md