Platform — Kiến trúc hệ thống
Trang này ráp các khái niệm thành bức tranh kỹ thuật: thành phần nào, dữ liệu chảy ra sao.
Bức tranh lớn
Điểm mấu chốt: mọi đường tạo notification đều hội tụ về NotificationJobFactory rồi vào cùng pipeline gửi. Notification, History và Domain handler là ba nhánh độc lập — một nhánh lỗi không làm bỏ qua nhánh khác trong cùng lượt xử lý.
① Entity Event Flow
Service phát event bằng cách gọi SendEntityEventAsync(...). Event vào entity-event-queue. EventHandler Worker đọc queue, định tuyến theo EventTypeId:
- Gọi domain handler tương ứng (Account/Attendee/Invoice…) cho phần nghiệp vụ lõi (projection tài chính, tạo scheduler…).
- Chạy các post-processor đã đăng ký (notification, history) — mỗi cái duyệt registry của nó.
Routing chỉ-thêm: thêm một concern mới = thêm một post-processor mới với registry riêng, không phình routing của handler hay rẽ nhánh bên trong post-processor có sẵn.
② Notification Platform
Hai đường tạo job → một pipeline gửi
NotificationJob (Azure Table) + NotificationScheduler (SQL, StatusId = Ready) chỉ là bản ghi việc cần làm. Sau đó, độc lập, pipeline 3 bước chạy:
| Bước | Worker | Làm gì |
|---|---|---|
| 1 | Tux.Workers.Notification.Scheduler | Quét scheduler Ready; kiểm tra Notification Settings của BU (loại này có bật không, gửi cho ai). |
| 2 | Tux.Workers.Notification.Generator | Đọc (string)job.EventData, resolve người nhận, render template; sinh OutgoingMessage (và UserMessage nếu cần). |
| 3 | Tux.Workers.Notification.Distributor | Gửi thật qua provider; "claim trước khi gửi" để không gửi đôi. |
Vì pipeline gửi đã async từ trước, chuyển một flow sang event-driven không làm việc gửi nhanh hơn — nó chỉ dời nơi tạo job sang worker. Lợi ích là về kiến trúc (decoupling, idempotency), không phải tốc độ.
Hợp đồng quan trọng (invariant)
| Mã | Nội dung |
|---|---|
INV-NOTIF-PLATFORM-001 | Mọi job tạo qua factory; EventData luôn là string (Generator đọc bằng (string)job.EventData). |
INV-NOTIF-PLATFORM-002 | Job tạo bởi worker dùng EventKey tất định + query-before-append ⇒ retry không trùng. |
INV-NOTIF-PLATFORM-003 | Mapping tường minh qua registry; event id bị overload mà thiếu context thì fail closed (log + bỏ qua). |
INV-NOTIF-PLATFORM-004 | Một loại notification sinh bởi đúng một đường (guardrail dual-path). |
INV-NOTIF-PLATFORM-005 | Trigger ở worker chỉ append job/scheduler; không sửa state aggregate nguồn, không render/gửi trực tiếp. |
INV-NOTIF-PLATFORM-006 v2 | "1 notification job event-derived = 1 dòng" (RowKey sentinel tất định + dedup point-read). |
③ Entity History
History đi theo đúng mô hình registry như notification, nhưng lưu vào bảng EntityHistory (Table + Blob snapshot):
Ba loại history mapping:
| Loại | Interface | Khi nào |
|---|---|---|
| Single-record | IEntityEventHistoryMapping | 1 event → 1 dòng (Account, Attendee, Employee, Invoice, CreditNote, Payment, Contact). |
| Multi-record | IEntityEventHistoryMultiMapping | 1 event → N dòng cho nhiều entity (PaymentBatch + từng Overpayment), mỗi dòng key theo identity của chính nó. |
| Hinted | HintedHistoryMapping | Event CRUD chung mang snapshot + action dẫn xuất — không cần class riêng. |
Mapping không được: gán RowKey, tự dedup, tạo scheduler/fan-out, hay sửa source aggregate. Nó chỉ đọc state và trả về bản ghi; post-processor sở hữu idempotency + persistence.
Invariant History
| Mã | Nội dung |
|---|---|
INV-HIST-PLATFORM-001 | Mọi history event-derived do mapping đã đăng ký sinh ra; post-processor không đổi theo type. |
INV-HIST-PLATFORM-002 | Dòng history do worker tạo dùng RowKey tất định + upsert ⇒ retry ghi đè, không trùng. |
INV-HIST-PLATFORM-003 | History post-processor chỉ tạo history; không tạo scheduler/fan-out. |
INV-HIST-PLATFORM-004 | Mapping chỉ đọc state; không sửa source aggregate. |
INV-HIST-PLATFORM-005 | History chạy độc lập với notification; một concern lỗi không bỏ qua cái kia. |
📚 Bảng invariant ở đây là bản tóm tắt để hiểu hành vi, không phải đặc tả đầy đủ.
Ranh giới phạm vi (cố ý không gộp)
- Booking ở lại bảng
BookingHistoryriêng (khác shape/đường-ghi). - Event chỉ-audit ở lại
EntityEventLog, không có history mapping. EntityEventLog(audit thô) vàEntityHistory(projection lịch sử) vẫn là hai cơ chế tách biệt.
Bảng kho lưu trữ tóm tắt
| Artifact | Kho | Vai trò |
|---|---|---|
entity-event-queue | Azure Queue | Vận chuyển EntityEvent tới worker |
NotificationJob | Azure Table | Một sự kiện thông báo; phân vùng theo entity để mở lịch sử |
NotificationScheduler | SQL | Hàng đợi "có notification cần xử lý" |
EntityHistory | Azure Table + Blob | Lịch sử thay đổi entity (timeline) |
OutgoingMessage | Azure Table | Một dòng/người nhận/kênh; trạng thái gửi |
EntityEventLog | (audit store) | Audit thô của event |
Tiếp theo: ánh xạ các thành phần này tới file & class cụ thể ở Luồng xử lý trong code.