Platform — Case thường gặp
Các tình huống thực tế khi làm việc với Platform, và cách hệ thống xử lý. Hữu ích cho cả kỹ sư lẫn support.
Thêm một thông báo mới
Câu hỏi đầu tiên: thông báo này action-derived hay event-derived?
Action-derived (direct):
var job = NotificationJobFactory.Create(eventTypeId: ..., eventData: ..., recipients: ...);
await _notificationClient.CreateNotificationJobAsync(_context, job);Event-derived id-only: chỉ cần gắn notificationEventTypeId ở đúng call-site có guard và bỏ direct call. Không cần viết mapping mới (HintedNotificationMapping lo phần còn lại). Reference: AttendeeNewProfile — AttendeeService.CreateAttendeeAsync + ConsumerAccountController.
Event-derived data phức tạp: viết một IEntityEventNotificationMapping riêng (như SettingsUpdateNotificationMapping), build job qua factory, đăng ký DI.
Không cần sửa
Handler/EntityEventNotificationPostProcessor. Rollback: gỡ đăng ký mapping trongProgram.cs, hoặc bỏnotificationEventTypeIdở call-site.
"Khách hàng nhận 2 email giống nhau"
Nguyên nhân thường gặp & cách hệ thống phòng:
| Nguyên nhân | Cơ chế phòng |
|---|---|
| Queue giao lại event (retry) | Deterministic key: cùng event → cùng key → đúng 1 dòng (point-read dedup). |
| Một loại notification bị nối vào cả đường event-derived lẫn direct | Test guardrail dual-path: hai tập loại phải rời nhau, trùng → test đỏ. |
| Double-submit từ UI (flow direct không có identity ổn định) | Đây là at-least-once có chủ đích; phòng ở tầng UI/idempotency request. |
Nếu thực sự thấy 2 email: kiểm tra (1) loại đó có bị tạo ở cả hai đường không, (2) key có thật sự tất định không, (3) có double-write ở call-site không.
"Map event CRUD chung → bị over-fire / spam"
⚠️ Đây là cái bẫy lớn nhất. Event như Create_Attendee, Update_Employee được bắn từ rất nhiều code path (kể cả các path phục vụ audit/history). Nếu map thẳng chúng → notification, nó sẽ bắn rộng hơn nhiều so với call-site hiện tại (vốn có guard như isEdit, "invitation path").
Cách đúng: không map lên event CRUD chung. Thay vào đó, tại đúng guarded call-site, gắn một hint (NotificationEventTypeId). Event không có hint → bỏ qua ⇒ không over-fire.
Ví dụ thực tế: Muốn gửi mail "hồ sơ trẻ mới"; lập trình viên map thẳng
Create_Attendee. → Tránh bắn cả từ path import/audit, không chỉ lúc đăng ký thật. → Gắn hint ở call-site có guard (AttendeeNewProfile) → chỉ path đó fire, các path khác bỏ qua.
"Retry tạo dòng history trùng"
Trước đây history ghi với RowKey = Guid.NewGuid() → retry tạo dòng thứ hai. Nay history đi qua EntityEventHistoryPostProcessor với HistoryEventKey tất định + insert-or-replace:
- Retry cùng event → cùng key → ghi đè đúng dòng (không trùng).
- Hai event khác nhau thật → khác
CreatedOn→ khác dòng (giữ lịch sử).
HistoryEventKey có dạng:
entity-history:{EventTypeId}:{EntityTypeId}:{entityIdentity}:{CreatedOn:O}:{ActionTypeId}entityIdentity theo thứ tự ưu tiên EntityGuid → EntityId → BusinessUnitId để ổn định qua các định dạng message.
Thêm một History Rule mới
- Viết một
IEntityEventHistoryMapping(hoặcHintedHistoryMappingcho snapshot + action dẫn xuất, hoặc multi-mapping cho một-nhiều). Chỉ dựngAddEntityToHistoryModel— để trốngRowKey. - Đăng ký vào DI của worker. Post-processor không cần đổi.
- Bỏ chỉ nhánh history khỏi
switchcủa domain đó — giữAddSchedulerAsync/ projection.
Một concern hoàn toàn mới (automation, reminder…) thêm dưới dạng một
IEntityEventPostProcessormới với registry riêng — đừng phình routing handler hay rẽ nhánh trong post-processor có sẵn.
"Notification mới không gửi ra dù đã tạo job"
Pipeline gửi đánh giá Notification Settings ở bước Scheduler. Một số nguyên nhân:
- Loại notification chưa được seed vào
NotificationOptioncho BU (vdIncidentSubmittedcần chạyscripts/seed_incident_submitted_notification.sql). - BU chưa cấu hình subscriber/recipient → handler skip gracefully.
- Generator chưa có handler cho loại mới (feature mới) → cần wire route trong Generator.
"Cần biết loại nào, bao nhiêu, qua worker"
EntityEventNotificationPostProcessor log một dòng có cấu trúc mỗi khi tạo job event-derived (EventTypeId / EventKey / BusinessUnitId). Dùng log này để đếm. Metric/dashboard tổng hợp là follow-up (cần hệ metrics riêng).
Những gì KHÔNG đổi (yên tâm khi refactor)
Pipeline Scheduler/Generator/Distributor và việc gửi; render template; schema + đường đọc của EntityHistory; pipeline Finance Transaction Projection; và id enum số của EntityType / EventType / EntityActionType / NotificationEventType. Các refactor platform chỉ đổi nơi side effect được sinh ra và cách giữ tính duy nhất — không đổi cái downstream nhìn thấy.