สถาปัตยกรรมที่สะอาดใน next.js
repo นี้เป็นตัวอย่างของวิธีการบรรลุสถาปัตยกรรมที่สะอาดใน next.js มีวิดีโอสอนที่ผ่านโครงการนี้ คลิกที่ภาพเพื่อตรวจสอบบน YouTube:
คุณสามารถเรียกใช้โครงการได้เพียงแค่เรียกใช้ npm install และ npm run dev
สถาปัตยกรรมที่สะอาด

บันทึก
- ฉันวาดไดอะแกรมสถาปัตยกรรมที่เรียบง่ายรุ่นนี้ ฉันทำให้มันง่ายขึ้นในวิธีที่ทำให้ฉันมีเหตุผลมากขึ้นและมันง่ายกว่าที่จะเข้าใจ ฉันหวังว่ามันจะช่วยคุณได้เช่นกัน
ฉันขอแนะนำให้คุณอ่านบทความต้นฉบับโดยลุงบ๊อบถ้านี่เป็นครั้งแรกที่คุณได้ยินเกี่ยวกับสถาปัตยกรรมที่สะอาด แต่ฉันจะพยายามสรุปให้คุณด้านล่าง
Clean Architecture เป็น ชุดของกฎ ที่ช่วยให้เราจัดโครงสร้างแอปพลิเคชันของเราในลักษณะที่ง่ายต่อการบำรุงรักษาและทดสอบและรหัสฐานของพวกเขาสามารถคาดเดาได้ มันเหมือนกับภาษาทั่วไปที่นักพัฒนาเข้าใจโดยไม่คำนึงถึงภูมิหลังทางเทคนิคและการตั้งค่าภาษาการเขียนโปรแกรม
สถาปัตยกรรมที่สะอาดและสถาปัตยกรรมที่คล้ายกัน/ได้มาทั้งหมดมีเป้าหมายเดียวกัน - การแยกข้อกังวล พวกเขาแนะนำ เลเยอร์ ที่มัดรหัสที่คล้ายกันเข้าด้วยกัน "เลเยอร์" ช่วยให้เราบรรลุประเด็นสำคัญใน codebase ของเรา:
- เป็นอิสระจาก UI - ตรรกะทางธุรกิจไม่ได้เชื่อมโยงกับกรอบ UI ที่ใช้ (ในกรณีนี้ next.js) ระบบเดียวกันสามารถใช้ในแอปพลิเคชัน CLI โดยไม่ต้องเปลี่ยนตรรกะหรือกฎทางธุรกิจ
- เป็นอิสระจากฐานข้อมูล - การใช้งานฐานข้อมูล/การดำเนินการถูกแยกในเลเยอร์ของตัวเองดังนั้นแอพที่เหลือไม่สนใจว่าจะใช้ฐานข้อมูลใด แต่สื่อสารโดยใช้ แบบจำลอง
- เป็นอิสระจากเฟรมเวิร์ก - กฎเกณฑ์ทางธุรกิจและตรรกะก็ไม่รู้อะไรเลยเกี่ยวกับโลกภายนอก พวกเขาได้รับข้อมูลที่กำหนดด้วยจาวาสคริปต์ธรรมดาใช้จาวาสคริปต์ธรรมดา บริการ และ ที่เก็บ เพื่อกำหนดตรรกะและฟังก์ชั่นของตนเอง สิ่งนี้ช่วยให้เราสามารถใช้เฟรมเวิร์กเป็นเครื่องมือแทนที่จะต้อง "แม่พิมพ์" ระบบของเราในการใช้งานและข้อ จำกัด ของพวกเขา หากเราใช้ตัวจัดการเส้นทางในแอพของเราและต้องการที่จะ refactor พวกเขาบางส่วนของพวกเขาในการกระทำของเซิร์ฟเวอร์สิ่งที่เราต้องทำคือเพียงเรียกใช้ ตัวควบคุม เฉพาะในการกระทำของเซิร์ฟเวอร์แทนที่จะเป็นตัวจัดการเส้นทาง แต่ตรรกะธุรกิจ หลัก ยังคงไม่เปลี่ยนแปลง
- ทดสอบได้ - ตรรกะทางธุรกิจและกฎสามารถทดสอบได้อย่างง่ายดายเนื่องจากไม่ได้ขึ้นอยู่กับเฟรมเวิร์ก UI หรือฐานข้อมูลหรือเว็บเซิร์ฟเวอร์หรือองค์ประกอบภายนอกอื่น ๆ ที่สร้างระบบของเรา
สถาปัตยกรรมที่สะอาดได้รับสิ่งนี้ผ่านการกำหนด ลำดับชั้นการพึ่งพา - เลเยอร์ขึ้นอยู่กับเลเยอร์ ด้านล่าง แต่ไม่ได้สูงกว่า
โครงสร้างโครงการ (เฉพาะบิตสำคัญ)
-
app - เลเยอร์เฟรมเวิร์กและไดรเวอร์ - โดยทั่วไปทุกอย่าง next.js (หน้า, การกระทำของเซิร์ฟเวอร์, ส่วนประกอบ, สไตล์ ฯลฯ ... ) หรืออะไรก็ตามที่ "ใช้" ตรรกะของแอป -
di - การฉีดพึ่งพา - โฟลเดอร์ที่เราตั้งค่าคอนเทนเนอร์ DI และโมดูล -
drizzle - DB ทุกอย่าง - การเริ่มต้นไคลเอนต์ DB, กำหนดสคีมา, การย้ายถิ่น -
src - "ราก" ของระบบ-
application - แอปพลิเคชันเลเยอร์ - ถือกรณีการใช้งานและอินเทอร์เฟซสำหรับที่เก็บและบริการ -
entities - เลเยอร์เอนทิตี - ถือโมเดลและข้อผิดพลาดที่กำหนดเอง -
infrastructure - เลเยอร์โครงสร้างพื้นฐาน - ถือการใช้งานของที่เก็บและบริการและดึงในอินเทอร์เฟซจาก application -
interface-adapters - อินเตอร์เฟสอะแดปเตอร์เลเยอร์ - เก็บคอนโทรลเลอร์ที่ทำหน้าที่เป็นจุดเข้าสู่ระบบ (ใช้ในเลเยอร์เฟรมเวิร์กและไดรเวอร์เพื่อโต้ตอบกับระบบ)
-
tests - การทดสอบหน่วยอาศัยอยู่ที่นี่ - โครงสร้างของโฟลเดอร์ย่อย unit ตรงกับ src -
.eslintrc.json - ที่ปลั๊กอิน eslint-plugin-boundaries ถูกกำหนด - สิ่งนี้จะหยุดคุณจากการทำลายกฎการพึ่งพา -
vitest.config.ts - จดบันทึกว่า @ นามแฝงถูกกำหนดอย่างไร!
คำอธิบายเลเยอร์
- Frameworks & Drivers : เก็บฟังก์ชั่น UI Framework ทั้งหมดและทุกอย่างอื่นที่โต้ตอบกับระบบ (เช่น AWS Lambdas, Stripe Webhooks ฯลฯ ... ) ในสถานการณ์นี้คือตัวจัดการเส้นทางถัดไป JS การกระทำของเซิร์ฟเวอร์ส่วนประกอบ (เซิร์ฟเวอร์และไคลเอนต์) หน้าระบบการออกแบบ ฯลฯ ...
- เลเยอร์นี้ควรใช้ คอนโทรลเลอร์ โมเดล และ ข้อผิดพลาด เท่านั้นและ ต้องไม่ ใช้ กรณีการใช้งาน ที่เก็บ และ บริการ
- อินเตอร์เฟสอะแดปเตอร์ : กำหนด คอนโทรลเลอร์ :
- คอนโทรลเลอร์ทำการ ตรวจสอบการรับรองความถูกต้อง และ การตรวจสอบอินพุต ก่อนส่งอินพุตไปยังกรณีการใช้งานเฉพาะ
- คอนโทรลเลอร์ orchestrate กรณีการใช้งาน พวกเขาไม่ได้ใช้ตรรกะใด ๆ แต่กำหนดการดำเนินการทั้งหมดโดยใช้กรณีการใช้งาน
- ข้อผิดพลาดจากเลเยอร์ที่ลึกกว่านั้นจะมีฟองสบู่และถูกจัดการในที่ที่มีการใช้คอนโทรลเลอร์
- คอนโทรลเลอร์ใช้ ผู้นำเสนอ เพื่อแปลงข้อมูลเป็นรูปแบบที่เป็นมิตรกับ UI ก่อนที่จะส่งคืนสู่ "ผู้บริโภค" สิ่งนี้ช่วยให้เราจัดส่ง JavaScript น้อยลงไปยังไคลเอนต์ (ตรรกะและไลบรารีในการแปลงข้อมูล) ช่วยป้องกันการรั่วไหลของคุณสมบัติที่ละเอียดอ่อนใด ๆ เช่นอีเมลหรือรหัสผ่านแฮชและยังช่วยให้เราลดจำนวนข้อมูลที่เราส่งกลับไปยังลูกค้า
- แอปพลิเคชัน : ที่ตรรกะทางธุรกิจอาศัยอยู่ บางครั้งเรียกว่า แกน เลเยอร์นี้กำหนดกรณีการใช้งานและอินเทอร์เฟซสำหรับบริการและที่เก็บ
- ใช้กรณี :
- เป็นตัวแทนของการดำเนินการส่วนบุคคลเช่น "Create Tode" หรือ "ลงชื่อเข้าใช้" หรือ "Toggle Tode"
- ยอมรับอินพุตที่ผ่านการตรวจสอบล่วงหน้า (จากตัวควบคุม) และ จัดการการตรวจสอบการอนุญาต
- ใช้ ที่เก็บ และ บริการ เพื่อเข้าถึงแหล่งข้อมูลและสื่อสารกับระบบภายนอก
- กรณีการใช้งานไม่ควรใช้กรณีการใช้งานอื่น ๆ นั่นคือรหัสที่มีกลิ่น หมายความว่ากรณีการใช้งานทำหลายสิ่งและควรแบ่งออกเป็นหลายกรณีการใช้งาน
- อินเทอร์เฟซสำหรับที่เก็บและบริการ:
- สิ่งเหล่านี้ถูกกำหนดไว้ในเลเยอร์นี้เพราะเราต้องการแยกการพึ่งพาเครื่องมือและเฟรมเวิร์กของพวกเขา (ไดรเวอร์ฐานข้อมูลบริการอีเมล ฯลฯ ... ) ดังนั้นเราจะนำไปใช้ในชั้น โครงสร้างพื้นฐาน
- เนื่องจากอินเทอร์เฟซอาศัยอยู่ในเลเยอร์นี้ให้ใช้กรณี (และเลเยอร์ด้านบนผ่าน) สามารถเข้าถึงได้ผ่าน การฉีดพึ่งพา
- การฉีดพึ่งพา ช่วยให้เราสามารถแยก คำจำกัดความ (อินเตอร์เฟส) ออกจาก การใช้งาน (คลาส) และเก็บไว้ในเลเยอร์แยกต่างหาก (โครงสร้างพื้นฐาน) แต่ยังอนุญาตให้ใช้งานได้
- เอนทิตี : ที่กำหนด แบบจำลอง และ ข้อผิดพลาด
- รุ่น :
- กำหนดรูปร่างข้อมูล "โดเมน" ด้วยจาวาสคริปต์ธรรมดาโดยไม่ต้องใช้เทคโนโลยี "ฐานข้อมูล"
- แบบจำลองไม่ได้เชื่อมโยงกับฐานข้อมูลเสมอไป - การส่งอีเมลต้องใช้บริการอีเมลภายนอกไม่ใช่ฐานข้อมูล แต่เรายังต้องมีรูปร่างข้อมูลที่จะช่วยให้เลเยอร์อื่นสื่อสาร "ส่งอีเมล"
- แบบจำลองยังกำหนดกฎการตรวจสอบความถูกต้องของตนเองซึ่งเรียกว่า "กฎเกณฑ์ทางธุรกิจขององค์กร" กฎที่มักจะไม่เปลี่ยนแปลงหรือมีแนวโน้มที่จะเปลี่ยนแปลงน้อยที่สุดเมื่อมีการเปลี่ยนแปลง ภายนอก (การนำทางหน้าความปลอดภัย ฯลฯ ... ) ตัวอย่างคือโมเดล
User ที่กำหนดฟิลด์ชื่อผู้ใช้ที่ต้องมี ความยาวอย่างน้อย 6 อักขระและไม่รวมอักขระพิเศษ
- ข้อผิดพลาด :
- เราต้องการข้อผิดพลาดของเราเองเพราะเราไม่ต้องการให้เกิดข้อผิดพลาดเฉพาะฐานข้อมูลหรือข้อผิดพลาดทุกประเภทที่เฉพาะเจาะจงกับไลบรารีหรือเฟรมเวิร์ก
- เรา
catch ข้อผิดพลาดที่มาจากไลบรารีอื่น ๆ (เช่น Drizzle) และแปลงข้อผิดพลาดเหล่านั้นเป็นข้อผิดพลาดของเราเอง - นั่นคือวิธีที่เราสามารถรักษา แกนกลาง ของเราให้เป็นอิสระจากกรอบงานห้องสมุดและเทคโนโลยีใด ๆ - หนึ่งในแง่มุมที่สำคัญที่สุดของสถาปัตยกรรมที่สะอาด
- โครงสร้างพื้นฐาน : มีการกำหนด ที่เก็บ และ บริการ
- เลเยอร์นี้จะดึงอินเทอร์เฟซของที่เก็บและบริการจาก เลเยอร์แอปพลิเคชัน และดำเนินการในชั้นเรียนของตนเอง
- ที่เก็บ เป็นวิธีที่เราใช้การดำเนินการฐานข้อมูล พวกเขาเป็นคลาสที่เปิดเผยวิธีการที่ดำเนินการฐานข้อมูลเดียวเช่น
getTodo หรือ createTodo หรือ updateTodo ซึ่งหมายความว่าเราใช้ไลบรารี / ไดรเวอร์ฐานข้อมูลในคลาสเหล่านี้เท่านั้น พวกเขาไม่ได้ทำการตรวจสอบข้อมูลใด ๆ เพียงแค่ดำเนินการสืบค้นและการกลายพันธุ์กับฐานข้อมูลและโยน ข้อผิดพลาด ที่กำหนดเองของเราหรือผลลัพธ์ส่งคืน - บริการ เป็นบริการที่ใช้ร่วมกันที่ใช้ในแอปพลิเคชันเช่นบริการตรวจสอบสิทธิ์หรือบริการอีเมลหรือใช้ระบบภายนอกเช่น Stripe (สร้างการชำระเงินตรวจสอบใบเสร็จรับเงิน ฯลฯ ... ) บริการเหล่านี้ยังใช้และขึ้นอยู่กับกรอบและห้องสมุดอื่น ๆ นั่นเป็นเหตุผลที่การดำเนินการของพวกเขาถูกเก็บไว้ที่นี่ควบคู่ไปกับที่เก็บ
- เนื่องจากเราไม่ต้องการให้เลเยอร์ใด ๆ ขึ้นอยู่กับอันนี้ (และขึ้นอยู่กับฐานข้อมูลและบริการทั้งหมด) เราจึงใช้ หลักการผกผันของการพึ่งพา สิ่งนี้ช่วยให้เราสามารถพึ่งพาอินเตอร์เฟสที่กำหนดไว้ใน เลเยอร์แอปพลิเคชัน แทนที่จะใช้งานใน เลเยอร์โครงสร้างพื้นฐาน เท่านั้น เราใช้ การผกผันของไลบรารีควบคุม เช่น ioctopus เพื่อเป็นนามธรรมการใช้งานที่อยู่เบื้องหลังอินเทอร์เฟซและ "ฉีด" เมื่อใดก็ตามที่เราต้องการ เราสร้างสิ่งที่เป็นนามธรรมในไดเรกทอรี
di เรา "ผูก" ที่เก็บ, บริการ, คอนโทรลเลอร์และการใช้เคสเป็นสัญลักษณ์และเรา "แก้ไข" พวกเขาโดยใช้สัญลักษณ์เหล่านั้นเมื่อเราต้องการการใช้งานจริง นั่นคือวิธีที่เราสามารถใช้การใช้งานได้โดยไม่จำเป็นต้องพึ่งพาอย่างชัดเจน (นำเข้า)
คำถามที่พบบ่อย
เคล็ดลับ
หากคุณมีคำถามที่ไม่ครอบคลุมโดยคำถามที่พบบ่อยอย่าลังเลที่จะเปิดปัญหาใน repo นี้หรือเข้าร่วมเซิร์ฟเวอร์ Discord ของฉันและเริ่มการสนทนาที่นั่น
Clean Architecture / การใช้งานนี้เป็นมิตรกับเซิร์ฟเวอร์หรือไม่? ฉันสามารถปรับใช้สิ่งนี้กับ vercel ได้หรือไม่?
ใช่! คุณสามารถใช้กับเราเตอร์หน้า, เราเตอร์แอพ, มิดเดิลแวร์, ตัวจัดการ API, การกระทำของเซิร์ฟเวอร์, อะไรก็ได้! โดยปกติแล้วการบรรลุการฉีดพึ่งพาในโครงการ JavaScript กำลังดำเนินการกับ Library Inversify.js ซึ่งไม่สอดคล้องกับรันไทม์อื่น ๆ ยกเว้นโหนด โครงการนี้ใช้ IOCTOPUS ซึ่งเป็นคอนเทนเนอร์ IOC ที่เรียบง่ายที่ไม่ได้พึ่งพา reflect-metadata และทำงานในทุกช่วงเวลา
ฉันควรเริ่มใช้งานสถาปัตยกรรมที่สะอาดทันทีเมื่อฉันสร้างโครงการต่อไปของฉัน
ฉันจะบอกว่า ไม่ หากคุณกำลังเริ่มโครงการใหม่เอี่ยมฉันขอแนะนำให้คุณมุ่งเน้นไปที่การบรรลุสถานะ MVP ให้เร็วที่สุด (เพื่อให้คุณสามารถตรวจสอบความคิดของคุณ / ดูว่ามีอนาคตสำหรับโครงการของคุณหรือไม่) เมื่อสิ่งต่าง ๆ เริ่มต้นขึ้นอย่างจริงจัง (คุณสมบัติเพิ่มเติมเริ่มดำเนินการฐานผู้ใช้ของคุณจะได้รับการเติบโตที่สำคัญหรือคุณกำลังเพิ่มนักพัฒนาอื่น ๆ ในโครงการของคุณ) นั่นคือเมื่อคุณต้องการลงทุนในการปรับสถาปัตยกรรมนี้ (หรือสถาปัตยกรรมใด ๆ สำหรับเรื่องนั้น)
หากคุณอยู่ลึกลงไปในวัชพืชในโครงการคุณ (และทีมงานของคุณ) สามารถวางแผนสำหรับการ refactoring แบบค่อยเป็นค่อยไปโดยเริ่มจากการวิ่งครั้งต่อไป ในกรณีนี้คุณมีรหัสที่เขียนอยู่แล้วคุณเพียงแค่ต้องจัดระเบียบใหม่นิดหน่อยและคุณสามารถทำส่วนนั้นได้โดยบางส่วนเส้นทางตัวจัดการเส้นทางโดย Route Handler, เซิร์ฟเวอร์การกระทำโดยการกระทำของเซิร์ฟเวอร์ โดยวิธีการที่ฉันพูดเบา ๆ "คุณเพียงแค่ต้องจัดระเบียบใหม่เล็กน้อย" แต่มันอาจจะไกลจากความเรียบง่ายอย่างนั้น คำนึงถึง "สิ่งที่ผิดพลาด" เสมอเมื่อคุณวางแผนการปรับโครงสร้างใหม่ และใช้เวลาในการเขียนการทดสอบ!
สิ่งนี้ดูเหมือนว่าจะมีการใช้วิศวกรรมมากเกินไปและทำให้การพัฒนาคุณมีความซับซ้อน
หากคุณไม่ได้ใช้เวลามากกว่า 3 นาทีคิดเกี่ยวกับเรื่องนี้ใช่แล้วมันดูเหมือนจะเป็นวิศวกรรมมากเกินไป แต่ถ้าคุณทำคุณจะรู้ว่า สถาปัตยกรรม = วินัย สถาปัตยกรรมเป็นสัญญาระหว่างนักพัฒนาที่กำหนดสิ่งที่ไปที่ไหน จริง ๆ แล้วมันทำให้การพัฒนาคุณลักษณะ ง่ายขึ้น เพราะมันทำให้ codebase คาดการณ์ได้และทำให้การตัดสินใจเหล่านั้นสำหรับคุณ
คุณไม่สามารถเติบโตโครงการได้อย่างยั่งยืนหากนักพัฒนาทุกคนที่ทำงานเกี่ยวกับมันเขียนโค้ดที่สะดวกที่สุด codebase จะกลายเป็นฝันร้ายเพื่อทำงานด้วยและนั่นคือเมื่อคุณจะรู้สึกถึงกระบวนการพัฒนาคุณสมบัติที่ซับซ้อนจริง ในการต่อสู้กับสิ่งนี้ในที่สุดคุณจะวางกฎบางอย่าง กฎเหล่านั้นจะเพิ่มขึ้นตามที่ทีมของคุณเผชิญและแก้ไขปัญหาใหม่ ใส่กฎเหล่านั้นทั้งหมดไว้ในเอกสารและมีคำจำกัดความสถาปัตยกรรมของคุณเอง คุณยังคงใช้สถาปัตยกรรมบางอย่างคุณเพิ่งมาถึงจุดนั้นช้ามากและเจ็บปวด
สถาปัตยกรรมที่สะอาดช่วยให้คุณมีทางลัดและสถาปัตยกรรมที่กำหนดไว้ล่วงหน้าซึ่งได้รับการทดสอบ และใช่แน่นอนคุณต้องเรียนรู้ทั้งหมดนี้ แต่คุณทำครั้งเดียวในชีวิตของคุณแล้วใช้หลักการในภาษาหรือกรอบใด ๆ ที่คุณใช้ในอนาคต
ฉันควรใช้สถาปัตยกรรมที่สะอาดในทุกโครงการของฉันหรือไม่?
เลขที่ . ไม่ใช่ถ้าคุณไม่คาดหวังว่าโครงการจะเติบโตทั้งในจำนวนฟีเจอร์หรือจำนวนผู้ใช้หรือจำนวนนักพัฒนาที่ทำงานอยู่
สถาปัตยกรรมที่คล้ายกันอื่น ๆ ในการทำความสะอาดสถาปัตยกรรมคืออะไร?
ดังที่ได้กล่าวไว้ในโพสต์บล็อกต้นฉบับที่ฉันพูดถึงที่ด้านบนของ readme คุณได้รับ:
- สถาปัตยกรรมหกเหลี่ยม (พอร์ตหรืออะแดปเตอร์อาคา) โดยอลิสแตร์ Cockburn
- สถาปัตยกรรมหัวหอมโดย Jeffrey Palermo
- Screaming Architecture โดย Uncle Bob (คนเดียวกันอยู่เบื้องหลังสถาปัตยกรรมที่สะอาด)
- และอีกสองสาม (ตรวจสอบโพสต์บล็อกต้นฉบับ)