เล่าให้ทีมฟัง: Frontend Framework + Clean Architecture (ฉบับเข้าใจง่าย)

·

ทำไมต้อง “ออกแบบสถาปัตยกรรม 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 แบบนี้ออกมาเสมอ:

  1. กำหนด JE/Page/Journey/Step
  2. ระบุ Event ที่เกิดขึ้น
  3. ระบุ State ที่ต้องแสดง
  4. วาดเส้นทางข้อมูล (อ่าน/เขียน/ซิงก์/แคช)

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 🚀