รูปแบบที่แนะนำสำหรับการวางแผนจุดปลาย REST สำหรับการเปลี่ยนแปลงที่คาดการณ์ไว้คืออะไร


25

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

ข้อกังวลหลักของบทความนี้คือรูปแบบใดที่ควรปฏิบัติตามสำหรับจุดสิ้นสุดที่กำหนดไว้ทั้งหมดสำหรับผลิตภัณฑ์ / บริษัท ที่กำหนด

โครงการฐาน

ได้รับเทมเพลต URL ฐานของhttps://rest.product.com/ผมได้วางแผนว่าบริการทั้งหมดอาศัยอยู่ภายใต้/apiพร้อมกับ/authและปลายทางที่ไม่ใช่ส่วนที่เหลืออื่น ๆ /docเช่น ดังนั้นฉันสามารถสร้างจุดปลายพื้นฐานดังนี้:

https://rest.product.com/api/...
https://rest.product.com/auth/login
https://rest.product.com/auth/logout
https://rest.product.com/doc/...

จุดบริการ

ตอนนี้สำหรับปลายทางเอง ความกังวลเกี่ยวกับPOST, GET, DELETEไม่ได้เป็นวัตถุประสงค์หลักของบทความนี้และเป็นกังวลกับการกระทำเหล่านั้นเอง

จุดปลายสามารถแบ่งออกเป็นเนมสเปซและการกระทำได้ แต่ละการกระทำจะต้องนำเสนอตัวเองในวิธีที่จะสนับสนุนการเปลี่ยนแปลงขั้นพื้นฐานในประเภทผลตอบแทนหรือพารามิเตอร์ที่จำเป็น

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

https://rest.product.com/api/messages/list/{user}
https://rest.product.com/api/messages/send

ตอนนี้เพื่อเพิ่มการรองรับเวอร์ชันสำหรับการเปลี่ยนแปลง API ในอนาคตซึ่งอาจแตกหัก เราสามารถเพิ่มลายเซ็นเวอร์ชันหลังจาก/api/หรือหลังจาก/messages/นั้น เมื่อกำหนดsendจุดสิ้นสุดเราจะได้สิ่งต่อไปนี้สำหรับ v1

https://rest.product.com/api/v1/messages/send
https://rest.product.com/api/messages/v1/send

ดังนั้นคำถามแรกของฉันคือสถานที่ที่แนะนำสำหรับตัวระบุรุ่นคืออะไร

ผู้จัดการรหัสควบคุม

ดังนั้นตอนนี้เราได้สร้างขึ้นแล้วเราจำเป็นต้องสนับสนุนเวอร์ชันก่อนหน้าดังนั้นเราจึงต้องจัดการกับรหัสสำหรับเวอร์ชั่นใหม่แต่ละเวอร์ชั่นซึ่งอาจเลิกใช้ไปตามกาลเวลา สมมติว่าเรากำลังเขียนจุดปลายใน Java เราสามารถจัดการสิ่งนี้ผ่านแพ็คเกจ

package com.product.messages.v1;
public interface MessageController {
    void send();
    Message[] list();
}

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

อีกวิธีหนึ่งคือการสร้างตัวจัดการสำหรับแต่ละปลายทาง

package com.product.messages;
public class MessageServiceImpl {
    public void send(String version) {
        getMessageSender(version).send();
    }
    // Assume we have a List of senders in order of newest to oldest.
    private MessageSender getMessageSender(String version) {
        for (MessageSender s : senders) {
            if (s.supportsVersion(version)) {
                return s;
            }
        }
    }
}

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

ดังนั้นจึงมีคำถามที่สองของฉัน "วิธีที่ดีที่สุดในการออกแบบรหัสบริการ REST เพื่อรองรับเวอร์ชันก่อนหน้าคืออะไร"

คำตอบ:


13

ดังนั้นจึงมีคำถามที่สองของฉัน "วิธีที่ดีที่สุดในการออกแบบรหัสบริการ REST เพื่อรองรับเวอร์ชันก่อนหน้าคืออะไร"

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

แน่นอนว่าคุณอาจไม่ได้ลองครั้งแรก ดังนั้น:

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

3

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

rest.product.com/api/v1/messages/send

สำหรับไลบรารี่ของลูกค้าฉันคิดว่าการใช้สองแอพพลิเคชั่นแบบเต็มในแพ็คเกจ Java แยกนั้นสะอาดกว่าใช้งานง่ายกว่าและบำรุงรักษาง่ายกว่า

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

http://theamiableapi.com/2011/10/18/api-design-best-practice-plan-for-evolution/

สำหรับเวอร์ชัน REST API โดยเฉพาะคุณจะพบว่าโพสต์นี้โดย Mark Nottingham มีประโยชน์:

http://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown


3

อีกวิธีหนึ่งในการจัดการการกำหนดเวอร์ชัน API คือการใช้เวอร์ชันในส่วนหัว HTTP ชอบ

POST /messages/list/{user} HTTP/1.1
Host: http://rest.service.com
Content-Type: application/json
API-Version: 1.0      <----- like here
Cache-Control: no-cache

คุณสามารถวิเคราะห์ส่วนหัวและจัดการอย่างเหมาะสมในส่วนหลัง

ด้วยวิธีการนี้ลูกค้าไม่จำเป็นต้องเปลี่ยน URL เพียงแค่ส่วนหัว และสิ่งนี้ทำให้จุดสิ้นสุด REST สะอาดเสมอ

หากไคลเอนต์รายใดไม่ส่งส่วนหัวของเวอร์ชันคุณจะส่ง400 - คำขอไม่ถูกต้องหรือคุณสามารถจัดการกับAPI เวอร์ชันย้อนหลังที่เข้ากันได้ของคุณ

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