才云开源 Nirvana:Golang REST API框架
自 2009 年開(kāi)源以來(lái),Go 作為一種強(qiáng)大、高效、簡(jiǎn)潔、易上手的編程語(yǔ)言,在幫助閱讀、調(diào)試和維護(hù)大型軟件系統(tǒng)上發(fā)揮著越來(lái)越重要的作用。而依托其健康生態(tài),Golang 社區(qū)也相繼涌現(xiàn)出諸如 beego、gin、chi、go-restful 等知名框架,為 Go 提供額外功能支持。
但選擇過(guò)多,反受其亂。面對(duì)層出不窮的優(yōu)秀框架,不同團(tuán)隊(duì)、不同開(kāi)發(fā)者在框架選擇上往往會(huì)出現(xiàn)分歧,不同框架之間也彼此壁壘高筑,導(dǎo)致業(yè)務(wù)與框架耦合,開(kāi)發(fā)效率大大降低。
為了解決這類問(wèn)題,才云 Caicloud 實(shí)現(xiàn)了 Golang API 框架 Nirvana,把 API 從對(duì)框架的依賴中徹底解放出來(lái)。它專為提高生產(chǎn)力和可用性設(shè)計(jì),可擴(kuò)展、性能高,旨在成為才云 Caicloud 所有 Golang 服務(wù)的基石,助力業(yè)務(wù)的高速開(kāi)發(fā)。
接軌 Kubernetes 這些痛點(diǎn)不可忽視
近年來(lái),為了滿足業(yè)務(wù)發(fā)展需要,應(yīng)對(duì)日益激烈的市場(chǎng)競(jìng)爭(zhēng),大量企業(yè)開(kāi)始使用容器部署云工作負(fù)載。而隨著容器使用量的增長(zhǎng)、Kubernetes 成為容器編排的事實(shí)標(biāo)準(zhǔn),在 Kubernetes 上打造新一代容器云平臺(tái)成了企業(yè)謀求發(fā)展的必由之路。
為了順應(yīng)時(shí)局,技術(shù)團(tuán)隊(duì)除了進(jìn)行思維上的轉(zhuǎn)變,還要應(yīng)對(duì)團(tuán)隊(duì)和業(yè)務(wù)方向的調(diào)整,對(duì)團(tuán)隊(duì)項(xiàng)目和產(chǎn)品完成切割分化。這之中就涉及對(duì) Go 框架變更的抉擇。
兩年前,才云 Caicloud 團(tuán)隊(duì)在構(gòu)建基于 Kubernetes 的云平臺(tái)時(shí),在框架上遇到了不少麻煩:當(dāng)時(shí) Kubernetes 正值發(fā)展期,為了快速開(kāi)發(fā),工程師們往往傾向于選擇自己熟悉的框架。因此,雖然大多數(shù)項(xiàng)目用的是 go-restful(Kubernetes 的選型),但用 beego、gin、chi 等框架的工程師也不在少數(shù),這就給業(yè)務(wù)整合帶來(lái)了很多問(wèn)題:
- 不同框架對(duì) API 的描述形式不一致,這增加了不同團(tuán)隊(duì)間的溝通成本;
- 不同框架 API 風(fēng)格差距較大,文檔自動(dòng)化困難;
- 錯(cuò)誤定義和處理方式在不同項(xiàng)目之間存在差別,會(huì)導(dǎo)致客戶端處理困難。
曾經(jīng)開(kāi)放的工程師文化成了變革的最大阻力。
Nirvana 的緣起
Nirvana 就是在這個(gè)背景下誕生的。
它借鑒了 Kubernetes 聲明式 API 的設(shè)計(jì),巧妙規(guī)避了框架對(duì) API 的侵入。同時(shí),為了實(shí)現(xiàn)業(yè)務(wù)和框架的隔離,它定義一個(gè)規(guī)范,讓 API 按照規(guī)范書(shū)寫(xiě),完全屏蔽了框架對(duì) API 的影響。
一言以蔽之,Nirvana 框架的設(shè)計(jì)思路始終圍繞工程師們親歷的種種痛點(diǎn):
- 構(gòu)建風(fēng)格一致的 API 項(xiàng)目;
- 使用統(tǒng)一的 RESTful 路由與聲明式 API 定義,避免業(yè)務(wù)對(duì)框架產(chǎn)生依賴;
- 使用統(tǒng)一的錯(cuò)誤生成和處理方式;
- 提供開(kāi)箱即用的基礎(chǔ)功能,包括 Prometheus metrics、tracing、profiling 等;
- 自動(dòng)生成 API 文檔和客戶端代碼。
在 Nirvana 中,我們用一套 API Definition 來(lái)聲明式地描述業(yè)務(wù) API。下面是一個(gè)列出消息列表 API 的例子:
Definition{ // 這個(gè) API 返回的是資源數(shù)組,所以使用 List 方法。 Method: def.List, // Summary 是一個(gè)短語(yǔ),用于描述這個(gè) API 的用途。這個(gè)短語(yǔ)在生成文檔和客戶端的時(shí)候用于區(qū)分 API。 // 這個(gè)字符串去掉空格后會(huì)作為生成客戶端時(shí)的函數(shù)名,因此請(qǐng)確保這個(gè)字符串是有意義的。 Summary: \u0026quot;List Messages\u0026quot;, // 詳細(xì)描述這個(gè) API 的用途。 Description: \u0026quot;Query a specified number of messages and returns an array\u0026quot;, // 業(yè)務(wù)函數(shù) Function: message.ListMessages, // 對(duì)應(yīng)業(yè)務(wù)函數(shù)的參數(shù)信息。用于告知 Nirvana 從請(qǐng)求的那一部分取得數(shù)據(jù),然后傳遞給業(yè)務(wù)函數(shù)。 Parameters: []def.Parameter{ { // 參數(shù)來(lái)源 Source: def.Query, // 參數(shù)名稱,作為 key 從 Source 里取值。 // 與業(yè)務(wù)函數(shù)的參數(shù)名稱無(wú)關(guān)。 Name: \u0026quot;count\u0026quot;, // 默認(rèn)值 Default: 10, // 參數(shù)描述 Description: \u0026quot;Number of messages\u0026quot;, }, }, // 對(duì)應(yīng)業(yè)務(wù)函數(shù)的返回結(jié)果。用于告知 Nirvana 業(yè)務(wù)函數(shù)返回結(jié)果如何放到請(qǐng)求的響應(yīng)中。 Results: def.DataErrorResults(\u0026quot;A list of messages\u0026quot;),}而對(duì)應(yīng)業(yè)務(wù) API 的形式則是:
type Message struct { ID int `json:\u0026quot;id\u0026quot;` Title string `json:\u0026quot;title\u0026quot;` Content string `json:\u0026quot;content\u0026quot;`}func ListMessages(ctx context.Context, count int) ([]Message, error) { messages := make([]Message, count) for i := 0; i \u0026lt; count; i++ { messages[i].ID = i messages[i].Title = fmt.Sprintf(\u0026quot;Example %d\u0026quot;, i) messages[i].Content = fmt.Sprintf(\u0026quot;Content of example %d\u0026quot;, i) } return messages, nil}很顯然,業(yè)務(wù)函數(shù)并不關(guān)心自身是如何暴露給外部的,實(shí)現(xiàn)方法也和其他內(nèi)部函數(shù)沒(méi)有差別(這只是一個(gè)簡(jiǎn)單的例子,更多詳細(xì)內(nèi)容請(qǐng)查閱 Nirvana Definition 文檔)。
在這個(gè)例子里,API Definition 描述了完整的 API 結(jié)構(gòu),包括 RESTful 路徑、請(qǐng)求方法、請(qǐng)求描述、請(qǐng)求參數(shù)、請(qǐng)求響應(yīng)和請(qǐng)求 handler。框架只需要解析 API Definition,就能得到業(yè)務(wù)邏輯的入口和出口處理方式。對(duì)開(kāi)發(fā)者而言,API 的開(kāi)發(fā)過(guò)程從“ 命令式路由 + 數(shù)據(jù)轉(zhuǎn)換 + 業(yè)務(wù)邏輯” 變成了“API Definition + 業(yè)務(wù)邏輯”??蚣芘c業(yè)務(wù)邏輯之間通過(guò) API Definition 進(jìn)行橋接。
Nirvana 框架
API Definition 作為聲明式 API,除了讓框架讀取信息生成路由和對(duì)外提供服務(wù),它本身也完整描述了一個(gè) API 的工作方式。因此,我們還能用它生成 openapi 文檔和客戶端。在接下來(lái)的幾個(gè)小節(jié)中,我們將詳述 Nirvana 提供的這些能力。
路由工作流
任何一個(gè) API 框架都具備基本的 HTTP Serve 能力( HTTP 接口服務(wù)能力)。在 Nirvana 中,HTTP Serve 由 Golang 基礎(chǔ)庫(kù) http 提供。HTTP 請(qǐng)求的路由方式如下圖所示:
在這個(gè)請(qǐng)求的工作流中:
- Filters 是請(qǐng)求過(guò)濾器,可以對(duì)不符合要求的請(qǐng)求進(jìn)行提前響應(yīng),而不會(huì)進(jìn)入路由匹配過(guò)程;
- Middlewares 是圍繞請(qǐng)求的中間件,能夠控制請(qǐng)求的處理行為;
- Executor 是最終處理請(qǐng)求的執(zhí)行器,負(fù)責(zé)通過(guò)參數(shù)生成器(ParameterGenerators)和結(jié)果處理器(DestinationHandlers)將請(qǐng)求注入到業(yè)務(wù)邏輯之中,并將返回結(jié)果處理后返回。
在這里,Filters、Middlewares、ParameterGenerators 和 DestinationHandlers 都是可以被開(kāi)發(fā)者重新定義的。這也意味著開(kāi)發(fā)者可以改變 Nirvana 的原生處理行為,使之符合自身需求。
服務(wù)構(gòu)建工作流
Nirvana 另一個(gè)工作流是將 API Definition 構(gòu)建到路由中去,并完成整個(gè)服務(wù)的啟動(dòng)工作。
在 Nirvana 啟動(dòng)時(shí),除 API Definition 之外,它還能夠通過(guò)插件往框架里注入一些功能,包括注冊(cè) Filters、Middlewares、添加其他路由 endpoint 等。目前 Nirvana 已經(jīng)提供對(duì) metrics、profiling、tracing、log 等常用插件的支持。
通過(guò)以上兩個(gè)工作流,以及 Nirvana 中大量的模塊方法注冊(cè)接口,開(kāi)發(fā)者可以自行擴(kuò)展 API Definition 的能力,讓 API Definition 與業(yè)務(wù)邏輯更加貼合。API Definition 單向依賴并描述了業(yè)務(wù)邏輯,并作為框架和業(yè)務(wù)之間的橋梁,既達(dá)到了聲明式 API 的定義形式,也滿足了業(yè)務(wù)不依賴框架的目標(biāo)。
框架擴(kuò)展
無(wú)論是路由還是服務(wù)構(gòu)建過(guò)程,Nirvana 的實(shí)現(xiàn)都不是 hardcode 的。在框架的大部分包里,你能發(fā)現(xiàn)類似以下形式的代碼結(jié)構(gòu):
var consumers = map[string]Consumer{ definition.MIMENone: \u0026amp;NoneSerializer{}, definition.MIMEText: NewSimpleSerializer(definition.MIMEText), definition.MIMEJSON: \u0026amp;JSONSerializer{}, definition.MIMEXML: \u0026amp;XMLSerializer{}, definition.MIMEOctetStream: NewSimpleSerializer(definition.MIMEOctetStream), definition.MIMEURLEncoded: \u0026amp;URLEncodedConsumer{}, definition.MIMEFormData: \u0026amp;FormDataConsumer{},}// RegisterConsumer register a consumer. A consumer must not handle \u0026quot;*/*\u0026quot;.func RegisterConsumer(c Consumer) error { if c.ContentType() == definition.MIMEAll { return invalidConsumer.Error(definition.MIMEAll) } consumers[c.ContentType()] = c return nil}在這種結(jié)構(gòu)里,開(kāi)發(fā)者可以自定義每一塊的實(shí)現(xiàn)方式,甚至替換框架默認(rèn)行為。幫助自己最大限度地利用 Nirvana,使它符合業(yè)務(wù)特定需求。
插件機(jī)制
在服務(wù)構(gòu)建工作流中,我們提到了插件機(jī)制。下面我們會(huì)介紹三個(gè)重要插件 metrics、profiling和tracing。
Prometheus 作為 CNCF 的開(kāi)源項(xiàng)目,為我們提供了非常適于描述業(yè)務(wù)指標(biāo)的格式:Prometheus Metrics。Nirvana 提供的 metrics 插件可以在啟動(dòng)時(shí)默認(rèn)暴露 /metrics 接口。對(duì)此,開(kāi)發(fā)者可以將業(yè)務(wù)指標(biāo)注冊(cè)到 Prometheus 中,通過(guò)這個(gè)接口讓 Prometheus 采集。也就是說(shuō),Nirvana 不僅能幫助開(kāi)發(fā)者了解業(yè)務(wù)的運(yùn)行狀態(tài),也為開(kāi)發(fā)者定位業(yè)務(wù)問(wèn)題提供了強(qiáng)有力的幫助。
Golang 提供了進(jìn)程級(jí)別的 profiling 能力。Nirvana 通過(guò) profiling 插件直接將這個(gè)能力注入到框架中。開(kāi)發(fā)者只需要啟用插件即可獲得這些調(diào)試和性能測(cè)試能力,獲得業(yè)務(wù)內(nèi)部執(zhí)行的狀態(tài)信息。
在 CloudNative 時(shí)代,業(yè)務(wù)通常會(huì)以微服務(wù)或 Serverless 的形式進(jìn)行架構(gòu)。多個(gè)服務(wù)之間的交互通常是黑盒的,這易導(dǎo)致我們難以定位分布式架構(gòu)中的服務(wù)問(wèn)題。Tracing 作為解決分布式架構(gòu)下的調(diào)用鏈方案,可以在分布式系統(tǒng)出現(xiàn)問(wèn)題時(shí)為開(kāi)發(fā)者提供大量的信息,幫助開(kāi)發(fā)者排查問(wèn)題。Nirvana 的 tracing 插件使用了 open-tracing 的接口規(guī)范,并借助 CNCF 開(kāi)源項(xiàng)目 jaeger 的能力,為開(kāi)發(fā)者提供一站式 tracing 方案。
文檔和客戶端生成
API Definition 和 Nirvana 的結(jié)合幫助我們完成了服務(wù)構(gòu)建。在服務(wù)之外,通過(guò) API Definition 的描述能力,Nirvana 還實(shí)現(xiàn)了基于 openapi 的文檔生成和客戶端生成。
Nirvana 的文檔生成基于 openapi 2.0(即 swagger 2.0)規(guī)范,從 API Definition 中提取 API 信息,生成 API 描述文件 swagger.json。除了生成 API 描述文件之外,它還能通過(guò) ReDoc 直接提供文檔服務(wù)(更多信息請(qǐng)查看文檔)。
在 Nirvana 中,生成文檔的方式十分簡(jiǎn)單:
$ nirvana api --serve=\u0026quot;:8081\u0026quot;
只需一條命令,API 文檔即可生成,并通過(guò) 8081 端口提供服務(wù)。之后我們就能通過(guò)網(wǎng)頁(yè)查看文檔了:
Nirvana 的客戶端生成同樣基于 API Definition ——讀取類型信息,生成客戶端包(目前僅支持生成 Golang 包)??蛻艨梢灾苯右眠@個(gè)包來(lái)調(diào)用服務(wù),整個(gè)客戶端的使用方式與本地調(diào)用一致(更多信息請(qǐng)查看文檔)。
下面是一鍵生成客戶端的示例:
$ nirvana client
這個(gè)命令會(huì)在當(dāng)前目錄下生成一個(gè) client 目錄,在這個(gè)目錄內(nèi)生成 Golang 客戶端代碼:
type Client struct { rest *rest.Client}// ListMessages returns all messages.func (c *Client) ListMessages(ctx context.Context, count int) (messages []Message, err error) { err = c.rest.Request(\u0026quot;GET\u0026quot;, 200, \u0026quot;/apis/v1/messages\u0026quot;). Query(\u0026quot;count\u0026quot;, count). Data(\u0026amp;messages). Do(ctx) return}如果您想進(jìn)一步了解 Nirvana,可以參考才云 Caicloud 云原生 CI/CD 項(xiàng)目 Cyclone。它包含一個(gè) API 組件 Cyclone Server, 基于 Nirvana 對(duì)外提供簡(jiǎn)單易用的 API。
Nirvana:涅槃
Nirvana,梵語(yǔ)中的涅槃。
一如我們對(duì)它的期待:讓 API 從對(duì)框架的依賴中涅槃重生。
經(jīng)過(guò)一年多的開(kāi)源,目前 Nirvana 在基礎(chǔ) API 服務(wù)能力上已經(jīng)經(jīng)過(guò)內(nèi)部驗(yàn)證。由于數(shù)據(jù)傳輸層和轉(zhuǎn)換層的復(fù)雜度被大大降低,才云 Caicloud 也切實(shí)體驗(yàn)到了這個(gè)開(kāi)源框架帶來(lái)的便捷,以及它在加速業(yè)務(wù)開(kāi)發(fā)上的顯著效果。
未來(lái),Nirvana 將在以下幾方面繼續(xù)發(fā)力:
- 持續(xù)優(yōu)化擴(kuò)展文檔和客戶端生成的能力,降低開(kāi)發(fā)者在這兩塊上的心智負(fù)擔(dān);
- 持續(xù)優(yōu)化 metrics、profiling、tracing 的能力,并增加新的云原生能力,讓這些能力成為云原生應(yīng)用的標(biāo)配;
- 框架模塊化加強(qiáng),讓 Nirvana 的每一塊代碼皆可定制;
- 優(yōu)化框架性能,降低反射對(duì)服務(wù)的影響;
- 讓 Nirvana 成為 Golang 的 CloudNative \u0026amp; SOA 框架。
Nirvana 正致力于成為讓業(yè)務(wù)無(wú)感知的框架。目前,才云 Caicloud 正在這條路上踽踽獨(dú)行,我們也真誠(chéng)地希望將來(lái)能有更多開(kāi)發(fā)者愿意加入進(jìn)來(lái),一起構(gòu)建 Nirvana,一起建設(shè) Golang 社區(qū),一起擁抱開(kāi)源的勝利。
也許你的加入,能讓這場(chǎng)涅槃更加炫目!
感謝參與開(kāi)源本項(xiàng)目的所有開(kāi)發(fā)者!
Nirvana GitHub:https://github.com/caicloud/nirvana
Nirvana 文檔:https://caicloud.github.io/nirvana/zh-hans/
總結(jié)
以上是生活随笔為你收集整理的才云开源 Nirvana:Golang REST API框架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: SAP Customer Data Cl
- 下一篇: logback的简单使用