ภาพรวม (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 ถูกจัดเรียงเป็นลำดับชั้นแบบนี้:
จากภาพด้านบน: เสาหลักสามต้น เรียงจากซ้ายไปขวาคือ "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 (เรียงจากบนลงล่าง):
- App — ทุกอย่างที่ทำให้แอปทำงานได้ — routing, entrypoints, global styles, providers
- Processes (deprecated) — complex inter-page scenarios (เลิกใช้แล้ว)
- Pages — หน้าเว็บเต็มๆ หรือส่วนใหญ่ของหน้าใน nested routing
- Widgets — ชิ้นส่วนฟังก์ชันหรือ UI ขนาดใหญ่ที่จบในตัว มักจะครอบคลุม use case หนึ่งๆ ได้เบ็ดเสร็จ
- Features — การ implement ฟีเจอร์ของโปรดักต์ที่ นำไปใช้ซ้ำได้ (reused) คือการกระทำที่ส่งมอบคุณค่าทางธุรกิจให้ผู้ใช้
- Entities — business entities ที่โปรเจกต์ต้องจัดการ เช่น
userหรือproduct - Shared — โค้ดที่นำกลับมาใช้ซ้ำได้ โดยเฉพาะโค้ดที่ไม่ขึ้นกับ project/business logic (แต่ก็ไม่จำเป็นเสมอไปนะ)
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 logiclib— 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 เราแนะนำกลยุทธ์ตามนี้ ซึ่งเราพบว่าเวิร์กมากจากประสบการณ์ย้ายของพวกเราเอง:
-
เริ่มจากการค่อยๆ จัดระเบียบ layers App และ Shared ทีละโมดูล เพื่อสร้างรากฐานที่แข็งแรงก่อน
-
กระจาย UI ที่มีอยู่ทั้งหมดลงไปใน Widgets และ Pages แบบกว้างๆ ไปก่อน ถึงแม้จะมี dependencies ที่ผิดกฎการ import ของ FSD บ้างก็ไม่เป็นไร
-
เริ่มค่อยๆ แก้ไขการ import ที่ผิดกฎ และดึง Entities หรือ Features ออกมา
ข้อแนะนำคือ อย่าเพิ่งเพิ่ม entities ใหญ่ๆ เข้ามาใหม่ในระหว่างที่กำลัง refactor หรือถ้าจะทำ feature ใหม่ก็ควร refactor เฉพาะส่วนที่เกี่ยวข้องเท่านั้นจะดีกว่า
ขั้นตอนต่อไป
- อยากเข้าใจวิธีคิดแบบ FSD ให้มากขึ้น? ลองดูที่ Tutorial
- ชอบเรียนรู้จากตัวอย่าง? เรามีตัวอย่างเพียบในหัวข้อ Examples
- มีคำถามสงสัย? แวะมาคุยกันได้ที่ Telegram chat ให้คอมมูนิตี้ช่วยตอบได้เลย 😊