REST API的演变
每個(gè)開發(fā)人員都以某種方式接觸到API 。 要么為一家大公司集成一個(gè)主要系統(tǒng),或者使用最新的圖形庫生成一些精美的圖表,要么直接與他喜歡的編程語言進(jìn)行交互。 事實(shí)是,API無處不在! 它們實(shí)際上代表了當(dāng)今Internet的基本構(gòu)建塊,在不同系統(tǒng)和設(shè)備之間發(fā)生的數(shù)據(jù)交換過程中扮演著重要角色。 從您手機(jī)上的簡(jiǎn)單天氣小部件到您在網(wǎng)上商店中執(zhí)行的信用卡付款,如果這些系統(tǒng)無法通過調(diào)用彼此的API相互通信,那么所有這些都是不可能的。
因此,隨著連接到互聯(lián)網(wǎng)的異構(gòu)設(shè)備生態(tài)系統(tǒng)的不斷發(fā)展,API提出了一系列嚴(yán)峻的挑戰(zhàn)。 盡管它們必須繼續(xù)以可靠和安全的方式運(yùn)行,但它們還必須與所有這些設(shè)備兼容,從手表到數(shù)據(jù)中心內(nèi)最先進(jìn)的服務(wù)器。
REST來解救
用于構(gòu)建此類API的最廣泛使用的技術(shù)之一就是所謂的REST API。 這些API旨在提供異構(gòu)系統(tǒng)之間通用且標(biāo)準(zhǔn)化的通信方式。 由于它們嚴(yán)重依賴于標(biāo)準(zhǔn)的通信協(xié)議和數(shù)據(jù)表示(例如HTTP,XML或JSON),因此很容易在大多數(shù)編程語言上提供客戶端實(shí)現(xiàn),從而使其與絕大多數(shù)系統(tǒng)和設(shè)備兼容。
因此,盡管這些REST API可以與大多數(shù)設(shè)備和技術(shù)兼容,但它們也必須不斷發(fā)展。 演化的問題在于,有時(shí)您必須保持與舊客戶端版本的回溯兼容性。
讓我們建立一個(gè)例子。
讓我們想象一個(gè)約會(huì)系統(tǒng),在該系統(tǒng)中您具有一個(gè)用于創(chuàng)建和檢索約會(huì)的API。 為簡(jiǎn)化起見,讓我們想象一下約會(huì)對(duì)象和日期和來賓姓名。 像這樣:
public class AppointmentDTO {public Long id;public Date date;public String guestName; }一個(gè)非常簡(jiǎn)單的REST API如下所示:
@Path("/api/appointments") public class AppointmentsAPI {@GET@Path("/{id}")public AppointmentDTO getAppointment(@PathParam("id") String id) { ... }@POSTpublic void createAppointment(AppointmentDTO appointment) { ... }}讓我們假設(shè)這個(gè)簡(jiǎn)單的簡(jiǎn)單API可以正常工作,并且可以在允許預(yù)訂和顯示約會(huì)的手機(jī),平板電腦和各種網(wǎng)站上使用。 到目前為止,一切都很好。
在某個(gè)時(shí)候,您認(rèn)為開始收集有關(guān)您的約會(huì)系統(tǒng)的一些統(tǒng)計(jì)信息將非常有趣。 為了簡(jiǎn)單起見,您只想知道誰是預(yù)訂次數(shù)最多的人。 為此,您需要將訪客之間關(guān)聯(lián)起來,并決定需要為每個(gè)訪客添加唯一的標(biāo)識(shí)符。 讓我們使用電子郵件。 因此,現(xiàn)在您的對(duì)象模型將如下所示:
public class AppointmentDTO {public Long id;public Date date;public GuestDTO guest; }public class GuestDTO {public String email;public String name; }因此,我們的對(duì)象模型稍有變化,這意味著我們將不得不在api上調(diào)整業(yè)務(wù)邏輯。
問題
盡管使API適應(yīng)存儲(chǔ)和檢索新對(duì)象類型應(yīng)該是一件容易的事,但問題是您當(dāng)前的所有客戶端都在使用舊模型,并且將繼續(xù)這樣做直到更新。 可以說您不必為此擔(dān)心,客戶應(yīng)該更新到較新的版本,但事實(shí)是您不能真正地從頭到尾進(jìn)行更新。 始終會(huì)有一個(gè)時(shí)間窗口,您必須保持兩個(gè)模型都運(yùn)行,這意味著您的api必須具有復(fù)古兼容性。
這是您的問題開始的地方。
回到我們的示例,在這種情況下,這意味著我們的API將必須處理兩個(gè)對(duì)象模型,并能夠根據(jù)客戶端存儲(chǔ)和檢索那些模型。 因此,讓我們將guestName添加回我們的對(duì)象中,以保持與舊客戶端的兼容性:
public class AppointmentDTO {public Long id;public Date date;@Deprecated //For retro compatibility purposespublic String guestName;public GuestDTO guest; }請(qǐng)記住,關(guān)于API對(duì)象的一條很好的經(jīng)驗(yàn)法則是,永遠(yuǎn)不要?jiǎng)h除字段。 添加新字段通常不會(huì)破壞任何客戶端實(shí)現(xiàn)(假設(shè)它們遵循忽略新字段的良好經(jīng)驗(yàn)法則),但是刪除字段通常是噩夢(mèng)之路。
現(xiàn)在,為了維護(hù)API兼容,有幾種不同的選擇。 讓我們看一些替代方案:
- 復(fù)制 :單純。 為新客戶端創(chuàng)建一種新方法,并使舊客戶端使用相同的方法。
- 查詢參數(shù) :引入一個(gè)標(biāo)志來控制行為。 諸如useGuests = true之類的東西。
- API版本控制 :在您的URL路徑中引入一個(gè)版本,以控制要調(diào)用的方法版本。
因此,所有這些替代方案都有其優(yōu)缺點(diǎn)。 盡管復(fù)制很簡(jiǎn)單,但它可以輕松地將您的API類變成一碗重復(fù)的代碼。
可以(并且應(yīng)該)將查詢參數(shù)用于行為控制(例如,將分頁添加到列表中),但是我們應(yīng)避免將它們用于實(shí)際的API演變,因?yàn)檫@些參數(shù)通常是永久性的,因此您不希望使用對(duì)于消費(fèi)者來說是可選的。
版本控制似乎是個(gè)好主意。 它提供了一種發(fā)展API的干凈方法,它使舊客戶端與新客戶端分離,并為您在API壽命期間發(fā)生的各種更改提供了通用基礎(chǔ)。 另一方面,它也引入了一些復(fù)雜性,特別是如果您在不同版本上有不同的調(diào)用時(shí)。 您的客戶最終將不得不通過升級(jí)調(diào)用而不是API來自己管理API的演變。 就像您沒有升級(jí)庫到下一個(gè)版本,而是只升級(jí)了該庫的某個(gè)類。 這很容易變成版本夢(mèng)night……
為了克服這個(gè)問題,我們必須確保我們的版本涵蓋整個(gè)API。 這意味著我應(yīng)該能夠使用/ v2來調(diào)用/ v1上的每個(gè)可用方法。 當(dāng)然,如果v2上存在給定方法的較新版本,則應(yīng)在/ v2調(diào)用上運(yùn)行它。 但是,如果給定的方法在v2中沒有更改,我希望可以無縫調(diào)用v1版本。
基于繼承的API版本控制
為了實(shí)現(xiàn)這一點(diǎn),我們可以利用Java對(duì)象的多態(tài)功能。 我們可以以分層的方式構(gòu)建API版本,以便較新版本可以覆蓋較舊版本的方法,而對(duì)未更改方法的較新版本的調(diào)用可以無縫地回退到其較早版本。
因此,回到我們的示例,我們可以構(gòu)建一個(gè)新版本的create方法,以便API如下所示:
@Path("/api/v1/appointments") //We add a version to our base path public class AppointmentsAPIv1 { //We add the version to our API classes@GET@Path("/{id}")public AppointmentDTO getAppointment(@PathParam("id") String id) { ... }@POSTpublic void createAppointment(AppointmentDTO appointment) { //Your old way of creating Appointments only with names} }//New API class that extends the previous version @Path("/api/v2/appointments") public class AppointmentsAPIv2 extends AppointmentsAPIv1 {@POST@Overridepublic void createAppointment(AppointmentDTO appointment) { //Your new way of creating appointments with guests} }因此,現(xiàn)在我們有2個(gè)有效的API版本。 盡管所有尚未升級(jí)到新版本的舊客戶端將繼續(xù)使用v1,并且不會(huì)看到任何更改,但您的所有新客戶現(xiàn)在都可以使用最新的v2。 請(qǐng)注意,所有這些調(diào)用均有效:
| GET /api/v1/appointments/123 | 將在v1類上運(yùn)行g(shù)etAppointment |
| GET /api/v2/appointments/123 | 將在v1類上運(yùn)行g(shù)etAppointment |
| POST /api/v1/appointments | 將在v1類上運(yùn)行createAppointment |
| POST /api/v2/appointments | 將在v2類上運(yùn)行createAppointment |
這樣,任何想要開始使用最新版本的使用者都只需將其基本URL更新為相應(yīng)的版本,并且所有API將無縫轉(zhuǎn)換為最新的實(shí)現(xiàn),同時(shí)保持舊的不變。
警告
出于敏銳的眼光,這種方法立即引起了警告。 如果您的API由十分之幾的不同類組成,那么即使您實(shí)際上沒有任何更改,較新的版本也意味著將它們?nèi)繌?fù)制為較高版本。 這是一些可以自動(dòng)生成的樣板代碼。 仍然很煩。
盡管沒有快速的方法可以解決此問題,但是使用接口可能會(huì)有所幫助。 除了創(chuàng)建新的實(shí)現(xiàn)類之外,您還可以創(chuàng)建一個(gè)新的帶路徑注釋的接口,并在當(dāng)前的實(shí)現(xiàn)類中對(duì)其進(jìn)行實(shí)現(xiàn)。 盡管您將必須為每個(gè)API類創(chuàng)建一個(gè)接口,但它有點(diǎn)干凈。 它有一點(diǎn)幫助,但仍然是一個(gè)警告。
最后的想法
API版本控制似乎是當(dāng)前的熱門話題。 存在許多不同的觀點(diǎn)和意見,但似乎缺乏標(biāo)準(zhǔn)的最佳實(shí)踐。 盡管本文并非旨在提供這樣的內(nèi)容,但我希望它有助于實(shí)現(xiàn)更好的API結(jié)構(gòu)并有助于其可維護(hù)性。
最后要說的是羅伯托·科爾特斯 ( Roberto Cortez)鼓勵(lì)并允許將此帖子發(fā)布在他的博客上。 這實(shí)際上是我的第一篇博文,因此請(qǐng)加載大炮并隨意開火。 :)
翻譯自: https://www.javacodegeeks.com/2015/03/rest-api-evolution.html
總結(jié)
以上是生活随笔為你收集整理的REST API的演变的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓手机当麦克风给电脑用(安卓手机当麦克
- 下一篇: Oracle MAF中的LOV