# Architecture Decisions — DBK Platform > **Platform scope:** DBK Platform architecture decisions. HomAroma references in this ADR are reference-tenant context unless a decision explicitly names an operational resource. See [`docs/PLATFORM_OVERVIEW.md`](docs/PLATFORM_OVERVIEW.md). > บันทึกการตัดสินใจเชิงสถาปัตยกรรม (ADR) จากการประชุมทีม > อัปเดตล่าสุด: 2026-07-12 --- ## 1. Page Builder CMS — Section Types ที่มีอยู่ ระบบมี Block/Section ทั้งหมด **19 แบบ** กำหนดใน `packages/types/index.ts` → `PAGE_SECTION_TYPES` ### กลุ่ม Headers & Banners | Type | คำอธิบาย | |---|---| | `HERO` | Hero หลัก (image, text, 1 button) | | `HERO_V2` | Hero อัปเกรด (2 buttons, alignment, ornament) | | `BANNER` | แบนเนอร์รูปภาพ + link | | `ANNOUNCEMENT_BAR` | แถบประกาศด้านบน (auto-slide, ปรับสีได้) | ### กลุ่ม Products & Categories | Type | คำอธิบาย | |---|---| | `PRODUCTS` | แสดงสินค้า (by brand/category/custom, grid/carousel) | | `PRODUCT_CAROUSEL` | สไลด์สินค้าแนวนอน | | `CATEGORIES` | แสดงหมวดหมู่สินค้า | ### กลุ่ม Content & Editorials | Type | คำอธิบาย | |---|---| | `TEXT` | ข้อความทั่วไป (จัดตำแหน่งได้) | | `RICH_TEXT` | เนื้อหาจัดรูปแบบได้ | | `EDITORIAL_TILE` | รูปภาพ + เนื้อหา (สลับซ้าย-ขวาได้) | | `EDITORIAL_CAROUSEL` | สไลด์บทความ (standard / split screen) | | `HERO_ARTICLE` | หัวบทความ (ผู้เขียน, วันที่, หมวดหมู่) | | `QUOTE_CALLOUT` | โควทคำพูด / คำโปรย | ### กลุ่ม Brand Features | Type | คำอธิบาย | |---|---| | `SCENT_NOTES_BREAKDOWN` | แสดง Top/Heart/Base Notes ของกลิ่น | | `USP_BAR` | จุดเด่นของร้าน (max 4 items) | | `ACCORDION` | Q&A / FAQ แบบย่อ-ขยาย | ### กลุ่ม Forms & Contacts | Type | คำอธิบาย | |---|---| | `NEWSLETTER` | ฟอร์มรับอีเมล + privacy checkbox | | `CONTACT_INFO` | ที่อยู่, อีเมล, LINE, เวลาทำการ | | `CONTACT_FORM` | ฟอร์มส่งข้อความ | --- ## 2. Multi-Client White-Label Strategy ### หลักการสำคัญ > **CMS เก็บข้อมูล (Data + Semantics) — Renderer คือสิ่งที่เปลี่ยนตาม Client** ความแตกต่างระหว่าง Client มี 2 ระดับที่แก้ต่างกัน: | ระดับ | ตัวอย่าง | แก้ด้วย | |---|---|---| | **Visual Style** | สี, font, spacing ต่างกัน | Design Tokens / CSS Variables | | **Layout Structure** | Card ตั้ง vs Card นอน (HTML ต่างกัน) | Component Variants ใน Renderer | > Theme Token ช่วยไม่ได้กับ Layout Structure เพราะ HTML shape ต่างกันทั้งหมด ### Git Repository Strategy ``` [product-template] ← Source of Truth เสมอ │ ├── fork → [homaroma-client] ├── fork → [client-a-repo] │ └── good feature → cherry-pick กลับ product-template └── fork → [client-b-repo] ← clone จาก product-template (ที่ updated แล้ว) ``` **กฎ:** Client B ไม่ได้ start จาก Client A แต่ start จาก **product-template ที่รับ feature ดีๆ กลับมาแล้ว** ### Commit Discipline (สำคัญมาก) ```bash # ✅ แยก commit ให้ชัดเสมอ git commit -m "feat(categories): add horizontal-card layout variant" # generic → กลับ master ได้ git commit -m "feat(client-a): set default theme to warm-beige" # client-specific git commit -m "fix(checkout): correct discount calculation" # generic → กลับ master ได้ # ❌ อย่าทำแบบนี้ git commit -m "client A changes" # cherry-pick ยากมาก ``` ### Decision: Feature กลับ master ได้หรือไม่? | ลักษณะ Feature | กลับ master? | |---|---| | Generic component / layout variant ใหม่ | ✅ ควร | | Bug fix | ✅ เสมอ | | Infrastructure / DevOps | ✅ เสมอ | | Branding เฉพาะ client (สี, logo) | ❌ | | Business logic เฉพาะ client | ❌ | | Config ที่ structure generic แต่ค่าต่างกัน | ✅ structure กลับ, ค่าอยู่ที่ client | --- ## 3. componentRegistry Pattern ### เป้าหมาย ให้ **Backend + Schema กลางไม่ต้องเปลี่ยน** เมื่อ Client ต้องการ Layout ที่ต่างออกไป ### โครงสร้างที่ควรมี ``` apps/spa/src/ config/ componentRegistry.ts ← Client-specific (ไม่ขึ้น master) themeConfig.ts ← Client-specific components/ dynamic/ categories/ VerticalCard.tsx ← Generic variant (ขึ้น master) HorizontalCard.tsx ← Generic variant (ขึ้น master) ``` ### การทำงาน ```ts // apps/spa/src/config/componentRegistry.ts export const componentRegistry = { CATEGORIES: DynamicCategoriesVertical, // HomAroma เลือก vertical HERO: DynamicHeroV2, } // DynamicPageRenderer.tsx — generic, ไม่มี hardcode switch-case sections.map(section => { const Component = componentRegistry[section.type] return }) ``` **ผลลัพธ์:** เพิ่ม Client ใหม่ = สร้าง `componentRegistry.ts` ใหม่ + component ที่ต้องการ ไม่แตะ Backend เลย --- ## 4. CUSTOM Block — Developer Escape Hatch ### เหตุผลที่ต้องมี ทุกครั้งที่ Dev เพิ่ม Section ใหม่แบบเดิม ต้องแก้ **4 จุดพร้อมกัน:** 1. `packages/types/index.ts` → Schema + Type 2. `apps/backend/routes/pages.ts` → validation 3. `apps/spa/DynamicPageRenderer.tsx` → render 4. `apps/admin/PageBuilderPage.tsx` → form ถ้าเป็น client-specific section → ต้องแก้ Schema กลาง (ผิดหลักการ) ### แนวทาง: CUSTOM Block Type ```json { "type": "CUSTOM", "payload": { "componentId": "BakeryCategories", "props": { "title": "หมวดหมู่ของเรา", "layout": "horizontal" } } } ``` ### Dev Workflow ใหม่ (2 ขั้นตอน ทำแค่ Frontend) ```ts // Step 1: สร้าง Component ใดๆ ก็ได้ // apps/spa/src/components/custom/BakeryCategories.tsx export default function BakeryCategories({ title, layout }) { return
...JSX ได้เลย ไม่มีข้อจำกัด...
} // Step 2: Register ใน customRegistry // apps/spa/src/config/componentRegistry.ts export const customRegistry = { 'BakeryCategories': BakeryCategories, } ``` **Backend ไม่ต้องแก้เลย** ### กฎการตัดสินใจ ``` ถาม: "Section นี้จะถูกใช้ใน project อื่นด้วยไหม?" ใช่ → สร้าง Block Type ใหม่ตาม flow เดิม (เพิ่มเข้า master) ไม่ → ใช้ CUSTOM block + register ใน client customRegistry เท่านั้น ``` --- ## 5. Backlog — สิ่งที่ต้อง Implement ### ต้องทำ (ครั้งเดียว — foundation) - [ ] เพิ่ม `'CUSTOM'` เข้า `PAGE_SECTION_TYPES` ใน `packages/types/index.ts` - [ ] สร้าง `apps/spa/src/config/componentRegistry.ts` แยกออกมาจาก DynamicPageRenderer - [ ] เพิ่ม `case 'CUSTOM'` ใน `apps/spa/src/components/DynamicPageRenderer.tsx` - [ ] (Optional) เพิ่มใน `apps/website` ด้วย ### ควรทำ (เพิ่ม variants) - [ ] สร้าง `HorizontalCard.tsx` variant ใน `components/dynamic/categories/` - [ ] สร้าง `VerticalCard.tsx` (refactor จากของเดิม) ### Long-term - [ ] `packages/ui/` — generic component library สำหรับ white-label --- ## 6. Anti-patterns — สิ่งที่ไม่ควรทำ - ❌ **อย่าเพิ่ม UI layout ลงใน Schema กลาง** เช่น `layout: 'vertical' | 'horizontal'` ใน `CategoriesSectionPayloadSchema` — Schema จะพองและผูก UI กับ Data - ❌ **อย่าให้ Admin ใส่ Raw HTML ตรงๆ** — XSS risk และ bypass design system - ❌ **อย่า start Client B จาก Client A repo** — ใช้ product-template เสมอ - ❌ **อย่า commit รวมกัน** เช่น "client A changes" — cherry-pick ยากมาก - ❌ **อย่า hardcode switch-case ใน DynamicPageRenderer** — ใช้ componentRegistry แทน