摘要:通常情況下,偽都是基于第一層次與第二層次設(shè)計(jì)的。為了解決這個(gè)版本不兼容問題,在設(shè)計(jì)的一種實(shí)用的做法是使用版本號。例如,建議第三位版本號通常表示兼容升級,只有不兼容時(shí)才需要變更服務(wù)版本。
原文地址:梁桂釗的博客博客地址:http://blog.720ui.com
歡迎關(guān)注公眾號:「服務(wù)端思維」。一群同頻者,一起成長,一起精進(jìn),打破認(rèn)知的局限性。
有一段時(shí)間沒怎么寫文章了,今天提筆寫一篇自己對 API 設(shè)計(jì)的思考。首先,為什么寫這個(gè)話題呢?其一,我閱讀了《阿里研究員谷樸:API 設(shè)計(jì)最佳實(shí)踐的思考》一文后受益良多,前兩天并轉(zhuǎn)載了這篇文章也引發(fā)了廣大讀者的興趣,我覺得我應(yīng)該把我自己的思考整理成文與大家一起分享與碰撞。其二,我覺得我針對這個(gè)話題,可以半個(gè)小時(shí)之內(nèi)搞定,爭取在 1 點(diǎn)前關(guān)燈睡覺,哈哈。
現(xiàn)在,我們來一起探討 API 的設(shè)計(jì)之道。我會拋出幾個(gè)觀點(diǎn),歡迎探討。
一、定義好的規(guī)范,已經(jīng)成功了一大半通常情況下,規(guī)范就是大家約定俗成的標(biāo)準(zhǔn),如果大家都遵守這套標(biāo)準(zhǔn),那么自然溝通成本大大降低。例如,大家都希望從阿里的規(guī)范上面學(xué)習(xí),在自己的業(yè)務(wù)中也定義幾個(gè)領(lǐng)域模型:VO、BO、DO、DTO。其中,DO(Data Object)與數(shù)據(jù)庫表結(jié)構(gòu)一一對應(yīng),通過 DAO 層向上傳輸數(shù)據(jù)源對象。 而 DTO(Data Transfer Object)是遠(yuǎn)程調(diào)用對象,它是 RPC 服務(wù)提供的領(lǐng)域模型。對于 BO(Business Object),它是業(yè)務(wù)邏輯層封裝業(yè)務(wù)邏輯的對象,一般情況下,它是聚合了多個(gè)數(shù)據(jù)源的復(fù)合對象。那么,VO(View Object) 通常是請求處理層傳輸?shù)膶ο螅ㄟ^ Spring 框架的轉(zhuǎn)換后,往往是一個(gè) JSON 對象。
事實(shí)上,阿里這種復(fù)雜的業(yè)務(wù)中如果不劃分清楚 ?DO、BO、DTO、VO 的領(lǐng)域模型,其內(nèi)部代碼很容易就混亂了,內(nèi)部的 RPC 在 service 層的基礎(chǔ)上又增加了 manager 層,從而實(shí)現(xiàn)內(nèi)部的規(guī)范統(tǒng)一化。但是,如果只是多帶帶的域又沒有太多外部依賴,那么,完全不要設(shè)計(jì)這么復(fù)雜,除非預(yù)期到可能會變得龐大和復(fù)雜化。對此,設(shè)計(jì)過程中因地制宜就顯得特別重要了。
另外一個(gè)規(guī)范的例子是 RESTful API。在 REST 架構(gòu)風(fēng)格中,每一個(gè) URI 代表一種資源。因此,URI 是每一個(gè)資源的地址的唯一資源定位符。所謂資源,實(shí)際上就是一個(gè)信息實(shí)體,它可以是服務(wù)器上的一段文本、一個(gè)文件、一張圖片、一首歌曲,或者是一種服務(wù)。RESTful API 規(guī)定了通過 GET、 POST、 PUT、 PATCH、 DELETE 等方式對服務(wù)端的資源進(jìn)行操作。
【GET】 /users # 查詢用戶信息列表 【GET】 /users/1001 # 查看某個(gè)用戶信息 【POST】 /users # 新建用戶信息 【PUT】 /users/1001 # 更新用戶信息(全部字段) 【PATCH】 /users/1001 # 更新用戶信息(部分字段) 【DELETE】 /users/1001 # 刪除用戶信息
事實(shí)上,RESTful API 的實(shí)現(xiàn)分了四個(gè)層級。第一層次(Level 0)的 Web API 服務(wù)只是使用 HTTP 作為傳輸方式。第二層次(Level 1)的 Web API 服務(wù)引入了資源的概念。每個(gè)資源有對應(yīng)的標(biāo)識符和表達(dá)。第三層次(Level 2)的 Web API 服務(wù)使用不同的 HTTP 方法來進(jìn)行不同的操作,并且使用 HTTP 狀態(tài)碼來表示不同的結(jié)果。第四層次(Level 3)的 Web API 服務(wù)使用 HATEOAS。在資源的表達(dá)中包含了鏈接信息。客戶端可以根據(jù)鏈接來發(fā)現(xiàn)可以執(zhí)行的動作。通常情況下,偽 RESTful API 都是基于第一層次與第二層次設(shè)計(jì)的。例如,我們的 Web API 中使用各種動詞,例如 get_menu?和 save_menu?,而真正意義上的?RESTful API 需要滿足第三層級以上。如果我們遵守了這套規(guī)范,我們就很可能就設(shè)計(jì)出通俗易懂的 API。
注意的是,定義好的規(guī)范,我們已經(jīng)成功了一大半。如果這套規(guī)范是業(yè)內(nèi)標(biāo)準(zhǔn),那么我們可以大膽實(shí)踐,不要擔(dān)心別人不會用,只要把業(yè)界標(biāo)準(zhǔn)丟給他好好學(xué)習(xí)一下就可以啦。例如,Spring 已經(jīng)在 Java 的生態(tài)中舉足輕重,如果一個(gè)新人不懂 Spring 就有點(diǎn)說不過去了。但是,很多時(shí)候因?yàn)闃I(yè)務(wù)的限制和公司的技術(shù),我們可能使用基于第一層次與第二層次設(shè)計(jì)的偽 RESTful API,但是它不一定就是落后的,不好的,只要團(tuán)隊(duì)內(nèi)部形成規(guī)范,降低大家的學(xué)習(xí)成本即可。很多時(shí)候,我們試圖改變團(tuán)隊(duì)的習(xí)慣去學(xué)習(xí)一個(gè)新的規(guī)范,所帶來的收益(投入產(chǎn)出比)甚微,那就得不償失了。
總結(jié)一下,定義好的規(guī)范的目的在于,降低學(xué)習(xí)成本,使得 API 盡可能通俗易懂。當(dāng)然,設(shè)計(jì)的 API?通俗易懂還有其他方式,例如我們定義的 API 的名字易于理解,API 的實(shí)現(xiàn)盡可能通用等。
二、探討 API 接口的兼容性API 接口都是不斷演進(jìn)的。因此,我們需要在一定程度上適應(yīng)變化。在 RESTful API 中,API 接口應(yīng)該盡量兼容之前的版本。但是,在實(shí)際業(yè)務(wù)開發(fā)場景中,可能隨著業(yè)務(wù)需求的不斷迭代,現(xiàn)有的 API 接口無法支持舊版本的適配,此時(shí)如果強(qiáng)制升級服務(wù)端的 API 接口將導(dǎo)致客戶端舊有功能出現(xiàn)故障。實(shí)際上,Web 端是部署在服務(wù)器,因此它可以很容易為了適配服務(wù)端的新的 API 接口進(jìn)行版本升級,然而像 Android 端、IOS 端、PC 端等其他客戶端是運(yùn)行在用戶的機(jī)器上,因此當(dāng)前產(chǎn)品很難做到適配新的服務(wù)端的 API 接口,從而出現(xiàn)功能故障,這種情況下,用戶必須升級產(chǎn)品到最新的版本才能正常使用。為了解決這個(gè)版本不兼容問題,在設(shè)計(jì) RESTful API 的一種實(shí)用的做法是使用版本號。一般情況下,我們會在 url 中保留版本號,并同時(shí)兼容多個(gè)版本。
【GET】 /v1/users/{user_id} // 版本 v1 的查詢用戶列表的 API 接口 【GET】 /v2/users/{user_id} // 版本 v2 的查詢用戶列表的 API 接口
現(xiàn)在,我們可以不改變版本 v1 的查詢用戶列表的 API 接口的情況下,新增版本 v2 的查詢用戶列表的 API 接口以滿足新的業(yè)務(wù)需求,此時(shí),客戶端的產(chǎn)品的新功能將請求新的服務(wù)端的 API 接口地址。雖然服務(wù)端會同時(shí)兼容多個(gè)版本,但是同時(shí)維護(hù)太多版本對于服務(wù)端而言是個(gè)不小的負(fù)擔(dān),因?yàn)榉?wù)端要維護(hù)多套代碼。這種情況下,常見的做法不是維護(hù)所有的兼容版本,而是只維護(hù)最新的幾個(gè)兼容版本,例如維護(hù)最新的三個(gè)兼容版本。在一段時(shí)間后,當(dāng)絕大多數(shù)用戶升級到較新的版本后,廢棄一些使用量較少的服務(wù)端的老版本API 接口版本,并要求使用產(chǎn)品的非常舊的版本的用戶強(qiáng)制升級。注意的是,“不改變版本 v1 的查詢用戶列表的 API 接口”主要指的是對于客戶端的調(diào)用者而言它看起來是沒有改變。而實(shí)際上,如果業(yè)務(wù)變化太大,服務(wù)端的開發(fā)人員需要對舊版本的 API 接口使用適配器模式將請求適配到新的API 接口上。
有趣的是,GraphQL 提供不同的思路。GraphQL 為了解決服務(wù) API 接口爆炸的問題,以及將多個(gè) HTTP 請求聚合成了一個(gè)請求,提出只暴露單個(gè)服務(wù) API 接口,并且在單個(gè)請求中可以進(jìn)行多個(gè)查詢。GraphQL 定義了 API 接口,我們可以在前端更加靈活調(diào)用,例如,我們可以根據(jù)不同的業(yè)務(wù)選擇并加載需要渲染的字段。因此,服務(wù)端提供的全量字段,前端可以按需獲取。GraphQL 可以通過增加新類型和基于這些類型的新字段添加新功能,而不會造成兼容性問題。
此外,在使用 RPC API 過程中,我們特別需要注意兼容性問題,二方庫不能依賴 parent,此外,本地開發(fā)可以使用 SNAPSHOT,而線上環(huán)境禁止使用,避免發(fā)生變更,導(dǎo)致版本不兼容問題。我們需要為每個(gè)接口都應(yīng)定義版本號,保證后續(xù)不兼容的情況下可以升級版本。例如,Dubbo 建議第三位版本號通常表示兼容升級,只有不兼容時(shí)才需要變更服務(wù)版本。
關(guān)于規(guī)范的案例,我們可以看看 k8s 和 github,其中 k8s 采用了 RESTful API,而 github 部分采用了 GraphQL。
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/
https://developer.github.com/v4/
三、提供清晰的思維模型所謂思維模型,我的理解是針對問題域抽象模型,對域模型的功能有統(tǒng)一認(rèn)知,構(gòu)建某個(gè)問題的現(xiàn)實(shí)映射,并劃分好模型的邊界,而域模型的價(jià)值之一就是統(tǒng)一思想,明確邊界。假設(shè),大家沒有清晰的思維模型,那么也不存在對 API 的統(tǒng)一認(rèn)知,那么就很可能出現(xiàn)下面圖片中的現(xiàn)實(shí)問題。
我認(rèn)為好的 API 接口具有抽象性,因此需要盡可能的屏蔽業(yè)務(wù)實(shí)現(xiàn)。那么,問題來了,我們怎么理解抽象性?對此,我們可以思考?java.sql.Driver 的設(shè)計(jì)。這里,java.sql.Driver 是一個(gè)規(guī)范接口,而 com.mysql.jdbc.Driver
則是 mysql-connector-java-xxx.jar 對這個(gè)規(guī)范的實(shí)現(xiàn)接口。那么,切換成 Oracle 的成本就非常低了。
一般情況下,我們會通過 API 對外提供服務(wù)。這里,API 提供服務(wù)的接口的邏輯是固定的,換句話說,它具有通用性。但是,但我們遇到具有類似的業(yè)務(wù)邏輯的場景時(shí),即核心的主干邏輯相同,而細(xì)節(jié)的實(shí)現(xiàn)略有不同,那我們該何去何從?很多時(shí)候,我們會選擇提供多個(gè) API 接口給不同的業(yè)務(wù)方使用。事實(shí)上,我們可以通過 SPI 擴(kuò)展點(diǎn)來實(shí)現(xiàn)的更加優(yōu)雅。什么是 SPI?SPI 的英文全稱是 Serivce Provider Interface,即服務(wù)提供者接口,它是一種動態(tài)發(fā)現(xiàn)機(jī)制,可以在程序執(zhí)行的過程中去動態(tài)的發(fā)現(xiàn)某個(gè)擴(kuò)展點(diǎn)的實(shí)現(xiàn)類。因此,當(dāng) API 被調(diào)用時(shí)會動態(tài)加載并調(diào)用 SPI 的特定實(shí)現(xiàn)方法。
此時(shí),你是不是聯(lián)想到了模版方法模式。模板方法模式的核心思想是定義骨架,轉(zhuǎn)移實(shí)現(xiàn),換句話說,它通過定義一個(gè)流程的框架,而將一些步驟的具體實(shí)現(xiàn)延遲到子類中。事實(shí)上,在微服務(wù)的落地過程中,這種思想也給我們提供了非常好的理論基礎(chǔ)。
現(xiàn)在,我們來看一個(gè)案例:電商業(yè)務(wù)場景中的未發(fā)貨僅退款。這種情況在電商業(yè)務(wù)中非常場景,用戶下單付款后由于各種原因可能就申請退款了。此時(shí),因?yàn)椴簧婕巴素洠灾恍枰脩羯暾埻丝畈⑻顚懲丝钤颍缓笞屬u家審核退款。那么,由于不同平臺的退款原因可能不同,我們可以考慮通過 SPI 擴(kuò)展點(diǎn)來實(shí)現(xiàn)。
此外,我們還經(jīng)常使用工廠方法+策略模式來屏蔽外部的復(fù)雜性。例如,我們對外暴露一個(gè) API 接口?getTask(int operation),那么我們就可以通過工廠方法來創(chuàng)建實(shí)例,通過策略方法來定義不同的實(shí)現(xiàn)。
@Component public class TaskManager { private static final Logger logger = LoggerFactory.getLogger(TaskManager.class); private static TaskManager instance; public MapInteger, ITask> taskMap = new HashMap(); public static TaskManager getInstance() { return instance; } public ITask getTask(int operation) { return taskMap.get(operation); } /** * 初始化處理過程 */ @PostConstruct private void init() { logger.info("init task manager"); instance = new TaskManager(); // 單聊消息任務(wù) instance.taskMap.put(EventEnum.CHAT_REQ.getValue(), new ChatTask()); // 群聊消息任務(wù) instance.taskMap.put(EventEnum.GROUP_CHAT_REQ.getValue(), new GroupChatTask()); // 心跳任務(wù) instance.taskMap.put(EventEnum.HEART_BEAT_REQ.getValue(), new HeatBeatTask()); } }
還有一種屏蔽內(nèi)部復(fù)雜性設(shè)計(jì)就是外觀接口,它是將多個(gè)服務(wù)的接口進(jìn)行業(yè)務(wù)封裝與整合并提供一個(gè)簡單的調(diào)用接口給客戶端使用。這種設(shè)計(jì)的好處在于,客戶端不再需要知道那么多服務(wù)的接口,只需要調(diào)用這個(gè)外觀接口即可。但是,壞處也是顯而易見的,即增加了服務(wù)端的業(yè)務(wù)復(fù)雜度,接口性能不高,并且復(fù)用性不高。因此,因地制宜,盡可能保證職責(zé)單一,而在客戶端進(jìn)行“樂高式”組裝。如果存在 SEO 優(yōu)化的產(chǎn)品,需要被類似于百度這樣的搜索引擎收錄,可以當(dāng)首屏的時(shí)候,通過服務(wù)端渲染生成 HTML,使之讓搜索引擎收錄,若不是首屏的時(shí)候,可以通過客戶端調(diào)用服務(wù)端 RESTful API 接口進(jìn)行頁面渲染。
此外,隨著微服務(wù)的普及,我們的服務(wù)越來越多,許多較小的服務(wù)有更多的跨服務(wù)調(diào)用。因此,微服務(wù)體系結(jié)構(gòu)使得這個(gè)問題更加普遍。為了解決這個(gè)問題,我們可以考慮引入一個(gè)“聚合服務(wù)”,它是一個(gè)組合服務(wù),可以將多個(gè)微服務(wù)的數(shù)據(jù)進(jìn)行組合。這樣設(shè)計(jì)的好處在于,通過一個(gè)“聚合服務(wù)”將一些信息整合完后再返回給調(diào)用方。注意的是,“聚合服務(wù)”也可以有自己的緩存和數(shù)據(jù)庫。 事實(shí)上,聚合服務(wù)的思想無處不在,例如?Serverless 架構(gòu)。我們可以在實(shí)踐的過程中采用 AWS Lambda 作為 Serverless 服務(wù)背后的計(jì)算引擎,而 AWS Lambda 是一種函數(shù)即服務(wù)(Function-as-a-Servcie,F(xiàn)aaS)的計(jì)算服務(wù),我們直接編寫運(yùn)行在云上的函數(shù)。那么,這個(gè)函數(shù)可以組裝現(xiàn)有能力做服務(wù)聚合。
當(dāng)然,還有很多很好的設(shè)計(jì),我也會在陸續(xù)在公眾號中以續(xù)補(bǔ)的方式進(jìn)行補(bǔ)充與探討。
五、考慮背后的性能我們需要考慮入?yún)⒆侄蔚母鞣N組合導(dǎo)致數(shù)據(jù)庫的性能問題。有的時(shí)候,我們可能暴露太多字段給外部組合使用,導(dǎo)致數(shù)據(jù)庫沒有相應(yīng)的索引而發(fā)生全表掃描。事實(shí)上,這種情況在查詢的場景特別常見。因此,我們可以只提供存在索引的字段組合給外部調(diào)用,或者在下面的案例中,要求調(diào)用方必填 taskId 和 caseId 來保證我們數(shù)據(jù)庫合理使用索引,進(jìn)一步保證服務(wù)提供方的服務(wù)性能。
ResultVoid> agree(Long taskId, Long caseId, Configger configger);
同時(shí),對于報(bào)表操作、批量操作、冷數(shù)據(jù)查詢等 API 應(yīng)該可以考慮異步能力。
此外,GraphQL 雖然解決將多個(gè) HTTP 請求聚合成了一個(gè)請求,但是 schema 會逐層解析方式遞歸獲取全部數(shù)據(jù)。例如分頁查詢的統(tǒng)計(jì)總條數(shù),原本 1 次可以搞定的查詢,演變成了 N + 1 次對數(shù)據(jù)庫查詢。此外,如果寫得不合理還會導(dǎo)致惡劣的性能問題,因此,我們在設(shè)計(jì)的過程中特別需要注意。
六、異常響應(yīng)與錯(cuò)誤機(jī)制業(yè)內(nèi)對 RPC API 拋出異常,還是拋出錯(cuò)誤碼已經(jīng)有太多的爭論。《阿里巴巴 Java 開發(fā)手冊》建議:跨應(yīng)用 RPC 調(diào)用優(yōu)先考慮使用 isSuccess() 方法、“錯(cuò)誤碼”、“錯(cuò)誤簡短信息”。關(guān)于 RPC 方法返回方式使用 Result 方式的理由 : 1)使用拋異常返回方式,調(diào)用方如果沒有捕獲到,就會產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。2)如果不加棧信息,只是 new 自定義異常,加入自己的理解的 error message,對于調(diào)用端解決問題的幫助不會太多。如果加了棧信息,在頻繁調(diào)用出錯(cuò)的情況下,數(shù)據(jù)序列化和傳輸?shù)男阅軗p耗也是問題。當(dāng)然,我也支持這個(gè)論點(diǎn)的實(shí)踐擁護(hù)者。
public ResultXxxDTO> getXxx(String param) { try { // ... return Result.create(xxxDTO); } catch (BizException e) { log.error("...", e); return Result.createErrorResult(e.getErrorCode(), e.getErrorInfo(), true); } }
在 Web API 設(shè)計(jì)過程中,我們會使用?ControllerAdvice 統(tǒng)一包裝錯(cuò)誤信息。而在微服務(wù)復(fù)雜的鏈?zhǔn)秸{(diào)用中,我們會比單體架構(gòu)更難以追蹤與定位問題。因此,在設(shè)計(jì)的時(shí)候,需要特別注意。一種比較好的方案是,當(dāng) RESTful API 接口出現(xiàn)非 2xx 的 HTTP 錯(cuò)誤碼響應(yīng)時(shí),采用全局的異常結(jié)構(gòu)響應(yīng)信息。其中,code 字段用來表示某類錯(cuò)誤的錯(cuò)誤碼,在微服務(wù)中應(yīng)該加上“{biz_name}/”前綴以便于定位錯(cuò)誤發(fā)生在哪個(gè)業(yè)務(wù)系統(tǒng)上。我們來看一個(gè)案例,假設(shè)“用戶中心”某個(gè)接口沒有權(quán)限獲取資源而出現(xiàn)錯(cuò)誤,我們的業(yè)務(wù)系統(tǒng)可以響應(yīng)“UC/AUTH_DENIED”,并且通過自動生成的 UUID 值的 request_id 字段,在日志系統(tǒng)中獲得錯(cuò)誤的詳細(xì)信息。
HTTP/1.1 400 Bad Request Content-Type: application/json { "code": "INVALID_ARGUMENT", "message": "{error message}", "cause": "{cause message}", "request_id": "01234567-89ab-cdef-0123-456789abcdef", "host_id": "{server identity}", "server_time": "2014-01-01T12:00:00Z" }七、思考 API 的冪等性
冪等機(jī)制的核心是保證資源唯一性,例如客戶端重復(fù)提交或服務(wù)端的多次重試只會產(chǎn)生一份結(jié)果。支付場景、退款場景,涉及金錢的交易不能出現(xiàn)多次扣款等問題。事實(shí)上,查詢接口用于獲取資源,因?yàn)樗皇遣樵償?shù)據(jù)而不會影響到資源的變化,因此不管調(diào)用多少次接口,資源都不會改變,所以是它是冪等的。而新增接口是非冪等的,因?yàn)檎{(diào)用接口多次,它都將會產(chǎn)生資源的變化。因此,我們需要在出現(xiàn)重復(fù)提交時(shí)進(jìn)行冪等處理。那么,如何保證冪等機(jī)制呢?事實(shí)上,我們有很多實(shí)現(xiàn)方案。其中,一種方案就是常見的創(chuàng)建唯一索引。在數(shù)據(jù)庫中針對我們需要約束的資源字段創(chuàng)建唯一索引,可以防止插入重復(fù)的數(shù)據(jù)。但是,遇到分庫分表的情況是,唯一索引也就不那么好使了,此時(shí),我們可以先查詢一次數(shù)據(jù)庫,然后判斷是否約束的資源字段存在重復(fù),沒有的重復(fù)時(shí)再進(jìn)行插入操作。注意的是,為了避免并發(fā)場景,我們可以通過鎖機(jī)制,例如悲觀鎖與樂觀鎖保證數(shù)據(jù)的唯一性。這里,分布式鎖是一種經(jīng)常使用的方案,它通常情況下是一種悲觀鎖的實(shí)現(xiàn)。但是,很多人經(jīng)常把悲觀鎖、樂觀鎖、分布式鎖當(dāng)作冪等機(jī)制的解決方案,這個(gè)是不正確的。除此之外,我們還可以引入狀態(tài)機(jī),通過狀態(tài)機(jī)進(jìn)行狀態(tài)的約束以及狀態(tài)跳轉(zhuǎn),確保同一個(gè)業(yè)務(wù)的流程化執(zhí)行,從而實(shí)現(xiàn)數(shù)據(jù)冪等。事實(shí)上,并不是所有的接口都要保證冪等,換句話說,是否需要冪等機(jī)制可以通過考量需不需要確保資源唯一性,例如行為日志可以不考慮冪等性。當(dāng)然,還有一種設(shè)計(jì)方案是接口不考慮冪等機(jī)制,而是在業(yè)務(wù)實(shí)現(xiàn)的時(shí)候通過業(yè)務(wù)層面來保證,例如允許存在多份數(shù)據(jù),但是在業(yè)務(wù)處理的時(shí)候獲取最新的版本進(jìn)行處理。
(完,轉(zhuǎn)載請注明作者及出處。)
寫在末尾【服務(wù)端思維】:我們一起聊聊服務(wù)端核心技術(shù),探討一線互聯(lián)網(wǎng)的項(xiàng)目架構(gòu)與實(shí)戰(zhàn)經(jīng)驗(yàn)。同時(shí),擁有眾多技術(shù)大牛的「后端圈」大家庭,期待你的加入,一群同頻者,一起成長,一起精進(jìn),打破認(rèn)知的局限性。
更多精彩文章,盡在「服務(wù)端思維」!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74520.html
摘要:通常情況下,偽都是基于第一層次與第二層次設(shè)計(jì)的。為了解決這個(gè)版本不兼容問題,在設(shè)計(jì)的一種實(shí)用的做法是使用版本號。例如,建議第三位版本號通常表示兼容升級,只有不兼容時(shí)才需要變更服務(wù)版本。 原文地址:梁桂釗的博客 博客地址:blog.720ui.com 歡迎關(guān)注公眾號:「服務(wù)端思維」。一群同頻者,一起成長,一起精進(jìn),打破認(rèn)知的局限性。 有一段時(shí)間沒怎么寫文章了,今天提筆寫一篇自己對 API 設(shè)...
摘要:我們知道是一種從服務(wù)器公開數(shù)據(jù)的流行方式。描述所有的可能類型系統(tǒng)基于類型和字段的方式進(jìn)行組織,而非入口端點(diǎn)。因此,需要對后端進(jìn)行調(diào)整,以滿足新的數(shù)據(jù)需求,這會降低生產(chǎn)力并顯著降低將用戶反饋集成到產(chǎn)品中的能力。 showImg(https://segmentfault.com/img/remote/1460000017875905?w=2234&h=974); 在前幾天的《StateOf...
摘要:我們知道是一種從服務(wù)器公開數(shù)據(jù)的流行方式。描述所有的可能類型系統(tǒng)基于類型和字段的方式進(jìn)行組織,而非入口端點(diǎn)。因此,需要對后端進(jìn)行調(diào)整,以滿足新的數(shù)據(jù)需求,這會降低生產(chǎn)力并顯著降低將用戶反饋集成到產(chǎn)品中的能力。 showImg(https://segmentfault.com/img/remote/1460000017875905?w=2234&h=974); 在前幾天的《StateOf...
摘要:我們知道是一種從服務(wù)器公開數(shù)據(jù)的流行方式。描述所有的可能類型系統(tǒng)基于類型和字段的方式進(jìn)行組織,而非入口端點(diǎn)。因此,需要對后端進(jìn)行調(diào)整,以滿足新的數(shù)據(jù)需求,這會降低生產(chǎn)力并顯著降低將用戶反饋集成到產(chǎn)品中的能力。 showImg(https://segmentfault.com/img/remote/1460000017875905?w=2234&h=974); 在前幾天的《StateOf...
摘要:我們知道是一種從服務(wù)器公開數(shù)據(jù)的流行方式。描述所有的可能類型系統(tǒng)基于類型和字段的方式進(jìn)行組織,而非入口端點(diǎn)。因此,需要對后端進(jìn)行調(diào)整,以滿足新的數(shù)據(jù)需求,這會降低生產(chǎn)力并顯著降低將用戶反饋集成到產(chǎn)品中的能力。 showImg(https://segmentfault.com/img/remote/1460000017875905?w=2234&h=974); 在前幾天的《StateOf...
閱讀 1951·2021-11-15 17:58
閱讀 2135·2021-10-19 11:45
閱讀 3495·2021-09-02 15:40
閱讀 2602·2021-07-25 10:50
閱讀 3750·2019-08-30 15:56
閱讀 3152·2019-08-30 12:44
閱讀 1034·2019-08-26 13:38
閱讀 1877·2019-08-23 18:29