25 ก.ย. 2568·อ่าน 2 นาที

การทดสอบสัญญาสำหรับ API: ป้องกันการเปลี่ยนแปลงที่ทำให้พังในทีมที่เร็ว

การทดสอบสัญญาสำหรับ API ช่วยจับการเปลี่ยนแปลงที่ทำให้พังก่อนที่เว็บและมือถือจะปล่อยงาน ขั้นตอนปฏิบัติจริง ข้อผิดพลาดที่ควรหลีกเลี่ยง และเช็คลิสต์ด่วนก่อนปล่อย

การทดสอบสัญญาสำหรับ API: ป้องกันการเปลี่ยนแปลงที่ทำให้พังในทีมที่เร็ว

ทำไมการเปลี่ยนแปลงที่ทำให้ API พังยังคงเล็ดลอดเข้ามาในการปล่อยงานบ่อย ๆ

ทีมส่วนใหญ่มี API เพียงชุดเดียวที่ให้บริการไคลเอนต์หลายตัว: เว็บ แอป iOS แอป Android และบางครั้งก็มีเครื่องมือภายในด้วย ถึงแม้ทุกคนจะเห็นตรงกันว่าใช้ endpoint เดียวกัน แต่แต่ละไคลเอนต์ก็พึ่งพา API แบบต่างกันเล็กน้อย หน้าจอหนึ่งอาจคาดหวังให้ฟิลด์มีตลอด ในขณะที่อีกหน้าจอใช้ฟิลด์นั้นเฉพาะเมื่อเปิดการกรองเท่านั้น

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

อาการที่เกิดมักไม่ละเอียดอ่อน:\n\n- หน้าจอที่จู่ ๆ กลายเป็นว่างเพราะฟิลด์ถูกเปลี่ยนชื่อหรือย้าย\n- แอปเด้งเพราะค่า null ที่ไม่คาดคิดหรือวัตถุที่หายไป\n- ตั๋วแจ้งปัญหา “มีบางอย่างพัง” ที่ยากจะทำซ้ำตามขั้นตอน\n- การเพิ่มขึ้นของบันทึกข้อผิดพลาดทันทีหลังการ deploy ฝั่งแบ็กเอนด์\n- การออก hotfix ที่เพิ่มโค้ดป้องกันแทนการแก้สาเหตุราก

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

ตัวอย่างทั่วไป: ฝั่งแบ็กเอนด์แทนที่ status: "approved" ด้วย status: { code: "approved" } เพื่อรองรับการแปล เว็บอัปเดตในวันเดียวกันและดูปกติ แต่เวอร์ชัน iOS ปัจจุบันยังคาดหวังสตริง จึงแปลงการตอบกลับไม่ได้และผู้ใช้จะเห็นหน้าเปล่าหลังล็อกอิน

นี่คือเหตุผลที่ต้องมีการทดสอบสัญญาสำหรับ API: ไม่ใช่เพื่อมาแทนที่ QA แต่เพื่อตรวจจับการเปลี่ยนแปลงแบบ “ใช้ได้กับไคลเอนต์ล่าสุดของฉัน” ก่อนที่มันจะถึง production

การทดสอบสัญญาคืออะไร (และไม่ใช่อะไร)

การทดสอบสัญญาคือวิธีที่ผู้บริโภค API (เว็บ แอปมือถือ หรือบริการอื่น) และผู้ให้บริการ API (แบ็กเอนด์ของคุณ) ตกลงกันว่าจะสื่อสารกันอย่างไร ข้อตกลงนั้นคือสัญญา การทดสอบสัญญาตรวจสอบเรื่องง่าย ๆ เรื่องเดียว: ผู้ให้บริการยังคงทำงานตามที่ผู้บริโภคคาดหวังหรือไม่ แม้จะมีการเปลี่ยนแปลงก็ตาม

ในการใช้งานจริง การทดสอบสัญญาสำหรับ API อยู่ระหว่าง unit tests และ end-to-end tests Unit tests รวดเร็วและรันในเครื่อง แต่สามารถพลาดความไม่ตรงกันระหว่างทีมเพราะทดสอบโค้ดภายใน ไม่ใช่ขอบเขตที่แชร์กัน End-to-end tests ทดสอบฟลว์จริงข้ามหลายระบบ แต่ช้ากว่า ยากจะดูแล และมักล้มเหลวด้วยเหตุผลที่ไม่เกี่ยวกับการเปลี่ยนแปลง API (ข้อมูลทดสอบ, เวลา UI, สภาพแวดล้อมที่ไม่เสถียร)

สัญญาไม่ใช่เอกสารยาว มันคือคำอธิบายเน้น ๆ ของคำขอที่ผู้บริโภคจะส่งและการตอบกลับที่ต้องได้รับ การทดสอบสัญญาที่ดีมักครอบคลุม:\n\n- Endpoint และ method (เช่น POST /orders)\n- ฟิลด์ที่จำเป็นและไม่จำเป็น รวมถึงชนิดข้อมูลและกฎพื้นฐาน\n- รหัสสถานะและรูปแบบการตอบข้อผิดพลาด (เช่น 400 เทียบกับ 404 เป็นอย่างไร)\n- Header และความคาดหวังด้านการยืนยันตัวตน (มี token, content type)\n- ค่าเริ่มต้นที่สำคัญและกฎความเข้ากันได้ (ถ้าฟิลด์หายไปจะเกิดอะไรขึ้น)

ตัวอย่างง่าย ๆ ของการแตกหักที่การทดสอบสัญญาจับได้ก่อนคือ: ฝั่งแบ็กเอนด์เปลี่ยนชื่อ total_price เป็น totalPrice Unit tests อาจยังผ่าน End-to-end tests อาจไม่ได้ครอบคลุมหน้าจอนั้นหรืออาจล้มเหลวในแบบที่สับสน การทดสอบสัญญาจะล้มทันทีและชี้ไปที่ความไม่ตรงกันอย่างชัดเจน

ควรชัดเจนว่า testing แบบนี้ไม่ใช่อะไรบ้าง มันไม่มาแทนการทดสอบประสิทธิภาพ ความปลอดภัย หรือการทดสอบเส้นทางผู้ใช้เต็มรูปแบบ นอกจากนี้ก็จับบั๊กเชิงตรรกะทุกอย่างไม่ได้ แต่สิ่งที่มันทำคือ ลดความเสี่ยงปล่อยงานที่พบบ่อยที่สุดในทีมที่เร็ว: การเปลี่ยนแปลง API “เล็ก ๆ” ที่ทำให้ไคลเอนต์พังเงียบ ๆ

ถ้าแบ็กเอนด์ของคุณถูกสร้างหรือเปลี่ยนบ่อย (เช่น regenerating APIs ในแพลตฟอร์มอย่าง AppMaster) การทดสอบสัญญาเป็นตาข่ายความปลอดภัยที่ใช้งานได้จริงเพราะมันยืนยันว่าความคาดหวังของไคลเอนต์ยังคงเป็นจริงหลังการเปลี่ยน

เลือกแนวทางสัญญาสำหรับทีมเว็บและมือถือ

เมื่อเว็บและมือถือปล่อยงานบ่อย ส่วนที่ยากไม่ใช่แค่ “ทดสอบ API” แต่คือการตกลงกันว่าอะไรต้องไม่เปลี่ยนสำหรับแต่ละไคลเอนต์ นั่นคือที่การทดสอบสัญญามาช่วย แต่คุณยังต้องเลือกว่าการเป็นเจ้าของสัญญาจะเป็นอย่างไร

ตัวเลือก 1: Consumer-driven contracts (CDCs)

กับ consumer-driven contracts แต่ละไคลเอนต์ (เว็บ, iOS, Android, พาร์ทเนอร์) จะกำหนดสิ่งที่ต้องการจาก API แล้วผู้ให้บริการก็พิสูจน์ว่าทำตามที่คาดหวังได้

วิธีนี้เหมาะเมื่อไคลเอนต์แต่ละตัวเคลื่อนไหวเป็นอิสระเพราะสัญญาสะท้อนการใช้งานจริง ไม่ใช่สิ่งที่ทีมแบ็กเอนด์คิดว่าน่าจะใช้ นอกจากนี้ยังเข้ากับความเป็นจริงที่มีหลายไคลเอนต์: iOS อาจพึ่งพาฟิลด์ที่เว็บไม่ใช้ และเว็บอาจสนใจการเรียงหรือการแบ่งหน้า (pagination) ที่มือถือไม่สน

ตัวอย่างง่าย ๆ: แอปมือถือพึ่งพา price_cents เป็นจำนวนเต็ม เว็บแค่แสดงราคาที่ฟอร์แมตแล้วจึงอาจไม่สังเกตหากแบ็กเอนด์เปลี่ยนเป็นสตริง CDC จากมือถือจะจับการเปลี่ยนแปลงนั้นก่อนปล่อย

ตัวเลือก 2: สกีมาที่ผู้ให้บริการเป็นเจ้าของ

กับสกีมาที่ผู้ให้บริการเป็นเจ้าของ ทีมแบ็กเอนด์เผยแพร่สัญญาเดียว (มักเป็นสกีมหรือสเปค) และบังคับใช้ ไคลเอนต์ทดสอบกับแหล่งความจริงเดียว

วิธีนี้เหมาะเมื่อ API เป็นสาธารณะหรือแชร์กับไคลเอนต์จำนวนมากที่คุณไม่ควบคุม หรือต้องการความสม่ำเสมออย่างเข้มงวดระหว่างทีม เริ่มต้นก็ง่ายกว่า: สัญญาเดียว ที่เดียวสำหรับการทบทวน เส้นทางการอนุมัติเดียว

วิธีเลือกอย่างรวดเร็ว:\n\n- เลือก CDCs เมื่อไคลเอนต์ปล่อยบ่อยและใช้ส่วนต่าง ๆ ของ API แตกต่างกัน\n- เลือกสกีมาที่ผู้ให้บริการรับผิดชอบเมื่อคุณต้องการสัญญา “ทางการ” ที่เสถียรสำหรับทุกคน\n- ใช้ไฮบริดเมื่อเป็นไปได้: สกีมา provider เป็นฐาน และ CDCs สำหรับ endpoint เสี่ยงสูงไม่กี่จุด

ถ้าคุณกำลังสร้างด้วยแพลตฟอร์มอย่าง AppMaster แนวคิดเดียวกันใช้ได้: ถือว่าเว็บและแอปมือถือเป็นผู้บริโภคแยกต่างหาก แม้จะใช้แบ็กเอนด์ร่วมกัน แต่พวกมันน้อยครั้งที่จะพึ่งพาฟิลด์และกฎเหมือนกันทั้งหมด

ควรใส่อะไรในสัญญา API (เพื่อให้จับการแตกหักได้จริง)

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

เริ่มจากการใช้งานจริง ไม่ใช่การเดา เลือกคำเรียกไคลเอนต์ที่ใช้บ่อยที่สุด (จากโค้ดแอป ของ gateway logs หรือรายการสั้น ๆ จากทีม) แล้วเปลี่ยนเป็นเคสสัญญา: path, method, headers, query params และรูปแบบ request body ที่เกิดขึ้นจริง วิธีนี้ทำให้สัญญาเล็ก มีความเกี่ยวข้อง และโต้แย้งได้ยาก

รวมทั้ง response ทั้งตามปกติและเมื่อเกิดข้อผิดพลาด ทีมมักทดสอบสัญญาเฉพาะเส้นทางที่สำเร็จและลืมว่าผู้บริโภคพึ่งพาข้อผิดพลาดด้วย: รหัสสถานะ รูปแบบข้อผิดพลาด และแม้แต่รหัส/ข้อความข้อผิดพลาดที่คงที่ ถ้าแอปมือถือแสดงข้อความ “อีเมลถูกใช้แล้ว” โดยเฉพาะ สัญญาควรกำหนดรูปแบบ 409 นั้นไว้เพื่อไม่ให้มันกลายเป็น 400 ที่มีโครงต่างกันโดยไม่คาดคิด

ให้ความสำคัญพิเศษกับพื้นที่ที่พังบ่อยที่สุด:\n\n- ฟิลด์ที่เป็นทางเลือกเทียบกับฟิลด์ที่จำเป็น: การลบฟิลด์มักปลอดภัยกว่าการทำให้ฟิลด์ที่เป็นทางเลือกกลายเป็นจำเป็น\n- ค่า null: บางไคลเอนต์จัดการ null ต่างจาก "ไม่มี" ตัดสินใจว่าคุณยอมรับอะไรและรักษาความสอดคล้อง\n- Enum: การเพิ่มค่าใหม่อาจทำให้ไคลเอนต์เก่าพังถ้าคิดว่ารายการปิด\n- Pagination: ตกลงกันที่พารามิเตอร์และฟิลด์การตอบกลับ (เช่น cursor หรือ nextPageToken) และรักษาเสถียรภาพไว้\n- รูปแบบวันที่และตัวเลข: ระบุชัดเจน (สตริง ISO, cents เป็นจำนวนเต็ม ฯลฯ)

จะแสดงสัญญาอย่างไร

เลือกฟอร์แมตที่ทีมอ่านได้และเครื่องมือสามารถตรวจสอบได้ ตัวเลือกทั่วไปคือ JSON Schema, สัญญาแบบยกตัวอย่าง, หรือโมเดลที่มี type สร้างจาก OpenAPI ในปฏิบัติ ตัวอย่างบวกกับการตรวจสอบสกีมาใช้ได้ดี: ตัวอย่างแสดง payload จริง ขณะที่สกีมาจะจับข้อผิดพลาดแบบ “เปลี่ยนชื่อฟิลด์” หรือ “เปลี่ยนชนิดข้อมูล”

กฎง่าย ๆ: ถ้าการเปลี่ยนแปลงจะบังคับให้ไคลเอนต์ต้องอัปเดต ควรทำให้การทดสอบสัญญาล้ม การคิดแบบนี้ทำให้สัญญามุ่งไปที่การจับการแตกหักจริง ไม่ใช่ความสมบูรณ์แบบเชิงทฤษฎี

ขั้นตอนทีละข้อ: เพิ่มการทดสอบสัญญาใน CI ของคุณ

เริ่มเล็ก ขยายอย่างปลอดภัย
เริ่มต้นแบบเล็ก ๆ แล้วขยายอย่างปลอดภัย: โพรโทไทป์ endpoint สำคัญ ระวังสัญญาใน CI เมื่อโตขึ้น
เริ่มโปรเจค

เป้าหมายของการทดสอบสัญญาสำหรับ API ง่าย: เมื่อใครสักคนเปลี่ยน API CI ของคุณควรบอกว่ามีเว็บหรือแอปมือถือใดจะพังก่อนที่การเปลี่ยนจะปล่อย

1) เริ่มจากการจับสิ่งที่ไคลเอนต์พึ่งพาจริง ๆ

เลือก endpoint เดียวและจดความคาดหวังที่สำคัญในการใช้งานจริง: ฟิลด์ที่จำเป็น ชนิดข้อมูล ค่าอนุญาต รหัสสถานะ และการตอบข้อผิดพลาดทั่วไป อย่าพยายามอธิบายทั้ง API ในครั้งเดียว สำหรับแอปมือถือ ให้รวมความคาดหวังของ “เวอร์ชันแอปเก่า” ด้วย เพราะผู้ใช้ไม่ได้อัปเดตทันที

วิธีปฏิบัติได้คือเอาคำขอจริงที่ไคลเอนต์ทำวันนี้ (จาก logs หรือ test fixtures) แล้วเปลี่ยนเป็นตัวอย่างที่รันซ้ำได้

2) เก็บสัญญาในที่ที่ทีมจะดูแลมัน

สัญญาล้มเมื่อมันอยู่ในโฟลเดอร์ที่ถูกลืม เก็บไว้ใกล้กับโค้ดที่เปลี่ยน:\n\n- ถ้าทีมเดียวเป็นเจ้าของทั้งสองด้าน ให้เก็บสัญญาไว้กับ repo ของ API\n- ถ้าทีมเว็บ มือถือ และ API เป็นคนละทีม ให้ใช้ repo ร่วมที่ทีมเป็นเจ้าของ ไม่ใช่คน ๆ เดียว\n- ปรับการอัปเดตสัญญาให้เป็นเหมือนโค้ด: รีวิว เวอร์ชัน และหารือ

3) เพิ่มการตรวจสอบทั้งสองด้านใน CI

คุณต้องการสัญญาณสองทาง:\n\n- การยืนยันผู้ให้บริการในทุกการ build ของ API: “API ยังคงทำตามสัญญาที่รู้จักทั้งหมดหรือไม่?”\n- การตรวจสอบผู้บริโภคในทุกการ build ของไคลเอนต์: “ไคลเอนต์นี้ยังเข้ากันได้กับสัญญาที่เผยแพร่ล่าสุดหรือไม่?”\n\nวิธีนี้จับปัญหาจากทั้งสองด้าน ถ้า API เปลี่ยนฟิลด์การตอบกลับ pipeline ของ API จะล้ม ถ้าไคลเอนต์เริ่มคาดหวังฟิลด์ใหม่ pipeline ของไคลเอนต์ก็จะล้มจนกว่า API จะรองรับ

4) ตัดสินกฎการล้มและบังคับใช้

ระบุชัดเจนว่าอะไรบล็อกการ merge หรือการปล่อย กฎทั่วไปคือ: การเปลี่ยนแปลงที่ทำให้สัญญาพังจะทำให้ CI ล้มและบล็อกการ merge หากต้องการข้อยกเว้น ให้ต้องมีการตัดสินเป็นลายลักษณ์อักษร (เช่น วันที่ปล่อยที่ประสานกัน)

ตัวอย่างชัดเจน: แบ็กเอนด์เปลี่ยนชื่อ totalPrice เป็น total_amount การยืนยันผู้ให้บริการล้มทันที ดังนั้นทีมแบ็กเอนด์จะเพิ่มฟิลด์ใหม่ขณะเก็บฟิลด์เก่าไว้ช่วงระยะหนึ่ง และทั้งเว็บและมือถือยังคงปล่อยงานได้อย่างปลอดภัย

การทำเวอร์ชันและความเข้ากันได้ย้อนหลังโดยไม่ชะลอทีม

ปกป้องการปล่อยแอปมือถือ
ส่งมอบแอป iOS และ Android เนทีฟโดยไม่ต้องเขียนตรรกะซ้ำหลังการอัปเดต API ทุกครั้ง
สร้างแอปมือถือ

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

นี่คือการเปลี่ยนแปลงที่มักทำให้พัง (แม้ endpoint ยังอยู่):\n\n- ลบฟิลด์ใน response ที่ไคลเอนต์อ่าน\n- เปลี่ยนชนิดของฟิลด์ (เช่น "total": "12" เป็น "total": 12)\n- ทำให้ฟิลด์ที่เป็นทางเลือกกลายเป็นจำเป็น (หรือเพิ่มฟิลด์ request ที่ต้องการ)\n- เปลี่ยนกฎการยืนยันตัวตน (endpoint สาธารณะต้องการ token)\n- เปลี่ยนรหัสสถานะหรือรูปแบบข้อผิดพลาดที่ไคลเอนต์แปลง (200 เป็น 204 หรือรูปแบบข้อผิดพลาดใหม่)

ทีมส่วนใหญ่สามารถหลีกเลี่ยงการเพิ่มเวอร์ชันโดยเลือกทางเลือกที่ปลอดภัยกว่า หากต้องการข้อมูลเพิ่ม ให้เพิ่มฟิลด์ใหม่แทนการเปลี่ยนชื่อ หากต้องการ endpoint ใหม่ ให้เพิ่ม route ใหม่และรักษา route เก่าให้ทำงาน หากต้องการบีบการตรวจสอบให้เข้มขึ้น ให้ยอมรับทั้งอินพุตแบบเก่าและแบบใหม่ช่วงหนึ่ง แล้วค่อยบังคับใช้กฎใหม่ทีละน้อย การทดสอบสัญญาช่วยตรงนี้เพราะบังคับให้พิสูจน์ว่าไคลเอนต์ที่มีอยู่ยังคงได้รับสิ่งที่คาดหวัง

การประกาศเลิกใช้ (deprecation) คือส่วนที่ทำให้ความเร็วสูงโดยไม่ทำร้ายผู้ใช้ เว็บอาจอัปเดตทุกวัน แต่มือถืออาจล้าหลังเป็นสัปดาห์เพราะคิวตรวจสอบและการยอมรับช้า วางแผนการเลิกใช้โดยดูพฤติกรรมของไคลเอนต์จริง ไม่ใช่หวังว่าจะอัปเดต

นโยบายการเลิกใช้ที่ใช้งานได้จริงเป็นแบบนี้:\n\n- ประกาศการเปลี่ยนแปลงล่วงหน้า (release notes, ช่องทางภายใน, ตั๋ว)\n- รักษาพฤติกรรมเดิมจนกว่าการใช้งานจะลดลงต่ำกว่าเกณฑ์ที่ตกลงกันไว้\n- ส่งคำเตือนใน header/logs เมื่อใช้ path ที่เลิกใช้\n- ตั้งวันที่ลบหลังจากยืนยันว่าไคลเอนต์ส่วนใหญ่อัปเกรดแล้ว\n- ลบพฤติกรรมเก่าหลังจากการทดสอบสัญญายืนยันว่าไม่มีผู้บริโภคที่ยังสนับสนุนมัน

ใช้การทำเวอร์ชันอย่างชัดเจนเฉพาะเมื่อคุณไม่สามารถทำให้การเปลี่ยนแปลงเข้ากันได้ย้อนหลังได้ (เช่น การเปลี่ยนทรัพยากรหรือโมเดลความปลอดภัยครั้งใหญ่) การสร้างเวอร์ชันเพิ่มภาระระยะยาว: ต้องดูแลสองพฤติกรรม สองชุดเอกสาร และขอบเคสเพิ่มขึ้น เก็บเวอร์ชันให้หายากและตั้งใจ ใช้สัญญาเพื่อให้แน่ใจว่าทั้งสองเวอร์ชันยังตรงตามสัญญาจนกว่าเวอร์ชันเก่าปลอดภัยที่จะลบ

ข้อผิดพลาดทั่วไปในการทดสอบสัญญา (และวิธีหลีกเลี่ยง)

การทดสอบสัญญาทำงานได้ดีเมื่อมันตรวจสอบความคาดหวังจริง ไม่ใช่เวอร์ชันทดสอบเล็ก ๆ ของระบบของคุณ ความล้มเหลวส่วนใหญ่เกิดจากรูปแบบที่คาดได้ไม่กี่แบบซึ่งทำให้ทีมรู้สึกปลอดภัย แต่บั๊กยังเล็ดลอดเข้ามาใน production

ข้อผิดพลาด 1: ถือว่าสัญญาเป็น "ม็อกหรู"

การม็อกเกินไปคือกับดักคลาสสิก: การทดสอบสัญญาผ่านเพราะพฤติกรรมของผู้ให้บริการถูกม็อกให้ตรงกับสัญญา ไม่ใช่เพราะบริการจริงทำได้ เมื่อ deploy ครั้งแรก การเรียกใช้งานจริงล้ม

กฎที่ปลอดภัยกว่า: ควรยืนยันสัญญากับผู้ให้บริการที่รันอยู่จริง (หรือ artefact ของบิลด์ที่ทำงานเหมือนกัน) โดยใช้การซีเรียไรซ์จริง การตรวจสอบจริง และกฎการยืนยันตัวตนจริง

นี่คือความผิดพลาดที่มักพบและการแก้ที่ได้ผล:\n\n- Over-mocking พฤติกรรมผู้ให้บริการ: ยืนยันสัญญากับบิลด์ผู้ให้บริการจริง ไม่ใช่บริการสตับ\n- ทำสัญญาเข้มงวดเกินไป: ใช้การจับแบบยืดหยุ่นสำหรับ ID, timestamp และอาร์เรย์; หลีกเลี่ยงการอ้างทุกฟิลด์ถ้าไคลเอนต์ไม่พึ่งพา\n- เมินเฉยต่อการตอบข้อผิดพลาด: ทดสอบสัญญาอย่างน้อยเคสข้อผิดพลาดหลัก (401, 403, 404, 409, 422, 500) และรูปแบบบอดี้ที่ไคลเอนต์แปลง\n- ไม่มีการเป็นเจ้าของที่ชัดเจน: กำหนดว่าใครอัปเดตสัญญาเมื่อข้อกำหนดเปลี่ยน; ทำให้เป็นส่วนหนึ่งของ "definition of done" สำหรับการเปลี่ยนแปลง API\n- ลืมความจริงของมือถือ: ทดสอบด้วยเครือข่ายช้ากว่าและเวอร์ชันแอปเก่าที่อาจยังใช้งาน ไม่ใช่แค่เวอร์ชันล่าสุดบน Wi‑Fi เร็ว ๆ

ข้อผิดพลาด 2: สัญญาที่เปราะบางบล็อกการเปลี่ยนแปลงที่ไม่เป็นอันตราย

ถ้าสัญญาล้มเมื่อคุณเพิ่มฟิลด์ทางเลือกใหม่หรือสลับลำดับคีย์ JSON นักพัฒนาจะเรียนรู้ที่จะไม่สนใจกับ build สีแดง นั่นทำลายวัตถุประสงค์

ตั้งเป้าให้ “เข้มงวดในจุดที่สำคัญ” เข้มงวดเรื่องฟิลด์ที่จำเป็น ชนิดข้อมูล ค่า enum และกฎการตรวจสอบ ยืดหยุ่นเรื่องฟิลด์เพิ่ม ลำดับ และค่าที่เปลี่ยนตามธรรมชาติ

ตัวอย่างเล็ก ๆ: แบ็กเอนด์เปลี่ยน status จาก "active" | "paused" เป็น "active" | "paused" | "trial" ถ้าแอปมือถือจัดการค่าไม่รู้จักด้วยการเด้ง นี่คือการเปลี่ยนแปลงที่ทำให้พัง สัญญาควรจับโดยตรวจสอบว่าไคลเอนต์จัดการค่า enum ใหม่อย่างไร หรือโดยการบังคับให้ผู้ให้บริการยังคงส่งเฉพาะค่าที่รู้จักจนกว่าไคลเอนต์ทั้งหมดจะรองรับค่าใหม่

ไคลเอนต์มือถือควรได้รับความใส่ใจเป็นพิเศษเพราะมันอยู่ในป่าได้นาน ก่อนจะบอกว่าการเปลี่ยนแปลง API "ปลอดภัย" ให้ถาม:\n\n- เวอร์ชันแอปเก่ายังแปลงการตอบกลับได้หรือไม่?\n- จะเกิดอะไรขึ้นถ้าคำขอถูกลองใหม่หลังจาก timeout?\n- ข้อมูลที่แคชจะชนกับฟอร์แมตใหม่หรือไม่?\n- มี fallback เมื่อฟิลด์หายหรือไม่?\n ถ้า API ของคุณถูกสร้างหรืออัปเดตเร็ว (รวมถึงแพลตฟอร์มอย่าง AppMaster) สัญญาจะเป็นราวป้องกัน: ให้คุณเคลื่อนไหวเร็วขณะพิสูจน์ว่าเว็บและมือถือจะยังทำงานหลังการเปลี่ยนแต่ละครั้ง

เช็คลิสต์ด่วนก่อนปล่อยสำหรับการเปลี่ยนแปลง API

ปล่อยการเปลี่ยนแปลงอย่างมั่นใจ
ออกแบบข้อมูล สร้างโค้ด และทำให้การปล่อยงานยังเดินหน้าได้เมื่อความต้องการเปลี่ยน
ลอง AppMaster

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

5 คำถามที่ต้องถามทุกครั้ง

  • เราเพิ่ม ลบ หรือเปลี่ยนชื่อฟิลด์ในการตอบกลับที่ไคลเอนต์อ่านหรือไม่ (รวมฟิลด์ซ้อน)?\n- รหัสสถานะมีการเปลี่ยนไหม (200 เทียบกับ 201, 400 เทียบกับ 422, 404 เทียบกับ 410) หรือรูปแบบบอดี้ข้อผิดพลาดเปลี่ยนหรือไม่?\n- ฟิลด์ใดเปลี่ยนจากเป็นทางเลือกเป็นจำเป็นหรือกลับกัน (รวม “เป็น null ได้” กับ “ต้องมีอยู่”)?\n- การเรียง การแบ่งหน้า หรือค่าเริ่มต้นการกรองเปลี่ยนหรือไม่ (ขนาดหน้า การเรียง รหัส cursor ค่าเริ่มต้น)?\n- การทดสอบสัญญารันสำหรับผู้ให้บริการและผู้บริโภคที่ยังใช้งานอยู่ทั้งหมด (เว็บ, iOS, Android, และเครื่องมือภายในใด ๆ) หรือไม่?

ตัวอย่างง่าย ๆ: API เคยส่ง totalCount และไคลเอนต์ใช้มันแสดง “24 ผลลัพธ์” คุณลบเพราะ “รายชื่อมีไอเท็มแล้ว” ไม่มีอะไรเด้งในแบ็กเอนด์ แต่ UI เริ่มแสดงว่างหรือ “0 ผลลัพธ์” นั่นคือการแตกหักจริง แม้ endpoint จะยังคืน 200

ถ้าตอบ "ใช่" ในข้อใดข้อหนึ่ง

ทำตามขั้นตอนด่วนเหล่านี้ก่อนปล่อย:\n\n- ยืนยันว่าไคลเอนต์เก่ายังทำงานได้โดยไม่ต้องอัปเดตหรือไม่ ถ้าไม่ ให้เพิ่มทางสายที่เข้ากันได้ย้อนหลัง (เก็บฟิลด์เก่า หรือรองรับทั้งสองฟอร์แมตช่วงหนึ่ง)\n- ตรวจสอบการจัดการข้อผิดพลาดในไคลเอนต์ แอปหลายตัวจัดการรูปแบบข้อผิดพลาดที่ไม่รู้จักเป็น "มีบางอย่างผิดพลาด" และซ่อนข้อความที่มีประโยชน์\n- รันการทดสอบสัญญาของผู้บริโภคสำหรับทุกเวอร์ชันไคลเอนต์ที่ยังรองรับ ไม่ใช่แค่ branch ล่าสุด

ถ้าคุณสร้างเครื่องมือภายในอย่างรวดเร็ว (เช่น แผงแอดมินหรือแดชบอร์ดสนับสนุน) ให้แน่ใจว่าไคลเอนต์เหล่านั้นรวมอยู่ด้วย ใน AppMaster ทีมมักสร้างเว็บและแอปมือถือจากโมเดลแบ็กเอนด์เดียวกัน ซึ่งทำให้ลืมได้ง่ายว่าการแก้สคีมาเล็ก ๆ ยังสามารถทำให้ไคลเอนต์ที่ปล่อยแล้วพังได้ถ้าสัญญาไม่ได้ตรวจสอบใน CI

ตัวอย่าง: จับการเปลี่ยนแปลงที่ทำให้พังก่อนเว็บและมือถือปล่อย

ทำให้การเปลี่ยนสกีมาปลอดภัยขึ้น
ออกแบบโมเดล PostgreSQL แบบภาพและสร้างโค้ด backend ใหม่เมื่อมีการเปลี่ยนแปลงฟิลด์
สร้าง Backend

จินตนาการถึงการตั้งค่าทั่วไป: ทีม API deploy หลายครั้งต่อวัน เว็บปล่อยทุกวัน และแอปมือถือปล่อยเป็นรายสัปดาห์ (เพราะการตรวจสอบสโตร์และการเปิดตัวแบบเป็นขั้นบันได) ทุกคนเคลื่อนไหวเร็ว ความเสี่ยงจริง ๆ คือการเปลี่ยนแปลงเล็ก ๆ ที่ดูปลอดภัย

ตั๋วสนับสนุนขอชื่อที่ชัดเจนขึ้นใน response ของ user profile ทีม API เปลี่ยนชื่อฟิลด์ใน GET /users/{id} จาก phone เป็น mobileNumber

การเปลี่ยนชื่อนั้นดูสะอาด แต่เป็นการเปลี่ยนที่ทำให้พัง เว็บอาจแสดงหมายเลขโทรศัพท์ว่างในหน้าประวัติผู้ใช้ แย่กว่านั้น มือถืออาจเด้งหากถือว่า phone เป็นจำเป็น หรืออาจล้มการตรวจสอบเมื่อบันทึกโปรไฟล์

ด้วยการทดสอบสัญญา เรื่องนี้จะถูกจับก่อนถึงผู้ใช้ นี่คือวิธีที่มักล้ม ขึ้นอยู่กับวิธีที่คุณรันการตรวจสอบ:\n\n- บิลด์ของผู้ให้บริการล้ม (ฝั่ง API): งาน CI ของ API ยืนยันผู้ให้บริการกับสัญญาผู้บริโภคที่บันทึกไว้จากเว็บและมือถือ พบว่าผู้บริโภคยังคงคาดหวัง phone แต่ผู้ให้บริการส่ง mobileNumber ดังนั้นการยืนยันล้มและบล็อกการ deploy\n- บิลด์ของผู้บริโภคล้ม (ฝั่งไคลเอนต์): ทีมเว็บอัปเดตสัญญาของพวกเขาให้ต้องการ mobileNumber ก่อนที่ API จะปล่อย การทดสอบสัญญาของพวกเขาล้มเพราะผู้ให้บริการยังไม่ส่งฟิลด์นั้น

ไม่ว่าจะทางไหน ความล้มเป็นเรื่องที่ดังและเจาะจง: ชี้ไปที่ endpoint และฟิลด์ที่ไม่ตรง แทนที่จะเจอเป็น “หน้าโปรไฟล์พัง” หลังการปล่อย

การแก้มักง่าย: ทำให้การเปลี่ยนแปลงเป็นแบบเพิ่ม (additive) ไม่ใช่ทำลาย (destructive) API คืนทั้งสองฟิลด์ช่วงหนึ่ง:\n\n- เพิ่ม mobileNumber\n- เก็บ phone เป็น alias (ค่าตรงกัน)\n- ทำเครื่องหมาย phone ว่า deprecated ในบันทึกสัญญา\n- อัปเดตเว็บและมือถือให้อ่าน mobileNumber\n- ลบ phone เมื่อเห็นว่าไคลเอนต์ที่รองรับย้ายหมดแล้ว

ไทม์ไลน์ที่สมจริงภายใต้ความกดดันการปล่อยอาจเป็นแบบนี้:\n\n- จันทร์ 10:00: ทีม API เพิ่ม mobileNumber และเก็บ phone ไว้ การทดสอบสัญญาของผู้ให้บริการผ่าน\n- จันทร์ 16:00: เว็บเปลี่ยนไปใช้ mobileNumber และปล่อย\n- พฤหัสบดี: มือถือเปลี่ยนไปใช้ mobileNumber และส่ง release\n- อังคารถัดไป: การปล่อยมือถือถึงผู้ใช้ส่วนใหญ่\n- สปรินต์ถัดไป: API ลบ phone และการทดสอบสัญญายืนยันว่าไม่มีผู้บริโภคที่รองรับต้องการมันอีก

นี่คือคุณค่าหลัก: การทดสอบสัญญาเปลี่ยน “รูเล็ตการเปลี่ยนแปลงที่ทำให้พัง” ให้เป็นการย้ายแบบควบคุมและมีเวลาจัดการ

ขั้นตอนถัดไปสำหรับทีมที่เคลื่อนไหวเร็ว (รวมตัวเลือกแบบ no-code)

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

เริ่มด้วยแผนการเปิดตัวแบบน้ำหนักเบา เลือก 3 endpoint อันดับต้น ๆ ที่สร้างปัญหาเมื่อเปลี่ยน ส่วนใหญ่คือ auth, user profile และ endpoint หลักที่ใช้สำหรับ list หรือ search เอา 3 จุดแรกมาทำสัญญาก่อน แล้วขยายเมื่อทีมเริ่มเชื่อมั่นใน workflow

แผนการเปิดตัวที่จัดการได้จริง:\n\n- สัปดาห์ที่ 1: การทดสอบสัญญาสำหรับ 3 endpoint สำคัญ รันในทุก pull request\n- สัปดาห์ที่ 2: เพิ่มอีก 5 endpoint ที่มีการใช้งานมือถือมากที่สุด\n- สัปดาห์ที่ 3: ครอบคลุมการตอบข้อผิดพลาดและเคสขอบ (สภาวะว่าง, ข้อผิดพลาดการตรวจสอบ)\n- สัปดาห์ที่ 4: ทำให้ “contract green” เป็นเกตการปล่อยสำหรับการเปลี่ยนแปลง backend

ต่อมา แบ่งหน้าที่ให้ชัดเจน ทีมจะเคลื่อนไหวเร็วขึ้นเมื่อรู้แน่ชัดว่าใครรับผิดชอบล้มและใครอนุมัติการเปลี่ยนแปลง

รักษาบทบาทให้เรียบง่าย:\n\n- เจ้าของสัญญา: มักเป็นทีม backend รับผิดชอบการอัปเดตสัญญาเมื่อพฤติกรรมเปลี่ยน\n- ผู้ตรวจสอบผู้บริโภค: ผู้นำเว็บและมือถือที่ยืนยันการเปลี่ยนแปลงปลอดภัยสำหรับไคลเอนต์ของตน\n- Build sheriff: หมุนเวียนรายวันหรือรายสัปดาห์ คัดแยกและจัดลำดับความสำคัญของการล้มในการ CI\n- เจ้าของการปล่อย: ตัดสินใจบล็อกการปล่อยถ้าสัญญาพัง

ติดตามเมตริกสำเร็จเดียวที่ทุกคนสนใจ สำหรับหลายทีม สัญญาณที่ดีที่สุดคือการลด hotfix หลังการปล่อยและการลด "regressions ของไคลเอนต์" เช่น แอปเด้ง หน้าจอว่าง หรือการชำระเงินพังที่ผูกกับการเปลี่ยนแปลง API

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

ถ้าคุณสร้าง API และไคลเอนต์ด้วย AppMaster ขั้นตอนต่อไปที่ใช้งานได้จริงคือทดลองโดยสร้างแอป โมเดลข้อมูลของคุณใน Data Designer (PostgreSQL) ปรับ workflow ใน Business Process Editor แล้วสร้างและปรับใช้ไปยังคลาวด์ของคุณ (หรือส่งออกซอร์สโค้ด) จับคู่กับการตรวจสอบสัญญาใน CI เพื่อให้ทุกบิลด์ที่สร้างใหม่ยังพิสูจน์ได้ว่าตรงตามที่เว็บและมือถือคาดหวัง

ง่ายต่อการเริ่มต้น
สร้างบางสิ่งที่ น่าทึ่ง

ทดลองกับ AppMaster ด้วยแผนฟรี
เมื่อคุณพร้อม คุณสามารถเลือกการสมัครที่เหมาะสมได้

เริ่ม