คุณจัดการเวอร์ชันของเว็บ API สาธารณะกันอย่างไร?
(lobste.rs)- หากเว็บ API สาธารณะใช้ทั้งชื่ออย่าง Product API และพาธ
/api/v1ร่วมกัน อาจทำให้เวอร์ชันเชิงความหมายและโครงสร้างของ API ไม่สอดคล้องกัน - หากใช้พาธ
/v1/ควบคู่กับmajor.minor.patchจะทำให้ route กับสัญญา API ปะปนกัน และเลขตัวแรกของ semantic version ถูกตรึงไว้ใน URL - การเปลี่ยนแปลงที่ทำให้ความเข้ากันได้พังจำเป็นต้องมีพาธใหม่และการทำ route ผ่าน reverse proxy ส่งผลให้ข้อมูลสัญญาถูกกระจายอยู่ทั้งใน URL และหมายเลขเวอร์ชัน
- หากสร้าง API รุ่นถัดไปไปพร้อมกัน API เดิมก็แทบจะถูกผูกไว้กับ
v1โดยปริยาย และแม้ภายหลังจะมีการเปลี่ยนแปลงที่ทำให้ความเข้ากันได้พัง ความหมายของชื่อและพาธก็จะยิ่งคลุมเครือ - อยากทราบว่ามีจุดที่น่ารำคาญซ้ำ ๆ หรือมี หลักการออกแบบ ที่ดีกว่าสำหรับการจัดการเวอร์ชันของเว็บ API สาธารณะหรือไม่
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
การใส่
/v1/ไว้ใน URL จริง ๆ แล้วเป็นข้อดีอย่างหนึ่ง เพราะมัน บังคับไม่ให้ API พัง สำหรับผู้ใช้ จนกว่าจะปิด endpoint นั้นบทความอื่นของผู้เขียนคนเดียวกันอย่าง Evolving HTTP APIs ให้คำแนะนำที่มีประโยชน์
โดยพื้นฐานแล้วจะใส่
/v1/,/v2/ไว้ในแต่ละ route เพื่อบ่งบอกการเปลี่ยนแปลงแบบ breaking change หากเป็น public production API ไม่ใช่มาตรฐานที่พยายามนิยามให้ทำงานข้ามหลายโฮสต์ ก็แทบไม่มีเหตุผลต้องใช้ semantic versioning แบบเต็มรูปแบบsemantic versioning มีไว้เพื่อให้นักพัฒนาคนอื่นอัปเดต dependency ได้อย่างมั่นใจโดยไม่ต้องเสียเวลาอ่าน changelog ทีละ 20 นาที แต่ใน API ที่ใช้งานจริง คนใช้เลือกไม่ได้ว่าจะรับ minor version ใหม่หรือ bugfix version เมื่อไร
สิ่งที่นับเป็น breaking change คือการเปลี่ยน พฤติกรรมที่มีการระบุไว้ในเอกสาร หรือทำให้ client เดิมที่พึ่งพาพฤติกรรมนั้นใช้งานไม่ได้ บางที่นับการเปลี่ยนพฤติกรรมที่ไม่ได้ระบุไว้ในเอกสารว่าเป็น breaking change ด้วย แต่แนวทางนั้นมีความเสี่ยงมาก
ที่ Google ใช้แนวทางนี้: AIP-185: API Versioning, AIP-180: Bacwards compatibility
รู้สึกว่าเอกสารออกแบบพวกนี้ค่อนข้างเฉพาะกับวิธีทำงานของ Google แต่ก็เคยใช้อ้างอิงตอนออกแบบ API และมีไอเดียบางส่วนที่มีประโยชน์มาก
โดยทั่วไปคิดว่า API ทุกตัวควรพยายามลด breaking change ให้มากที่สุด ตัวอย่างเช่น ถ้าอยากเปลี่ยนชื่อ property ก็ควรเพิ่มชื่อใหม่ซ้ำเข้าไปมากกว่าลบ property เดิมทิ้ง
แต่แนวทางแบบ the people at Buttondown do it ก็สะอาดดี คือกำหนด migration ระหว่างเวอร์ชันของ API เพื่อให้ผู้ใช้ตรึงเวอร์ชัน API ของตัวเองผ่าน header ได้ และฝั่งผู้ให้บริการก็เดินหน้าปรับเปลี่ยนต่อได้
คำตอบที่นึกได้คือ “ให้ชื่อใหม่มีลำดับความสำคัญเสมอ” แต่พอ client ทำลำดับอ่าน-แก้ไข-เขียน แล้วส่งออบเจ็กต์ที่เซิร์ฟเวอร์สร้างกลับมาในเวอร์ชันแก้ไข วิธีนี้อาจพังได้ เพราะ client อาจอัปเดตแค่ property เก่า และส่ง property ใหม่ที่ไม่ได้แตะต้องกลับมาทั้งเดิม
ดูเหมือนการแปลงแบบนั้นอาจนำไปใช้กับการแมปพฤติกรรมได้ด้วย แต่ถ้าไม่ได้อ่านพลาด บทความนั้นไม่ได้กล่าวถึงส่วนนี้
ในอุดมคติควรใส่เวอร์ชันไว้ใน path และทำให้เวอร์ชันใหม่มีลักษณะเป็นการเพิ่มเติมจากเดิม จากนั้น API เวอร์ชันเก่าก็สามารถ reroute คำขอไปยัง API เวอร์ชันใหม่กว่า โดยทำการแปลง input/output ที่จำเป็น ผ่านไปหลายปี เมื่อไม่มีใครใช้เวอร์ชันเก่าแล้วก็ลบออกได้ และ path
/v1/ก็จะกลายเป็น errorเคยอ่านมานิดหน่อยเกี่ยวกับการทำ API versioning ผ่าน content negotiation ด้วย
Acceptheader ถ้าใครเคยทำ API versioning แบบนั้นจริง ๆ ก็อยากรู้ประสบการณ์จากประสบการณ์ของผม/ฉัน การทำ versioning ราย resource หรือแบบ global น่าจะเป็นวิธีที่ตรงไปตรงมาที่สุด สำหรับการเลิกใช้งาน API การใช้ HTTP response header
Deprecation(RFC 9745) และสุดท้ายให้ endpoint เก่าส่ง response อย่าง410 Goneดูเป็นแนวทางที่สมเหตุสมผลในการผลักให้ client ย้ายไปเวอร์ชันใหม่นอกจากนี้ก็อยากรู้จริง ๆ ว่ามีใครเคยสร้าง API ที่วิวัฒน์ต่อได้ บ้างไหม แบบที่รับคำขอเวอร์ชันเก่าแล้วแปลภายในให้เป็นคำขอของ API เวอร์ชันใหม่ ก่อนจะค่อย ๆ ลบเวอร์ชันเก่าทิ้งเมื่อ client ย้ายออกหรือเมื่อเวลาผ่านไป