

เวลามีหน้าเว็บกับ API อยู่ในทีมเดียวกัน การเปลี่ยนข้อมูลสักช่องอาจดูเป็นงานที่ประสานกันได้ง่าย ฝั่งหนึ่งเพิ่มค่า อีกฝั่งแก้หน้าแสดงผล แล้วก็ทดสอบก่อนเอาขึ้นพร้อมกัน แต่ภาพนี้จะเปลี่ยนไปเมื่อมีแอปมือถือ ระบบของคู่ค้า หรือ script ที่คนอื่นเขียนเรียก API ชุดเดียวกันอยู่
คนเรียกแต่ละกลุ่มอัปเดตไม่พร้อมกัน บางแอปยังอยู่บนเครื่องที่ไม่ได้เปิดมาหลายวัน ส่วน script บางตัวอาจทำงานตามรอบโดยไม่มีคนคอยเปิดดูทุกครั้ง แม้ server จะเปลี่ยนเป็นรุ่นล่าสุดแล้ว ความคาดหวังของผู้เรียกก็ยังเป็นแบบเดิมได้อีกนาน
ก่อนแก้ API จึงต้องดูให้ชัดว่าข้อตกลงเดิมมีอะไรบ้าง และสิ่งที่เราคิดว่าเพิ่มเล็กน้อยไปแตะสมมติฐานข้อใดของผู้เรียกอยู่ การดูแค่ชื่อ endpoint กับโครง JSON อาจยังไม่พอครับ
สมมติว่า API ส่งน้ำหนักออกมาเป็นตัวเลข โดยเอกสารเดิมระบุหน่วยเป็นกรัม หากเปลี่ยนไปส่งกิโลกรัมแต่ยังใช้ชื่อ field และชนิดตัวเลขเหมือนเดิม ตัวตรวจรูปแบบอาจไม่พบอะไรผิด แต่หน้าจอหรือการคำนวณของผู้เรียกกลับใช้ความหมายเก่าต่อไปได้
ลักษณะเดียวกันเกิดกับค่าเริ่มต้น ลำดับการเรียงข้อมูล หรือความหมายของสถานะ เช่น เดิมสถานะสำเร็จหมายถึงบันทึกเข้าฐานแล้ว ต่อมาปรับให้หมายถึงเพียงรับเข้าคิว คนที่ใช้ค่านั้นเป็นจังหวะส่งงานถัดไปก็จะตัดสินใจจากข้อมูลคนละความหมาย ทั้งที่คำตอบยังเป็นข้อความเดิม
ตอนทบทวนสัญญาจึงควรเก็บทั้งชนิดข้อมูล หน่วย ค่าที่ว่างได้ เงื่อนไขที่ไม่ส่ง field นั้น และผลข้างเคียงที่เกิดก่อนตอบกลับ สิ่งเหล่านี้ช่วยให้เห็นว่าการเปลี่ยนครั้งหนึ่งเป็นเพียงข้อมูลเพิ่ม หรือเป็นการเปลี่ยนพฤติกรรมที่คนเรียกพึ่งพาอยู่
คำขอขาเข้าก็อยู่ในสัญญาชุดนี้ด้วย หากข้อมูลที่เคยไม่ส่งได้กลายเป็นสิ่งบังคับ ผู้เรียกเก่าจะสร้างคำขอที่เคยถูกต้องแล้วถูกปฏิเสธทันที การเทียบเฉพาะหน้าตาของ response จึงมองไม่เห็นผลกระทบด้านนี้

โดยทั่วไปการเพิ่ม field ใน response สามารถออกแบบให้ผู้เรียกเดิมใช้งานต่อได้ หากตัวอ่านยอมข้ามข้อมูลที่ยังไม่รู้จัก แต่ถ้าฝั่ง client ตรวจว่าต้องมี key เท่าที่กำหนดไว้เท่านั้น การเพิ่มข้อมูลก็อาจทำให้มันปฏิเสธคำตอบทั้งก้อนได้ จึงต้องดูพฤติกรรมของตัวอ่านจริง ไม่สรุปจากฝั่ง server เพียงด้านเดียว
ค่า enum ก็มีประเด็นคล้ายกัน บางบริการระบุไว้ในนโยบายว่าการเพิ่มค่าที่เป็นไปได้ถือเป็นการเปลี่ยนแบบเพิ่มข้อมูล ผู้เรียกจึงต้องเตรียมกรณีที่ยังไม่รู้จัก เช่น แสดงสถานะทั่วไปและไม่อนุมานว่าค่าใหม่หมายถึงสำเร็จ หากเขียนเงื่อนไขครอบเฉพาะค่าที่รู้จักโดยไม่มีทางรองรับ ก็อาจหยุดทำงานเมื่อบริการขยายสถานะในภายหลัง
ตรงนี้ไม่ได้แปลว่าผู้ให้บริการเพิ่มหรือเปลี่ยนอะไรก็ได้ กติกาต้องตกลงกันก่อน รวมถึงแยกการเพิ่มค่าออกจากการเปลี่ยนความหมายของค่าเดิม ฝั่ง client เองก็ควรแยกการแสดงผลที่ยังไม่รู้จักออกจากการอนุญาตให้ทำงานสำคัญ ไม่ใช้ค่าเผื่อทั่วไปเป็นเหตุให้ข้ามการตรวจสิทธิ์หรือยืนยันผลผิด
เมื่อจำเป็นต้องเปลี่ยนสัญญาที่รองรับของเดิมไม่ได้ เราอาจแยก version ผ่าน path หรือ header ตามการออกแบบของบริการ ตัวอย่างอย่าง GitHub REST API ใช้ header เพื่อเลือก version ซึ่งทำให้เห็นว่ารุ่นของสัญญาที่ผู้เรียกขอใช้เป็นเรื่องที่ต้องระบุได้ ไม่ได้อ้างอิงจากวันที่ deploy server อย่างเดียว
อย่างไรก็ตาม การใส่คำว่า v2 ใน URL ยังไม่ตอบว่าของเดิมอยู่ได้นานเพียงใด มีข้อมูลส่วนไหนใช้ร่วมกัน และข้อผิดพลาดเปลี่ยนอย่างไร หากทั้งสองรุ่นไปเรียกโค้ดก้อนเดียวกันที่เปลี่ยนพฤติกรรมทันที ป้าย version ก็อาจไม่ได้แยกข้อตกลงจริงตามที่คนอ่านเอกสารเข้าใจ
ควรกำหนดขอบเขตที่ต้องรักษาและข้อยกเว้นไว้ด้วย โดยเฉพาะการแก้ช่องโหว่หรือปัญหาความปลอดภัย การรองรับรุ่นเดิมไม่ได้หมายถึงต้องเก็บพฤติกรรมที่เปิดความเสี่ยงไว้ตลอดไป แต่ต้องมีวิธีแจ้งและจัดการผลกระทบตามระดับของการเปลี่ยนแปลง
OpenAPI ช่วยบันทึกรูปแบบ request และ response รวมถึงเงื่อนไขหลายอย่างให้อยู่ในรูปที่เครื่องมืออ่านต่อได้ เราจึงนำฉบับก่อนกับหลังมาเทียบเพื่อหาจุดที่ต้องทบทวน เช่น field ที่เคยเลือกส่งได้กลับกลายเป็นบังคับ หรือคำตอบเปลี่ยนชนิดข้อมูล
แต่ค่า info.version ในเอกสารเป็นข้อมูลอธิบายตัวเอกสาร ไม่ได้ทำให้ server เลือกเส้นทางตามรุ่นหรือตรวจ response ให้เอง การมี schema จึงต้องเชื่อมกับวิธีตรวจและการทำงานจริง ไม่ถือว่ามีไฟล์อยู่ใน repository แล้วเท่ากับมีการบังคับสัญญาครบทุกจุด
ส่วนความหมายที่ schema บอกไม่ครบก็ควรมีตัวอย่างหรือเงื่อนไขตรวจเพิ่ม เช่น หน่วยของตัวเลขและจังหวะที่งานถือว่าสำเร็จ ตัวอย่างเหล่านี้ใช้ข้อมูลสมมติที่ไม่มีข้อมูลลูกค้า แล้วเก็บให้เห็นว่าต้องได้ความหมายอะไรจากคำตอบ ไม่ตรวจเพียงว่าแปลง JSON ผ่าน

ก่อนปล่อยการเปลี่ยนแปลง ควรนำตัวอ่านหรือชุดข้อมูลทดสอบของผู้เรียกรุ่นที่ยังรองรับมาลองด้วย ทั้งกรณีได้ field เพิ่ม ค่าใหม่ ค่าที่เป็น null และ field ที่ไม่ส่งตามเงื่อนไข การตรวจเฉพาะ client ล่าสุดที่แก้พร้อม server มักไม่เจอข้อสมมติที่รุ่นเก่ายังมีอยู่
เมื่อวางแผนเลิกรุ่นเดิม ต้องมีเจ้าของและช่องทางติดตามการย้าย หากจะอาศัยข้อมูลการเรียกใช้งานก็ควรตรวจว่าระบบแยกรุ่นได้จริง และช่วงเวลาที่เก็บครอบคลุมงานที่ไม่ได้เรียกทุกวัน บทนี้ยังไม่มีข้อมูลการใช้ API จริง จึงไม่อ้างว่ารุ่นใดไม่มีผู้ใช้เหลือแล้ว
ความเข้ากันได้ของ API อยู่ที่สิ่งที่ผู้เรียกยังตีความและใช้งานต่อได้ การเก็บสัญญาให้ชัด เตรียมทางรองรับข้อมูลใหม่ และนำผู้เรียกรุ่นเก่ามาตรวจร่วมกัน จะช่วยให้การพัฒนาฝั่ง server ไม่ตัดขาดจากคนที่อัปเดตช้ากว่า ส่วนการแยก version และเลิกรองรับควรตามมาจากขอบเขตการเปลี่ยนจริง พร้อมแผนที่ตรวจติดตามได้