ข้ามไปยังเนื้อหาหลัก

ภาพรวม (Overview)

Feature-Sliced Design (FSD) คือแนวทางการออกแบบสถาปัตยกรรมสำหรับ frontend application พูดง่ายๆ ก็คือ เป็นการรวบรวมกฎและข้อตกลงในการจัดระเบียบโค้ดนั่นเอง เป้าหมายหลักคือทำให้โปรเจกต์เข้าใจง่ายและมีความเสถียร (Stable) เสมอ แม้ว่าความต้องการทางธุรกิจจะเปลี่ยนไปบ่อยแค่ไหนก็ตาม 🚀

นอกจากชุดข้อตกลงแล้ว FSD ยังมาพร้อมกับเครื่องมือช่วยทุ่นแรงอีกด้วย เรามี linter เพื่อเช็คสถาปัตยกรรมของโปรเจกต์, folder generators ผ่าน CLI หรือ IDEs รวมไปถึงคลัง examples อีกเพียบให้ศึกษา

FSD เหมาะกับเราไหมนะ?

FSD เอาไปใช้ได้กับโปรเจกต์และทีมทุกขนาดเลยนะ มันจะเข้ากับโปรเจกต์ของคุณเป๊ะๆ ถ้า:

  • คุณทำ frontend (UI บนเว็บ, มือถือ, หรือเดสก์ท็อป ฯลฯ)
  • คุณกำลังสร้าง application (ไม่ใช่ library)

แค่นั้นแหละ! ไม่มีข้อจำกัดเลยว่าจะใช้ภาษาอะไร, UI framework ตัวไหน หรือ state manager อะไร นอกจากนี้ยังค่อยๆ ปรับใช้ทีละนิด (incrementally) ได้, ใช้ใน monorepos ก็ได้ แถมยังสเกลโปรเจกต์ได้ยาวๆ ด้วยการแตกแอปออกเป็น packages แล้วใช้ FSD แยกกันในแต่ละส่วน

ถ้าคุณมีสถาปัตยกรรมเดิมอยู่แล้วและกำลังลังเลว่าจะย้ายมา FSD ดีไหม ลองเช็คดูว่าสถาปัตยกรรมปัจจุบัน สร้างปัญหา ให้ทีมหรือเปล่า? เช่น โปรเจกต์เริ่มใหญ่จนพันกันยุ่งเหยิง จะเพิ่มฟีเจอร์ใหม่ทีก็ลำบาก หรือคาดว่ากำลังจะมีคนใหม่เข้าทีมเยอะๆ

ถ้าของเดิมยังเวิร์กดีอยู่ ก็อาจจะไม่คุ้มที่จะเปลี่ยนนะ แต่ถ้าตัดสินใจแล้วว่าจะย้าย ลองดูแนวทางที่หัวข้อ Migration ได้เลย

ตัวอย่างเบื้องต้น

นี่คือตัวอย่างโปรเจกต์ง่ายๆ ที่ใช้ FSD:

  • 📁 app
  • 📁 pages
  • 📁 shared

โฟลเดอร์ระดับบนสุดเหล่านี้เรียกว่า Layers (เลเยอร์) ลองเจาะลึกเข้าไปดูข้างในกัน:

  • 📂 app
    • 📁 routes
    • 📁 analytics
  • 📂 pages
    • 📁 home
    • 📂 article-reader
      • 📁 ui
      • 📁 api
    • 📁 settings
  • 📂 shared
    • 📁 ui
    • 📁 api

โฟลเดอร์ข้างใน 📂 pages เรียกว่า Slices (สไลซ์) ซึ่งจะแบ่งเลเยอร์ตามโดเมน (ในกรณีนี้คือแบ่งตามหน้า)

ส่วนโฟลเดอร์ข้างใน 📂 app, 📂 shared, และ 📂 pages/article-reader เรียกว่า Segments (เซกเมนต์) ซึ่งจะแบ่ง slices (หรือ layers) ตามหน้าที่ทางเทคนิค เช่น โค้ดส่วนนี้มีไว้ทำอะไร

คอนเซปต์ต่าง ๆ

Layers, slices, และ segments ถูกจัดเรียงเป็นลำดับชั้นแบบนี้:

Hierarchy of FSD concepts, described below

จากภาพด้านบน: เสาหลักสามต้น เรียงจากซ้ายไปขวาคือ "Layers", "Slices", และ "Segments"

เสา "Layers" ประกอบด้วย 7 ส่วน เรียงจากบนลงล่างได้แก่ "app", "processes", "pages", "widgets", "features", "entities", และ "shared" โดยส่วน "processes" ถูกขีดฆ่าไว้ ส่วน "entities" เชื่อมโยงกับเสาที่สอง "Slices" เพื่อสื่อว่าเสาที่สองคือเนื้อหาภายใน "entities"

เสา "Slices" ประกอบด้วย 3 ส่วน เรียงจากบนลงล่างได้แก่ "user", "post", และ "comment" โดยส่วน "post" เชื่อมโยงกับเสาที่สาม "Segments" ในลักษณะเดียวกัน

เสา "Segments" ประกอบด้วย 3 ส่วน เรียงจากบนลงล่างคือ "ui", "model", และ "api"

Layers (เลเยอร์)

Layers เป็นมาตรฐานที่เหมือนกันในทุกโปรเจกต์ FSD คุณไม่จำเป็นต้องใช้ครบทุก layer แต่ชื่อของมันมีความสำคัญมาก ปัจจุบันมีทั้งหมด 7 layers (เรียงจากบนลงล่าง):

  1. App — ทุกอย่างที่ทำให้แอปทำงานได้ — routing, entrypoints, global styles, providers
  2. Processes (deprecated) — complex inter-page scenarios (เลิกใช้แล้ว)
  3. Pages — หน้าเว็บเต็มๆ หรือส่วนใหญ่ของหน้าใน nested routing
  4. Widgets — ชิ้นส่วนฟังก์ชันหรือ UI ขนาดใหญ่ที่จบในตัว มักจะครอบคลุม use case หนึ่งๆ ได้เบ็ดเสร็จ
  5. Features — การ implement ฟีเจอร์ของโปรดักต์ที่ นำไปใช้ซ้ำได้ (reused) คือการกระทำที่ส่งมอบคุณค่าทางธุรกิจให้ผู้ใช้
  6. Entities — business entities ที่โปรเจกต์ต้องจัดการ เช่น user หรือ product
  7. Shared — โค้ดที่นำกลับมาใช้ซ้ำได้ โดยเฉพาะโค้ดที่ไม่ขึ้นกับ project/business logic (แต่ก็ไม่จำเป็นเสมอไปนะ)
warning

Layers App และ Shared จะต่างจาก layers อื่นตรงที่ไม่มี slices แต่จะแบ่งเป็น segments โดยตรงเลย

ส่วน layers อื่นๆ ทั้งหมด — Entities, Features, Widgets, และ Pages — จะยังคงโครงสร้างเดิม คือต้องสร้าง slices ก่อน แล้วค่อยสร้าง segments ข้างใน

เคล็ดลับของ layers คือ โมดูลใน layer หนึ่งจะสามารถรู้จักและ import โมดูลจาก layers ที่อยู่ ต่ำกว่าอย่างเคร่งครัด เท่านั้น

Slices (สไลซ์)

ต่อมาคือ slices ซึ่งจะแบ่งโค้ดตาม business domain คุณมีอิสระเต็มที่ในการตั้งชื่อและจะสร้างกี่อันก็ได้ Slices ช่วยให้ code base เลื่อนดูง่ายขึ้น (navigate) เพราะมันจับกลุ่มโมดูลที่เกี่ยวข้องกันไว้ด้วยกัน

Slices ไม่สามารถใช้งาน slices อื่นที่อยู่ใน layer เดียวกันได้ กฎนี้แหละที่ช่วยทำให้เกิด high cohesion และ low coupling ✨

Segments (เซกเมนต์)

Slices (รวมถึง layers App และ Shared) จะประกอบไปด้วย segments และ segments จะจัดกลุ่มโค้ดตามวัตถุประสงค์การใช้งาน ชื่อของ Segment ไม่ได้มีข้อบังคับตายตัวตามมาตรฐาน แต่ก็มีชื่อที่นิยมใช้กันบ่อยๆ ตามหน้าที่หลักๆ ดังนี้:

  • ui — ทุกอย่างที่เกี่ยวกับการแสดงผล UI: UI components, date formatters, styles ฯลฯ
  • api — การติดต่อกับ backend: request functions, data types, mappers ฯลฯ
  • model — data model: schemas, interfaces, stores, และ business logic
  • lib — library code ที่โมดูลอื่นๆ ใน slice นี้จำเป็นต้องใช้
  • config — ไฟล์ configuration และ feature flags

ปกติแล้ว segments เหล่านี้ก็เพียงพอสำหรับ layers ส่วนใหญ่ คุณอาจจะสร้าง segments ของตัวเองเพิ่มใน Shared หรือ App ก็ได้ ไม่ผิดกติกา

ข้อดี

  • ความเป็นระเบียบเดียวกัน (Uniformity)
    เมื่อโครงสร้างเป็นมาตรฐาน โปรเจกต์ต่าง ๆ ก็จะเป็นไปในทิศทางเดียวกัน ทำให้คนใหม่ที่เข้ามาเรียนรู้งานได้ง่ายขึ้นมาก

  • มั่นคงต่อการเปลี่ยนแปลงและ Refactor
    โมดูลใน layer หนึ่งไม่สามารถใช้โมดูลอื่นใน layer เดียวกัน หรือ layer ที่อยู่เหนือกว่าได้
    ทำให้เราสามารถแก้ไขส่วนต่างๆ แยกกันได้อย่างสบายใจ โดยไม่ต้องกลัวว่าจะไปกระทบส่วนอื่นแบบไม่รู้ตัว

  • ควบคุมการใช้ซ้ำได้ดี (Controlled reuse of logic)
    ขึ้นอยู่กับ layer เราสามารถทำให้โค้ดนำไปใช้ซ้ำได้ง่าย หรือจะจำกัดขอบเขตให้เป็นแบบ local ก็ได้
    ช่วยรักษาสมดุลระหว่างหลักการ DRY และการนำไปใช้งานจริง

  • เน้นตอบโจทย์ธุรกิจและผู้ใช้
    แอปถูกแบ่งตาม business domains และสนับสนุนให้ใช้ภาษาทางธุรกิจในการตั้งชื่อ ทำให้เราสามารถลุยงาน Product ได้อย่างมีประสิทธิภาพ โดยไม่ต้องทำความเข้าใจส่วนอื่นที่ไม่เกี่ยวข้องทั้งหมด

การปรับใช้ทีละส่วน

ถ้าคุณมี codebase เดิมอยู่แล้วและอยากย้ายมาใช้ FSD เราแนะนำกลยุทธ์ตามนี้ ซึ่งเราพบว่าเวิร์กมากจากประสบการณ์ย้ายของพวกเราเอง:

  1. เริ่มจากการค่อยๆ จัดระเบียบ layers App และ Shared ทีละโมดูล เพื่อสร้างรากฐานที่แข็งแรงก่อน

  2. กระจาย UI ที่มีอยู่ทั้งหมดลงไปใน Widgets และ Pages แบบกว้างๆ ไปก่อน ถึงแม้จะมี dependencies ที่ผิดกฎการ import ของ FSD บ้างก็ไม่เป็นไร

  3. เริ่มค่อยๆ แก้ไขการ import ที่ผิดกฎ และดึง Entities หรือ Features ออกมา

ข้อแนะนำคือ อย่าเพิ่งเพิ่ม entities ใหญ่ๆ เข้ามาใหม่ในระหว่างที่กำลัง refactor หรือถ้าจะทำ feature ใหม่ก็ควร refactor เฉพาะส่วนที่เกี่ยวข้องเท่านั้นจะดีกว่า

ขั้นตอนต่อไป

  • อยากเข้าใจวิธีคิดแบบ FSD ให้มากขึ้น? ลองดูที่ Tutorial
  • ชอบเรียนรู้จากตัวอย่าง? เรามีตัวอย่างเพียบในหัวข้อ Examples
  • มีคำถามสงสัย? แวะมาคุยกันได้ที่ Telegram chat ให้คอมมูนิตี้ช่วยตอบได้เลย 😊