Tổng quan
1. Trọng tâm thiết kế
- Xây một pipeline ingest dữ liệu listing thời gian thực, ổn định và có kiểm soát.
- Chuẩn hóa dữ liệu đa nguồn về một canonical schema thống nhất.
- Khử trùng lặp thực thể (entity resolution), giữ được lịch sử nguồn và thay đổi.
- Quản lý vòng đời listing bằng state machine nhất quán.
- Phân phối listing theo rules + SLA tới đúng hub/broker/agent.
- Bảo đảm tài chính và trạng thái quan trọng theo nguyên tắc atomic, idempotent, audit được.
2. 7 nguyên tắc bất biến (Architecture Principles)
7 nguyên tắc dưới đây là ràng buộc cứng cho mọi quyết định kỹ thuật trong VNexus. Bất kỳ sai khác nào đều phải có ADR phê duyệt bởi Tech Lead.
| # | Nguyên tắc | Hệ quả thiết kế |
|---|---|---|
| P1 | Data is the product | Mọi pipeline phải log raw, traceable, replayable. Không bao giờ ghi đè dữ liệu thô. |
| P2 | Listing ≠ Property | Tách rõ 2 entity. Một property có thể có N listing từ nhiều nguồn/môi giới. |
| P3 | Confidence over certainty | Mọi field do AI/rule sinh ra đều phải có confidence_score [0..1] và evidence. |
| P4 | AI suggests, human confirms | Trong MVP, không AI nào được tự ý merge property, ban contact, hoặc xóa dữ liệu. |
| P5 | Idempotency by design | Mọi job, mọi POST/PATCH đều phải idempotent qua idempotency_key hoặc content_hash. |
| P6 | Audit everything sensitive | Phone, address, legal docs, ownership change → bắt buộc audit log trước khi commit. |
| P7 | Schema-first, contract-first | DB migration & OpenAPI spec phải merge trước khi code business logic. |
3. Vấn đề cần ưu tiên
- Độ đúng của dữ liệu listing:
- Listing phải có vòng đời rõ ràng (
submitted → validated → approved/flagged → published → dispatched → closed/expired). - Các chuyển trạng thái quan trọng phải atomic, không được nửa vời.
- Listing phải có vòng đời rõ ràng (
- Dữ liệu đa nguồn và trùng lặp:
- Cùng một bất động sản có thể tới từ nhiều nguồn với format khác nhau.
- Cần pipeline cleaning/standardization/matching/merge để tránh duplicate và sai lệch.
- Chuẩn hóa schema từ đầu:
- Mọi nguồn map về canonical schema để không vỡ dữ liệu khi mở rộng.
- Phân phối vận hành theo SLA:
- Routing listing theo geo/rule.
- Timeout SLA thì re-route tự động.
- Realtime và minh bạch:
- Dashboard admin cập nhật nhanh theo event.
- Mọi thay đổi phải truy vết được qua audit log bất biến.
4. Sơ đồ tổng thể (architecture)
flowchart LR
subgraph S[Sources]
SA[API and Partner Feed]
SU[Manual or User Input]
end
subgraph I[Ingestion Layer]
IA[Source Adapters]
IN[Schema Normalizer]
IR[Entity Resolution]
IT[Trust and Quality Engine]
end
subgraph C[Core Platform]
CE[Event Log]
CW[State Store]
CR[Dispatch Rules Engine]
CL[Ledger and Wallet]
CA[Immutable Audit Log]
end
subgraph D[Read and Delivery]
DP[Read Models and Search]
DN[Notification Service]
DG[WebSocket and REST API]
DA[Admin Console]
DH[Hub or Broker or Agent]
end
SA --> IA
SU --> IA
IA --> IN
IN --> IR
IR --> IT
IT --> CE
CE --> CW
CE --> DP
CE --> CR
CW --> CL
CW --> CA
CW --> DN
CR --> DH
DN --> DG
DP --> DA
DG --> DA5. Phân biệt rõ Listing vs Property — quy ước cốt lõi
| Khía cạnh | Listing (tin đăng) | Property (tài sản thật) |
|---|---|---|
| Bản chất | Một bản tin xuất hiện trên thị trường; có thể tạm thời, có thể lặp lại | Một bất động sản có thật ngoài đời |
| Tính duy nhất | 1 property → N listing (nhiều môi giới đăng cùng 1 căn) | 1 property = 1 record duy nhất sau khi merge |
| Vòng đời | Active → Inactive/Removed/Duplicated; có thể tái xuất | Tồn tại lâu dài; chỉ đổi khi giao dịch hoặc thay đổi vật lý |
| Nguồn | Có source_id rõ (Chợ Tốt, Batdongsan, fanpage, manual, …) | Không có nguồn — kết quả tổng hợp từ nhiều listing |
| Khi nào tạo | Mỗi lần ingest thành công → 1 raw_listing | Khi listing match property cũ (đủ confidence) hoặc tạo property mới |
| Giá | price tại thời điểm đăng (listing_price_history) | Không giá cố định — chỉ valuation_snapshot tham chiếu |
| Liên hệ | contact_phone / contact_name của người đăng tin | Không gắn contact trực tiếp — qua contact_property_relations |
Ví dụ minh họa: Một căn nhà ở Tân Bình (property X) có thể có 6 listing — 1 chính chủ trên Chợ Tốt, 3 môi giới trên batdongsan.com.vn ở 3 thời điểm, 1 fanpage sàn, 1 nhập tay từ broker nội bộ. Property Matching Service phải gom 6 listing về cùng property_id = X.
Feature liên quan: listing-create, properties-create, listing-crawl-external-sources.
6. Mô hình triển khai: Modular Monolith + Async Workers
Lý do không dùng microservices ngay từ đầu
- Team nhỏ — microservices = overhead vận hành lớn, debug khó.
- Chưa biết bounded context nào scale trước — chia sớm = chia sai.
- Modular monolith vẫn cho tách service sau nếu mỗi module có DAO riêng, không cross-import service layer giữa module.
Ba process chính
| Process | Vai trò |
|---|---|
| backend-api | Modular monolith Java Spring Boot — mỗi domain một module/package; không gọi trực tiếp repository hoặc entity của module khác. |
| ai-service | FastAPI riêng, REST internal; chung host hoặc container tách. |
| data-worker | Crawler / parser / normalizer; job từ Redis queue (BullMQ). |
Tất cả dùng chung 1 PostgreSQL; mỗi service chỉ truy cập schema/bảng domain của mình.
Hướng dẫn consistency (bổ sung)
- Ingestion: adapter theo nguồn; idempotency, retry + backoff, validate schema ở biên (P5, P7).
- Event-driven core: thay đổi listing → event bất biến; partition theo
listing_id; replay được (P1). - Write path: listing state, dispatch, ledger/wallet commit atomic; outbox đồng bộ DB và bus.
- Read path: projection cho dashboard/search; WebSocket snapshot + delta + sequence.