ทีม Frontend ได้นำเลเยอร์การจำลอง API (API-mocking layer) แบบ type-safe มาใช้งาน ซึ่งจะทำงานเฉพาะในสภาพแวดล้อมการพัฒนา (development) เท่านั้น โดยใช้ Axios interceptors และการทำ tree-shaking ของ Vite ทำให้ bundle สำหรับ production ไม่ได้รับผลกระทบ วิศวกรสามารถดึงข้อมูลด้วยรูปแบบปกติในขณะที่รอ endpoint จาก backend จากนั้นเพียงแค่สลับ flag ของ environment เพียงจุดเดียวเพื่อเชื่อมต่อกับ API จริง

ทำไมทีมถึงต้องการวิธีจำลอง (mock) ที่ดีกว่าเดิม

นักพัฒนา Frontend มักจะเจอกับทางตันเมื่อ route ของ backend ยังไม่เสร็จสมบูรณ์ วิธีแก้ปัญหาเฉพาะหน้าอย่างการ hard-code response ไว้ใน component หรือการใส่บล็อก if (process.env.NODE_ENV === 'development') กระจัดกระจายไปทั่ว UI แม้จะช่วยให้แอปทำงานต่อไปได้ แต่ก็เป็นการสร้างหนี้ทางเทคนิค (technical debt) วัตถุจำลอง (mock objects) เหล่านั้นจะกลายเป็นส่วนหนึ่งของ logic ใน component ซึ่งเพิ่มความเสี่ยงที่จะส่งข้อมูลปลอมไปยัง production และทำให้โค้ดอ่านยากและทดสอบได้ลำบากขึ้น

ทีมต้องการย้าย mock ทุกอย่างออกจาก component tree บังคับใช้สัญญา (contract) ระหว่าง frontend และ backend และรับประกันว่าจะไม่มีโค้ดส่วนเกินหลุดเข้าไปใน production build

กระบวนการ 3 ขั้นตอนที่ทีมปฏิบัติตาม

  1. การประชุมเพื่อทำสัญญา (Contract meeting) – วิศวกร frontend และ backend มานั่งคุยกันเพื่อลิสต์รายการ request แต่ละรายการ รวมถึง URL, method และ payload ที่คาดหวัง
  2. สัญญาแบบระบุประเภท (Typed contract) – พวกเขาเปลี่ยนรายการเหล่านั้นให้เป็น TypeScript interface ซึ่งจะกลายเป็นแหล่งข้อมูลความจริงหนึ่งเดียว (single source of truth) สำหรับโครงสร้างของ request และ response
  3. การเชื่อมต่อ Interceptor (Interceptor wiring) – Axios interceptor จะตรวจสอบทุก outgoing request หาก URL ตรงกับ mock ที่ลงทะเบียนไว้ interceptor จะส่งคืนข้อมูล mock แทน มิฉะนั้น request จะถูกส่งต่อไปยัง server จริง

เนื่องจาก interceptor เป็นที่เดียวที่มี logic ของ mock อยู่ โค้ดใน component จึงไม่ต้องเปลี่ยนแปลง นักพัฒนาสามารถใช้ data-fetching hooks ปกติ เช่น useQuery ได้โดยไม่ต้องเพิ่ม logic เงื่อนไขใดๆ

วิธีหลีกเลี่ยงการบวมของ production (production bloat)

ทีมได้วางมาตรการป้องกัน 3 ชั้นที่ช่วยให้ Rollup (bundler ที่ Vite ใช้) สามารถตัดโค้ด mock ออกไปได้อย่างสิ้นเชิงเมื่อทำการ build สำหรับ production:

  • import.meta.env.DEV จะมีค่าเป็น false ใน production build ดังนั้นโมดูล interceptor ทั้งหมดจะหายไปในระหว่างขั้นตอน tree-shaking
  • ตัวแปร MODE จะถูกตั้งค่าเป็นอย่างอื่นที่ไม่ใช่ test เมื่อรัน unit tests เพื่อแยกโค้ดที่ใช้เฉพาะการทดสอบออกจากกัน
  • flag พิเศษ VITE_ENABLE_MSW จะมีค่าเริ่มต้นเป็น false และต้องเปิดใช้งานอย่างชัดเจนเพื่อเริ่มการทำงานของ mock

เมื่อเงื่อนไขทั้งสามเป็น false ตัว mock registry จะไม่หลุดเข้าไปใน bundle สุดท้าย

การจัดระเบียบไฟล์ mock

repository นี้ใช้โครงสร้างแบบเน้นฟีเจอร์ (feature-centric layout):

  • interfaces/ – เก็บ TypeScript definitions ที่สร้างขึ้นจากการประชุมเพื่อทำสัญญา
  • scenarios.ts – ประกอบด้วยตัวอย่างที่เป็นรูปธรรมของ response ที่สำเร็จและกรณีที่เกิด error สำหรับแต่ละ endpoint
  • devHandlers.ts – ทำหน้าที่เป็น registry กลางที่จับคู่ URL เข้ากับข้อมูล scenario และเชื่อมต่อ interceptor เข้ากับ Axios

สคริปต์ scaffolding ขนาดเล็กสามารถสร้างไฟล์เหล่านี้ได้โดยอัตโนมัติ เพียงแค่ระบุ URL และ interface ที่ตรงกัน สคริปต์จะสร้างไฟล์ stub และลงทะเบียน mock ให้ทันที สคริปต์นี้อยู่นอกเส้นทางการทำงานของ production ดังนั้นจึงไม่ส่งผลกระทบต่อขนาดของ bundle

สิ่งที่ทีมได้รับ

  • ไม่มี mock อยู่ภายใน component – ข้อมูลปลอมทั้งหมดอยู่ในเลเยอร์เฉพาะ ทำให้โค้ด UI สะอาด
  • ความปลอดภัยด้านประเภทข้อมูลแบบ end-to-end – ข้อมูล mock จะเป็นไปตาม TypeScript interfaces เดียวกันกับที่ใช้ใน response จริง ดังนั้นความไม่สอดคล้องกันจะถูกตรวจพบตั้งแต่ขั้นตอน compile time
  • ไม่มีน้ำหนักส่วนเกินใน production – Tree-shaking จะลบ interceptor และข้อมูล mock ออกไปทั้งหมด ทำให้ขนาดของ bundle ไม่เปลี่ยนแปลง
  • ใช้ scenario ร่วมกันทั้งการ dev และการ test – การนิยาม mock ชุดเดียวกันสามารถใช้ได้ทั้งการพัฒนาในเครื่องและการทดสอบอัตโนมัติ ช่วยลดความซ้ำซ้อน

ข้อแลกเปลี่ยนและข้อจำกัด

วิธีนี้ไม่ได้มาแทนที่ backend จริง หากสัญญาของ mock ไม่ตรงกับ API จริง นักพัฒนาจะพบความไม่สอดคล้องกันก็ต่อเมื่อพวกเขาได้สลับ environment flag ไปแล้วเท่านั้น

สิ่งที่ควรจับตามองต่อไป

  • การรวมเข้ากับเครื่องมือต่างๆ (Tooling integration)
  • การนำไปใช้งานในวงกว้างขึ้น (Broader adoption)
  • การตรวจสอบประสิทธิภาพ (Performance monitoring)

บทสรุปนั้นชัดเจน: การย้าย logic ของ mock ไปไว้ในเลเยอร์ที่มีการระบุประเภท (typed) และมีการควบคุมด้วย environment ช่วยให้ทีม frontend รักษาความสะอาดของ component, คงความปลอดภัยด้าน type และส่งมอบ production builds ได้โดยไม่มี payload ของ mock ซ่อนอยู่ การรักษาสัญญา (contract) ร่วมกันคือราคาที่ต้องจ่ายเพื่อให้ได้ workflow การพัฒนาที่ราบรื่นขึ้นและ codebase ที่สะอาดขึ้น