คุณจัดการโค้ดเบสสำหรับ API เวอร์ชันได้อย่างไร


107

ฉันได้อ่านข้อมูลเกี่ยวกับกลยุทธ์การกำหนดเวอร์ชันสำหรับ ReST APIs และสิ่งที่ดูเหมือนว่าไม่มีที่อยู่คือวิธีที่คุณจัดการ codebase ที่สำคัญ

สมมติว่าเรากำลังทำการเปลี่ยนแปลงหลายอย่างกับ API ตัวอย่างเช่นการเปลี่ยนทรัพยากรของลูกค้าเพื่อให้ส่งกลับแยกforenameและsurnameฟิลด์แทนที่จะเป็นnameฟิลด์เดียว (สำหรับตัวอย่างนี้ฉันจะใช้โซลูชันการกำหนดเวอร์ชัน URL เนื่องจากง่ายต่อการทำความเข้าใจแนวคิดที่เกี่ยวข้อง แต่คำถามนี้ใช้ได้กับการต่อรองเนื้อหาหรือส่วนหัว HTTP ที่กำหนดเอง)

ขณะนี้เรามีปลายทางที่และอีกปลายทางที่เข้ากันไม่ได้http://api.mycompany.com/v1/customers/{id} http://api.mycompany.com/v2/customers/{id}เรายังคงปล่อยการแก้ไขข้อบกพร่องและการอัปเดตความปลอดภัยให้กับ v1 API แต่การพัฒนาฟีเจอร์ใหม่ทั้งหมดมุ่งเน้นไปที่ v2 เราจะเขียนทดสอบและปรับใช้การเปลี่ยนแปลงในเซิร์ฟเวอร์ API ของเราได้อย่างไร ฉันเห็นวิธีแก้ปัญหาอย่างน้อยสองวิธี:

  • ใช้สาขา / แท็กควบคุมแหล่งที่มาสำหรับ v1 codebase v1 และ v2 ได้รับการพัฒนาและปรับใช้อย่างอิสระโดยมีการผสานการควบคุมการแก้ไขที่ใช้ตามความจำเป็นเพื่อใช้การแก้ไขข้อบกพร่องเดียวกันกับทั้งสองเวอร์ชันซึ่งคล้ายกับวิธีที่คุณจัดการโค้ดเบสสำหรับแอปเนทีฟเมื่อพัฒนาเวอร์ชันใหม่ที่สำคัญในขณะที่ยังคงรองรับเวอร์ชันก่อนหน้า

  • ทำให้ codebase ทราบถึงเวอร์ชันของ API ดังนั้นคุณจึงลงเอยด้วย codebase เดียวที่มีทั้งตัวแทนลูกค้า v1 และตัวแทนลูกค้า v2 ถือว่าการกำหนดเวอร์ชันเป็นส่วนหนึ่งของสถาปัตยกรรมโซลูชันของคุณแทนที่จะเป็นปัญหาในการปรับใช้ - อาจใช้เนมสเปซและการกำหนดเส้นทางร่วมกันเพื่อให้แน่ใจว่าคำขอได้รับการจัดการโดยเวอร์ชันที่ถูกต้อง

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


41
ขอบคุณที่ถามคำถามนี้! ไม่น่าเชื่อว่าจะมีคนไม่ตอบคำถามนี้อีก !! ฉันรู้สึกเบื่อหน่ายและเบื่อหน่ายกับการที่ทุกคนมีความเห็นเกี่ยวกับวิธีที่เวอร์ชันต่างๆเข้าสู่ระบบ แต่ดูเหมือนจะไม่มีใครจัดการกับปัญหาที่แท้จริงของการส่งเวอร์ชันไปยังรหัสที่เหมาะสมได้ ถึงตอนนี้ควรมี "รูปแบบ" หรือ "วิธีแก้ปัญหา" ที่เป็นที่ยอมรับอย่างน้อยสำหรับปัญหาที่ดูเหมือนทั่วไปนี้ มีคำถามมากมายเกี่ยวกับ SO เกี่ยวกับ "การกำหนดเวอร์ชัน API" การตัดสินใจว่าจะยอมรับเวอร์ชันเป็นอย่างไร FRIKKIN SIMPLE (ค่อนข้าง)! การจัดการใน codebase เมื่อเข้ามาแล้วยากมาก!
arijeet

คำตอบ:


46

ฉันใช้ทั้งสองกลยุทธ์ที่คุณพูดถึง ในสองข้อนั้นฉันชอบแนวทางที่สองคือง่ายกว่าในกรณีการใช้งานที่รองรับ นั่นคือหากความต้องการในการกำหนดเวอร์ชันนั้นเรียบง่ายให้ใช้การออกแบบซอฟต์แวร์ที่ง่ายขึ้น:

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

ฉันไม่พบว่าการลบเวอร์ชันที่เลิกใช้แล้วโดยใช้โมเดลนี้เป็นเรื่องยากเกินไป:

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

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

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

วิธีนี้จะได้ผลหากเวอร์ชัน API ของคุณมีแนวโน้มที่จะเปิดเผยทรัพยากรประเภทเดียวกัน แต่มีการแสดงโครงสร้างที่แตกต่างกันดังในตัวอย่างชื่อเต็ม / ชื่อ / นามสกุล / นามสกุลของคุณ มันจะยากขึ้นเล็กน้อยหากพวกเขาเริ่มใช้การคำนวณแบ็กเอนด์ที่แตกต่างกันเช่น "บริการแบ็กเอนด์ของฉันส่งคืนดอกเบี้ยทบต้นที่คำนวณไม่ถูกต้องซึ่งเปิดเผยใน API สาธารณะ v1 ลูกค้าของเราได้แก้ไขพฤติกรรมที่ไม่ถูกต้องนี้แล้วดังนั้นฉันจึงไม่สามารถอัปเดตได้ การคำนวณในแบ็กเอนด์และใช้งานได้จนถึง v2 ดังนั้นตอนนี้เราต้องแยกรหัสคำนวณดอกเบี้ยของเรา " โชคดีที่สิ่งเหล่านี้มักจะเกิดขึ้นไม่บ่อยนัก: ในทางปฏิบัติแล้วผู้บริโภคของ RESTful APIs ชอบการนำเสนอทรัพยากรที่ถูกต้องมากกว่าความเข้ากันได้แบบย้อนกลับของ bug-for-bug แม้ในระหว่างการเปลี่ยนแปลงที่ไม่ทำลายGETทรัพยากร ted ในทางทฤษฎี

ฉันจะสนใจที่จะรับฟังการตัดสินใจในที่สุดของคุณ


5
แค่อยากรู้ว่าในซอร์สโค้ดคุณทำซ้ำโมเดลระหว่าง v0 และ v1 ที่ไม่เปลี่ยนแปลงหรือไม่? หรือคุณมี v1 ใช้บางรุ่น v0? สำหรับฉันฉันจะสับสนถ้าฉันเห็น v1 ใช้โมเดล v0 สำหรับบางฟิลด์ แต่ในทางกลับกันมันจะลดการขยายตัวของโค้ด สำหรับการจัดการหลายเวอร์ชันเราต้องยอมรับและดำเนินชีวิตด้วยรหัสซ้ำสำหรับรุ่นที่ไม่เคยเปลี่ยนแปลงหรือไม่?
EdgeCaseBerg

1
ความทรงจำของฉันคือซอร์สโค้ดรุ่นที่กำหนดเวอร์ชันโดยไม่ขึ้นกับตัว API ดังนั้นตัวอย่างเช่น API v1 อาจใช้ Model V1 และ API v2 อาจใช้ Model V1 ด้วย โดยทั่วไปแล้วกราฟการอ้างอิงภายในสำหรับ API สาธารณะจะรวมทั้งโค้ด API ที่เปิดเผยรวมทั้งโค้ด "การเติมเต็ม" แบ็กเอนด์เช่นเซิร์ฟเวอร์และรหัสโมเดล สำหรับหลายเวอร์ชันกลยุทธ์เดียวที่ฉันเคยใช้คือการทำซ้ำสแต็กทั้งหมด - วิธีการแบบไฮบริด (โมดูล A ซ้ำกันโมดูล B เป็นเวอร์ชัน ... ) ดูเหมือนจะสับสนมาก YMMV แน่นอน :)
Palpatim

2
ฉันไม่แน่ใจว่าฉันทำตามสิ่งที่แนะนำสำหรับแนวทางที่สาม มีตัวอย่างโค้ดสาธารณะที่มีโครงสร้างเหมือนกันหรือไม่?
Ehtesh Choudhury

13

สำหรับฉันแนวทางที่สองดีกว่า ฉันใช้มันสำหรับบริการเว็บ SOAP และวางแผนที่จะใช้กับ REST ด้วย

ในขณะที่คุณเขียนโค้ดเบสควรเป็นเวอร์ชันที่ทราบ แต่สามารถใช้เลเยอร์ความเข้ากันได้เป็นเลเยอร์แยกต่างหาก ในตัวอย่างของคุณ codebase สามารถสร้างการแสดงทรัพยากร (JSON หรือ XML) ด้วยชื่อและนามสกุล แต่ชั้นความเข้ากันได้จะเปลี่ยนให้มีเฉพาะชื่อแทน

โค้ดเบสควรใช้เฉพาะเวอร์ชันล่าสุดสมมติว่า v3 เลเยอร์ความเข้ากันได้ควรแปลงการร้องขอและการตอบสนองระหว่างเวอร์ชันล่าสุด v3 และเวอร์ชันที่รองรับเช่น v1 และ v2 เลเยอร์ความเข้ากันได้สามารถมีอะแดปเตอร์แยกต่างหากสำหรับแต่ละเวอร์ชันที่รองรับซึ่งสามารถเชื่อมต่อเป็นสายโซ่

ตัวอย่างเช่น:

คำขอไคลเอ็นต์ v1: v1 ปรับให้เข้ากับ v2 ---> v2 ปรับให้เข้ากับ v3 ----> codebase

คำขอไคลเอ็นต์ v2: v1 ปรับให้เข้ากับ v2 (ข้าม) ---> v2 ปรับให้เข้ากับ v3 ----> codebase

สำหรับการตอบสนองอะแดปเตอร์จะทำงานในทิศทางตรงกันข้าม หากคุณใช้ Java EE คุณสามารถใช้โซ่ตัวกรอง servlet เป็นห่วงโซ่อะแด็ปเตอร์ได้

การลบหนึ่งเวอร์ชันเป็นเรื่องง่ายลบอะแดปเตอร์ที่เกี่ยวข้องและรหัสทดสอบ


เป็นการยากที่จะรับประกันความเข้ากันได้หากฐานรหัสพื้นฐานทั้งหมดมีการเปลี่ยนแปลง ปลอดภัยกว่ามากในการรักษา codebase เดิมไว้สำหรับรุ่นแก้ไขข้อบกพร่อง
Marcelo Cantos

5

การแตกกิ่งก้านดูเหมือนจะดีกว่าสำหรับฉันมากและฉันใช้วิธีนี้ในกรณีของฉัน

ใช่ตามที่คุณได้กล่าวไปแล้ว - การแก้ไขข้อผิดพลาดในการส่งข้อมูลย้อนกลับจะต้องใช้ความพยายาม แต่ในขณะเดียวกันการสนับสนุนหลายเวอร์ชันภายใต้ฐานซอร์สเดียว (ด้วยการกำหนดเส้นทางและสิ่งอื่น ๆ ทั้งหมด) จะทำให้คุณต้องใช้ความพยายามไม่น้อยกว่า แต่อย่างน้อยก็ต้องใช้ความพยายามเท่ากันทำให้ระบบมีมากขึ้น ซับซ้อนและน่ากลัวด้วยสาขาตรรกะที่แตกต่างกันภายใน (ในบางจุดของการกำหนดเวอร์ชันคุณจะต้องcase()ชี้ไปที่โมดูลเวอร์ชันที่มีรหัสซ้ำกันมากหรือแย่กว่านั้นif(version == 2) then...) อย่าลืมว่าเพื่อวัตถุประสงค์ในการถดถอยคุณยังคงต้องทำการทดสอบแยกส่วน

เกี่ยวกับนโยบายการกำหนดเวอร์ชัน: ฉันจะเก็บไว้เป็นเวอร์ชันสูงสุด -2 เวอร์ชันจากปัจจุบันโดยเลิกใช้การสนับสนุนสำหรับเวอร์ชันเก่าซึ่งจะทำให้ผู้ใช้มีแรงจูงใจในการย้าย


ฉันกำลังคิดเกี่ยวกับการทดสอบใน codebase เดียวในขณะนี้ คุณบอกว่าการทดสอบจะต้องมีการแยกสาขาเสมอ แต่ฉันคิดว่าการทดสอบทั้งหมดสำหรับ v1, v2, v3 และอื่น ๆ อาจอยู่ในโซลูชันเดียวกันได้เช่นกันและทั้งหมดจะถูกเรียกใช้ในเวลาเดียวกัน ฉันคิดว่าการตกแต่งการทดสอบที่มีคุณลักษณะที่ระบุสิ่งที่พวกเขาสนับสนุนรุ่น: เช่น [Version(From="v1", To="v2")], [Version(From="v2", To="v3")], [Version(From="v1")] // All versions เพียงแค่การสำรวจได้ในขณะนี้เคยได้ยินใครทำมันได้หรือไม่
Lee Gunn

1
หลังจาก 3 ปีฉันได้เรียนรู้ว่าไม่มีคำตอบที่แน่นอนสำหรับคำถามเดิม: D. ขึ้นอยู่กับโครงการมาก หากคุณสามารถตรึง API และรักษาไว้ได้เท่านั้น (เช่นการแก้ไขข้อบกพร่อง) ฉันจะยังคงแยก / แยกรหัสที่เกี่ยวข้อง (ตรรกะทางธุรกิจที่เกี่ยวข้องกับ API + การทดสอบ + จุดสิ้นสุดส่วนที่เหลือ) และมีสิ่งที่แชร์ทั้งหมดในไลบรารีแยกต่างหาก (ด้วยการทดสอบของตัวเอง ). หาก V1 จะอยู่ร่วมกับ V2 เป็นเวลาพอสมควรและการทำงานของฟีเจอร์ยังคงดำเนินต่อไปฉันจะเก็บไว้ด้วยกันและทดสอบด้วย (ครอบคลุม V1, V2 เป็นต้นและตั้งชื่อตามนั้น)
edmarisov

1
ขอบคุณ. ใช่ดูเหมือนว่าจะเป็นพื้นที่แสดงความคิดเห็น ฉันจะลองวิธีการแก้ปัญหาเดียวก่อนและดูว่าจะเป็นอย่างไร
Lee Gunn

0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.