ทำไมต้อง “ออกแบบสถาปัตยกรรม FE” ตั้งแต่ต้น
เวลาระบบยังเล็ก ทุกอย่างดูง่าย แต่พอโปรเจกต์โตขึ้น ฟีเจอร์มากขึ้น คนเข้ามาทำงานเยอะขึ้น โค้ดก็เริ่มปนกันยุ่งเหยิง: UI ปะปนกับ logic, ทดสอบยาก, แก้จุดหนึ่งพังอีกจุดหนึ่ง 😵💫
สิ่งที่ช่วยได้คือ การตั้งโครงสร้างและภาษากลางทั้งทีม ตั้งแต่แรก ว่าใครทำอะไร ส่วนไหนรับผิดชอบอะไร และข้อมูลไหลไปมาอย่างไร
1) ภาษากลาง: Journey → Page → Step → Widget → Atom
แทนที่จะคิดแค่ “หน้าจอ” ให้ลองคิดเป็น “เรื่องราวการใช้งานของผู้ใช้” แล้วแตกออกเป็นชั้น ๆ
- Journey Experience → กลุ่มของหน้า เช่น
/je_home,/je_profile - Page → 1 หน้าจอใน Journey Experience นั้น
- Journey → เรื่องราว เช่น “ซื้อแพ็กเกจ”, “ดูยอดคงเหลือ”
- Step → ขั้นตอนย่อย เช่น Step0 เลือกแพ็ก, Step1 ยืนยัน, Step2 ชำระเงิน
- Widget → กลุ่มของ Atom ที่นำมาใช้ซ้ำได้
- Atom → หน่วยเล็กที่สุด เช่น ปุ่ม, ฟอนต์, ไอคอน
การตั้งชื่อและคิดแบบนี้ช่วยให้ ทุกคนในทีมเข้าใจตรงกัน และสามารถ reuse flow หรือ UI ได้จริง
2) Clean Architecture: แบ่งหน้าที่ 3 เลเยอร์
หัวใจคือ “แยกบทบาท” ให้ชัด ไม่ให้ทุกอย่างปนกัน:
- Presentation (UI) → แสดงผล, รับอินพุต, สื่อสารกับ Domain
- Domain (Business Logic) → เก็บ use case, ตัดสินใจ, ทำงานผ่าน Repository Interface
- Data (Access Layer) → ติดต่อ API/DB, แปลงข้อมูลให้ Domain ใช้ได้
ข้อดี:
- เปลี่ยน UI หรือ data source ได้โดยไม่กระทบทั้งระบบ
- เขียนเทสต์ง่ายขึ้น
- โครงสร้างขยายได้ในอนาคต
3) BLoC: แยกสมองออกจาก UI
BLoC (Business Logic Component) คือการจัดการ state แบบ Event-driven
- Event → สิ่งที่ผู้ใช้ทำ เช่น กดปุ่ม, โหลดข้อมูล
- BLoC → ประมวลผล (รวมถึงเรียก API/DB)
- State → สถานะกลับไปให้ UI วาดผล
UI จึงเป็น Dumb UI ไม่ปน logic ทำให้เดาง่ายและเทสต์ง่าย
4) Stream Strategy: จัดการ Event ให้เหมาะกับ UX
เวลาออกแบบ flow ที่รับ event ต่อเนื่อง ควรกำหนดกลยุทธ์ที่ชัดเจน เช่น:
- Concurrent → ทำหลายงานพร้อมกัน
- Sequential → ทำทีละงาน เข้าคิว
- Restartable → ยกเลิกงานเก่า ถ้ามีงานใหม่มา
- Droppable → ทำงานแรกต่อให้เสร็จ แล้วทิ้งงานใหม่ไป
ตัวอย่าง: ปุ่มกดซื้อ → ใช้ droppable หรือ restartable จะปลอดภัยกว่าการทำงานซ้อนกัน
5) Realm: Offline-first + Reactive
Realm Database ช่วยให้ระบบเร็วและลื่นขึ้น:
- Offline-first → อ่าน/เขียนข้อมูลที่เครื่องตลอด ไม่ต้องรอเน็ต
- Sync อัตโนมัติ → ซิงก์กับ backend เบื้องหลังเมื่อออนไลน์
- Reactive UI → ข้อมูลเปลี่ยน UI เปลี่ยนตามทันที
- Performance → เร็วกว่าการใช้ SQLite หลายเท่า (จากการทดสอบ)
- Query Language (RQL) → ใช้ง่าย เช่น sort, distinct, limit
6) Navigation & Parameters: Flow ต้องตามกลับได้
ระบบจริงไม่ใช่แค่กดไป-กลับ แต่มีการ:
- ข้ามไป step ใหม่
- ข้ามไป journey อื่น
- Pop กลับมาหน้าเดิม
พร้อมกับส่ง/เก็บ parameter เช่น param1, param2 ให้ไม่หายและไม่ปนกัน
การออกแบบ flow และ param ให้ชัดเจนตั้งแต่แรก จะช่วยลดบั๊กได้เยอะ
7) Modularization: แตกโมดูลให้เป็นอิสระ
เมื่อระบบใหญ่ขึ้น ให้แยกแต่ละ feature เป็นโมดูล เช่น
- core-network
- core-data
- feature-buy-pack
- feature-profile
แต่ละโมดูลก็ใช้ Clean Architecture เหมือนกัน โค้ดจะดูแลง่าย ย้ายคนทำงานได้ไม่สะดุด และระบบขยายได้เป็นขั้นตอน
8) Call Flow & Workshop: แผนที่ของข้อมูล
เส้นทางข้อมูลควรชัดเจน:
UI → BLoC → Use Case → Repository → Data Source → Repository → Use Case → BLoC → UI
ก่อนเริ่ม workshop ควรเขียน flow แบบนี้ออกมาเสมอ:
- กำหนด JE/Page/Journey/Step
- ระบุ Event ที่เกิดขึ้น
- ระบุ State ที่ต้องแสดง
- วาดเส้นทางข้อมูล (อ่าน/เขียน/ซิงก์/แคช)
Checklist ก่อนขึ้นโปรดักชัน
- ตั้งชื่อ JE/Page/Journey/Step ให้สื่อสารได้ในประโยคเดียว
- ทำ UI แบบ Dumb UI → ทุกอย่างผ่าน Event/State
- เลือก Stream Strategy ที่เหมาะกับ UX
- ใช้ Realm ถ้าอยากได้ความเร็ว ออฟไลน์ และ Reactive
- แตก โมดูล ตามโดเมน และใช้ Clean Architecture ทุกโมดูล
สรุป
- Journey/Page/Step/Widget/Atom → ภาษากลางทั้งทีม
- Clean Architecture → โครงสร้างชัด แก้ง่าย เทสต์ง่าย
- BLoC + Stream Strategy → ทำให้หน้าจอนิ่ง เดาได้
- Realm → ช่วยให้แอปลื่น ออฟไลน์ได้
- Modularization → ทำให้ระบบโตแบบเป็นระเบียบ
บทความนี้คือ playbook ที่ทีมสามารถยึดเป็นแนวทางเดียวกัน ตั้งแต่ Product, Design, Dev จนถึง QA 🚀