Trả lời câu hỏi: installed module trên WS muốn dùng dữ liệu module khác thì platform phải cung cấp cơ chế gì? Đi từ khảo sát Odoo · Salesforce · MS Dataverse · Frappe/ERPNext · Lark, không đi từ hiện trạng. Khác với quan-he-module (chọn pattern P1–P6 theo loại action), trang này thiết kế tầng platform thực thi các pattern đó. Bản chi tiết: markdown.
| Sự thật trong code (ws_suns) | Vị trí | Hệ quả |
|---|---|---|
| Installed module nhận restricted Sequelize proxy: cấm raw SQL, ép prefix <key>_, cấm đụng core tables | runtime-core:57-95 | Module store cô lập tuyệt đối — muốn đọc chéo là không có đường hợp lệ nào |
| Built-in (hrm, org, crm…) nhận sequelize đầy đủ, shared model registry + associate() chéo tự do | models/index.js | Hai chế độ cực đoan ở hai đầu: hoặc cấm sạch, hoặc thả tự do |
| Manifest v2 đã có depends · scopes · provides | manifest-schema.json | scopes mới chỉ in ra lúc cài — chưa cấp quyền gì |
| Mỗi WS = 1 process Node + 1 DB Postgres chung schema | app.js · provisioner.js | JOIN chéo & in-process call chi phí ~0 — vấn đề chỉ là kiểm soát |
| Event dispatcher nằm riêng trong module workflow, handler tĩnh | workflow/events/ | Chưa phải năng lực platform; lỗi handler bị swallow |
| Archetype | Đại diện | Cơ chế liên kết | Được gì | Mất gì | |
|---|---|---|---|---|---|
| A | Shared ORM registry | Odoo, WordPress | Mọi module vào 1 registry, join/extend tự do (_inherit, bridge module) | Tích hợp sâu nhất, 1 query enrich mọi thứ | Đòi code trusted + nâng version đồng bộ; module bên thứ 3 vỡ ngầm |
| B | Platform-owned canonical entities | Salesforce, Shopify, Dataverse | Platform sở hữu entity lõi; app extend & tham chiếu entity lõi, hiếm khi nối app↔app | Hệ sinh thái ISV lớn nhất thế giới; gỡ app A không chết app B | Platform phải "đoán đúng" entity lõi; link app↔app vẫn cần cơ chế phụ |
| C | Metadata-declared links | Frappe (Link field, fetch_from), Dataverse | Quan hệ là metadata platform đọc được → tự render lookup, enrich, chặn xoá, permission | Liên kết = năng lực platform (lookup UI, integrity, degrade) | Phải xây tầng metadata; kém linh hoạt cho query lạ |
| D | API + events, không chung DB | Lark, Slack apps | Scoped API + event subscription, eventual consistency | Cô lập tuyệt đối | N+1, chậm — vô lý khi chung 1 DB |
Package có publisher prefix (= prefix bảng), dependency tracking khi cài/gỡ, không cho xoá table mà solution khác tham chiếu, upgrade theo version. Mô hình "gói cài được + chung DB + prefix" đã validate ở quy mô lớn — lời giải của họ cho liên kết là B + C, không phải A.
App package muốn nối app package khác → extension package declare dependency cả hai — đúng bridge module của Odoo. Hai hệ độc lập cùng hội tụ một pattern → tín hiệu mạnh cho quan hệ optional × optional.
| Hub entities | org / hrm / catalog / iam là canonical (bài học Salesforce) — module store link vào hub trước, link ngang hàng chỉ khi thật cần → giảm mạnh số cạnh đồ thị phụ thuộc |
| Bridge module | Quan hệ optional × optional đặt ở module thứ 3 depends cả hai (Odoo + Salesforce cùng hội tụ) |
| Copy-tại-thời-điểm | Dữ liệu gắn sự kiện quá khứ: copy lúc ghi (fetch_from của Frappe), không join lại; cột copy đặt tên rõ là snapshot |
Màn hình Hồ sơ học viên (module student) → nút "Thêm vào khóa" → dropdown chọn khóa (dữ liệu của course) → Lưu → ghi danh. Gồm 2 luồng khác bản chất:
| Luồng | Bản chất | Đường đi |
|---|---|---|
| Dropdown danh sách khóa | ĐỌC dữ liệu course | Tầng 1 — entity course.khoa_hoc |
| Lưu ghi danh (bảng course_enrollment thuộc course) | GHI vào đất course | Tầng 2 — service course.enroll |
| Việc | Ai lo |
|---|---|
| Đối chiếu scope khai ↔ entity/service tồn tại; inject ctx.entities / ctx.services lúc mount | runtime-core |
| course chưa cài/disabled → entities.get() trả null, services.call báo lỗi rõ ràng | runtime-core |
| Chặn student sờ trực tiếp bảng course_* (giữ nguyên restricted proxy) | runtime-core |
| Hiển thị lúc cài: "student cần đọc Khóa học và gọi Ghi danh của course" cho admin duyệt | installer |
quan-he-module chọn pattern theo loại action; cơ chế ở đây là tầng platform thực thi các pattern đó:
| Pattern | Cơ chế thực thi | Ghi chú |
|---|---|---|
| P1 Contract view | Tầng 1 — entities + ctx.entities | Nâng từ "quy ước tên view + tự JOIN" lên capability có kiểm soát scope |
| P2 Copy tại thời điểm | Quy ước (không cần cơ chế) | Đọc qua tầng 1 lúc ghi, copy vào bảng mình |
| P3 Service + callback | Tầng 2 — services + ctx.services | In-process thay vì HTTP; retry qua tầng 3 |
| P4 Event một chiều | Tầng 3 — outbox platform | Generalize dispatcher của workflow |
| P5 Bridge module | depends (đã có) + tầng 1/2 | Không cần cơ chế mới |
| P6 Snapshot / MV | Quy ước + tầng 1 | Query view qua entity, snapshot vào file/MV |
FE gọi GET /api/entities/course.khoa_hoc?q=… — mọi module có dropdown chọn khóa học / nhân viên / xe mà không cần biết module kia. (Cách Frappe render Link field, Dataverse render lookup.)
uninstallModule hiện chỉ chặn theo depends; có entity metadata thì chặn/cảnh báo cả theo dữ liệu đang tham chiếu (Dataverse làm đúng thế này).
Đổi/bỏ field → publish khoa_hoc_v2, view cũ giữ nguyên; SDK lint breaking change lúc opengate-module build vì contract nằm trong manifest.
Built-in dần publish entity như mọi module (hrm.employee, org.department…) — xoá tình trạng hai chế độ cực đoan. Đường Salesforce đi: standard & custom object chung một metadata engine. Hub entities chính là các built-in này.
| 1 | manifest-schema: thêm block entities (+ services); validator lint tên/scope tương ứng |
| 2 | runtime-core: registry entity/service khi mount; đối chiếu scopes → inject ctx.entities, ctx.services; degrade khi nguồn vắng mặt |
| 3 | Enforce scopes (hiện chỉ validate hình dạng): dùng entity/service không khai scope → lỗi lúc mount (fail fast) |
| 4 | Outbox platform: bảng outbox + worker retry trong host; migrate dispatcher của workflow sang |
| 5 | Generic entity API + lookup component cho FE (đợt 2, sau khi tầng 1 chạy) |
| 6 | Built-in publish entity đợt đầu: hrm.employee · org.department · catalog.* — module store Vĩnh An (student, course, dat…) tiêu thụ |
| # | Câu hỏi | Đề xuất |
|---|---|---|
| 1 | Điểm enforcement: capability trong Node hay Postgres role + GRANT per-module? | Capability trong Node — rẻ, đủ, khớp kiến trúc 1 process/WS. Postgres role chỉ đáng khi chạy code thật sự untrusted (đắt: pool per role, SET ROLE mỗi query) |
| 2 | Phạm vi tầng 1 đợt đầu: chỉ ctx.entities backend hay kèm lookup API cho FE? | Backend trước — nhỏ, tự chứa, test độc lập; lookup API đợt 2 |
| 3 | Built-in có bị ép theo contract không? | Grandfather built-in (giữ shared models nội bộ), chỉ yêu cầu publish entity cho module store dùng — hội tụ dần, không refactor big-bang |