Communication — Dữ liệu & bảng đọc/ghi
Góc nhìn database cho Communication: artifact nằm ở nhiều kho (SQL / Azure Table / Blob / Queue). Quy ước: 📖 READ, ✏️ WRITE (insert/update).
Khái niệm liên quan từ domain khác
| Khái niệm | Là gì | Vai trò trong Communication | Domain sở hữu |
|---|---|---|---|
| EntityEvent | Sự kiện "có gì đó vừa đổi". | Đầu vào đường event-derived → sinh NotificationJob. | Platform |
| NotificationSetting / NotificationOption | Cấu hình loại notification theo BU + subscriber. | Bước Scheduler đánh giá: loại này có bật không, gửi cho ai. | Org/Settings |
| MessageTemplate | Mẫu nội dung có placeholder. | Generator render template. | Messaging |
| ContactGroup | Nhóm người nhận. | Campaign nhắm nhóm (MessageJob_ContactGroup). | Attendee/Contact |
| Account / Attendee / Employee | Người nhận. | Resolve recipients (customer/staff). | Account/Staff |
| ReportSubscription / ReportSubscriber | Đăng ký báo cáo định kỳ. | Nguồn của đường report (xem Report Subscription). | Reporting |
| EmployeeCompliance / Invoice | Nguồn dữ liệu reminder. | Trigger provider tính ngày hết hạn / đến hạn. | Staff / Finance |
Bản đồ kho lưu trữ
| Kho | Artifact | Ghi chú |
|---|---|---|
| Azure SQL | NotificationScheduler, NotificationOption, NotificationSetting(+_Account/_Employee), MessageTemplate, MessagingJob, MessagingJobScheduler, MessageJob_ContactGroup, ReportSubscription, ReportSubscriptionScheduler, ReportSubscriber, ReminderStateLog, NotificationBanner, SmsUsage | Hàng đợi, cấu hình & ledger billing SMS. |
| Azure Table | NotificationJob, OutgoingMessage, UserMessage, AutomationJob | Bản ghi nhiều dòng, phân vùng theo entity/người nhận. |
| Azure Blob | container messaging-job (payload campaign/report) | Nội dung lớn; SQL chỉ giữ tên blob. |
| Azure Queue | hàng đợi giữa các worker | Vận chuyển việc. |
| Redis Stream | stream aimy:{environment}:user-message-inbox:events | Fanout/replay metadata realtime; không phải nguồn dữ liệu Inbox. |
| Azure SQL (automation) | AutomationScheduler | Điều phối due-work reminder/report (xem Automation). |
| Traceability | SourceKey + source-keyed OutgoingMessage | Đọc theo partition source; không có SQL reverse-index mới, không rewrite row lịch sử. |
💡
NotificationJob/OutgoingMessage/UserMessagelà Azure Table, không phải bảng SQL — đó là lý do chúng dùngPartitionKey/RowKeythay vì PK/FK.
UserMessage Inbox — lưu trữ và truy vấn
Primary row và các projection
| Thành phần | Nơi lưu | Key / prefix | Vai trò |
|---|---|---|---|
| Metadata + recipient state | Azure Table UserMessage trên Camel storage | PartitionKey canonical: Account_{id}, Employee_{id}, EnterpriseUser_{id} | Nguồn dữ liệu chính cho list, detail và state. |
| Legacy metadata | Cùng Table UserMessage | Customer_{UserId} | Chỉ đọc tương thích và chống duplicate; không phải partition đích của delivery mới. |
| Message body | Azure Blob container message-content | MessageBodyBlobName; một số producer dùng tên sha256-{hash} | HTML/text riêng tư; không trả Blob URL cho client. |
| Unread counter | Cùng Table UserMessage | RowKey _InboxUnread, _InboxUnread_Account_{AccountId} trong owner partition | Đọc badge nhanh; nếu chưa có thì khởi tạo từ bounded unread scan. |
| Retention index | Cùng Table UserMessage | PK _InboxUserMessageMaintenance_{yyyyMM}, RK SHA-256 của `sourcePartition | sourceRow` |
| Retention state | Cùng Table UserMessage | _InboxMaintenanceState / UserMessageBackfillV1 | Watermark cho backfill maintenance index. |
| Redis cache namespace | Azure Managed Redis, DB 0/default | tux:{environment}: | Cô lập key giữa environments khi dùng chung một logical database. |
| Inbox realtime stream | Azure Managed Redis, DB 0/default | aimy:{environment}:user-message-inbox:events; consumer group tux-api:user-message-inbox:* | Replay/fanout metadata, retention mặc định 24 giờ/100.000 entries; không chứa body. |
Nội dung của một UserMessage
- Identity/scope:
UserMessageId,UserId, role context, owner ids,BusinessUnitId,SiteId. - Source: category, source type, event/entity coordinates,
CampaignGuid,NotificationJobPartitionKey/RowKey. - Display/action:
Title, boundedMessagePreview, provider/attendee/site labels,ActionUrl. - Private body:
MessageBodyBlobName; body được fetch riêng qua API sau khi đã authorize. - Recipient state:
StatusId,ReadOn,ArchivedOn,SavedOn,Reply*,ETag. - Idempotency:
CompatibilityHashlà SHA-256 của immutable content/action và source fields; retry khác hash trảVERSION_CONFLICT.
PartitionKey, RowKey và cách truy vấn
| Trường hợp | PartitionKey | RowKey |
|---|---|---|
| Delivery mới tới Account | Account_{AccountId} | Deterministic theo producer; thường inbox_{sourcePartitionKey}_{hash} hoặc reverseTicks-deterministicKey. |
| Delivery mới tới Employee | Employee_{EmployeeId} | Như trên; producer giữ cùng identity khi retry. |
| Delivery mới tới EnterpriseUser | EnterpriseUser_{EnterpriseUserId} | Như trên; không dùng user id làm quyền truy cập duy nhất. |
| Dữ liệu Incident/Customer cũ | Customer_{UserId} | {reverseTicks} hoặc {reverseTicks}-{deterministicRowKey} |
PartitionKey không lấy trực tiếp từ metadata của row để cấp quyền. API resolve portal identity thành authorized scopes trước, rồi mới query đúng partition. List dùng PartitionKey eq ... và chỉ lấy Unread/Read; category, Account, Business Unit và Site được filter server-side.
Inbox API mặc định lấy 20 item, giới hạn 100. Với nhiều owner partitions, mỗi scope giữ một continuation token Azure Table; server merge các head theo CreatedOn và trả toàn bộ trạng thái trong NextCursor opaque. Client phải gửi lại cursor nguyên vẹn, không decode/chỉnh sửa.
Detail point-read theo PartitionKey + RowKey; body đọc message-content sau authorization. Read, Archive, Save và Unsave dùng ETag conditional Replace; Save chỉ set SavedOn, Unsave set null, không copy row và không tự chuyển Read. Redis/SSE chỉ báo invalidation, REST/Table + Blob mới là authoritative.
Ví dụ thực tế: Staff Portal có một
Employeevà haiEnterpriseUserđược phép xem. → API query ba owner partitions, merge theo cursor riêng từng scope. → Một row ngoài scope trả như không tồn tại.
Bảng dữ liệu trong từng luồng
Luồng gửi (Scheduler → Generator → Distributor)
| Bước | Bảng | Tác dụng |
|---|---|---|
| Scheduler | NotificationScheduler, MessagingJob 📖 READ; NotificationSetting/NotificationOption 📖 READ | Lấy việc Ready; đánh giá settings. |
| Generator | MessageTemplate 📖 READ; NotificationJob 📖 READ | Render + resolve recipients. |
| Generator (output) | UserMessage ✏️ WRITE; OutgoingMessage ✏️ WRITE | Sinh tin in-app + tin gửi ra (mỗi người nhận/kênh). |
| Distributor | OutgoingMessage ✏️ UPDATE (claim → Sending → Sent) | "Claim trước khi gửi". |
| Distributor (accept) | SmsUsage ✏️ WRITE | Ledger billing SMS khi provider chấp nhận (idempotent theo outgoing message). |
| Webhook & Tracker | OutgoingMessage ✏️ UPDATE (delivered/opened/bounce) | Cập nhật trạng thái từ provider. |
Luồng realtime Inbox
| Bước | Artifact | Tác dụng |
|---|---|---|
| Sau khi tạo mới | UserMessage ✏️ WRITE | Nguồn dữ liệu Inbox và trạng thái recipient-owned. |
| Phát tín hiệu | Redis Stream ✏️ WRITE | Ghi inbox.changed với metadata tối thiểu sau persistence. |
| Fanout | Redis Stream 📖 READ + ACK; SSE connection | Gửi event tới các Tuke connection của đúng JWT subject. |
| UI convergence | UnreadCount / Inbox first page 📖 READ | Tuke revalidate bằng REST; event không mang body hoặc quyền truy cập. |
Redis Stream có retention/max length cấu hình được. Event bị mất hoặc hết hạn không làm mất UserMessage; client gọi REST sync lại.
Luồng automation/reminder
| Bước | Bảng | Tác dụng |
|---|---|---|
| Tạo việc | AutomationScheduler ✏️ WRITE (ensure/deactivate) | Lịch việc đến hạn (SchedulerKey UNIQUE). |
| Đến hạn | AutomationScheduler 📖 READ + claim (Version) | Worker chốt quyền xử lý. |
| Trace mirror | AutomationJob ✏️ WRITE (Azure Table) | Bản sao trace mỏng. |
| State log | ReminderStateLog ✏️ WRITE | Ghi trạng thái reminder. |
| Giao hàng | NotificationScheduler ✏️ WRITE (qua dispatch guard) | Bắc cầu sang pipeline gửi. |
Luồng campaign
| Bước | Bảng | Tác dụng |
|---|---|---|
| Tạo campaign | MessagingJob ✏️ WRITE; blob messaging-job ✏️ WRITE; MessageJob_ContactGroup ✏️ WRITE | Điều phối payload lớn + nhóm người nhận. |
| Lên lịch | MessagingJobScheduler, NotificationScheduler ✏️ WRITE | Đưa vào pipeline gửi. |
Luồng Notification Settings và Report Subscription
| Luồng | Artifact | Tác dụng |
|---|---|---|
| Toggle notification | NotificationSetting + BU/site contact settings ✏️ WRITE | Group/batch enable và Site/BU copy; mixed state chỉ là read state. |
| Report run | ReportSubscriptionScheduler ✏️ WRITE/UPDATE; stored report Blob ✏️ WRITE | Run scheduled/manual, lưu history và file độc lập attachment. |
| Trace delivery | SourceKey 📖 READ; OutgoingMessage ✏️ WRITE/UPDATE | Partition theo source đã resolve, claim lease và provider status. |
Liên quan
- Kiến trúc — pipeline gửi & key policy.
- Lịch sử & quan sát — audit & trace.
Ví dụ thực tế: Một notification job chứa nhiều invoice. → Mỗi
OutgoingMessagetrace theoSourceKeyinvoice riêng; không tạo SQL reverse-index mới.