戰(zhàn)指南:主版本模塊隔離與 Accept 頭次版本協(xié)商)
Yii 2 REST API 版本化實(shí)戰(zhàn)指南主版本模塊隔離與 Accept 頭次版本協(xié)商【免費(fèi)下載鏈接】yii2Yii 2: The Fast, Secure and Professional PHP Framework項(xiàng)目地址: https://gitcode.com/gh_mirrors/yi/yii2本指南以 Yii 2 官方文檔《REST 版本化》對應(yīng)倉庫 docs/guide-ru/rest-versioning.md英文原版見 docs/guide/rest-versioning.md為核心骨架結(jié)合框架源碼展開。REST API 面向不受你控制的客戶端因此版本化是保障向后兼容Backward Compatibility簡稱 BC的必修課。讀完本文你將掌握 Yii 2 推薦的雙層版本化策略用獨(dú)立模塊承載每個主版本如v1、v2用Accept請求頭協(xié)商次版本并學(xué)會如何在控制器、資源模型與序列化器中讀取版本信息編寫條件邏輯。為什么 API 必須版本化Yii 官方文檔開宗明義地指出好的 API 必須是版本化的——新功能與變更應(yīng)當(dāng)通過新的 API 版本引入而不是在同一個版本上不斷改動。原因在于Web 應(yīng)用的后端與前端代碼都在你的掌控之中可以隨時同步升級而 API 的消費(fèi)方是你無法控制的客戶端第三方應(yīng)用、移動端、外部集成方它們可能長期停留在舊版本上。因此API 的向后兼容性應(yīng)當(dāng)盡可能保持。如果必須做出破壞兼容性的變更正確做法是把它放進(jìn)一個新版本的 API中并提升版本號。既有客戶端可以繼續(xù)使用舊版本新客戶端則切換到新版本獲取新能力。設(shè)計(jì)版本號時可以參考業(yè)界通行的語義化版本Semantic Versioning即 Major.Minor.Patch 三段式思路來規(guī)劃主版本、次版本與補(bǔ)丁版本的語義邊界。兩種主流版本化方式的對比文檔梳理了業(yè)界最常見的兩種 API 版本化實(shí)現(xiàn)方式它們各有擁躉也各有取舍。方式一URL 路徑內(nèi)嵌版本號將版本號直接嵌入調(diào)用 URL例如https://example.com/v1/users即請求 API 版本 1 的/users端點(diǎn)。這種方式直觀、易調(diào)試、便于緩存與日志分析是歷史最悠久也最普及的做法。方式二HTTP 請求頭攜帶版本號把版本號放進(jìn) HTTP 請求頭通常是Accept頭常見有兩種寫法// 作為參數(shù)傳遞 Accept: application/json; versionv1 // 作為供應(yīng)商自定義內(nèi)容類型vendor content type Accept: application/vnd.company.myapp-v1json第一種通過Accept頭的參數(shù)versionv1聲明版本第二種則把版本號編入 MIME 類型形如application/vnd.廠商.應(yīng)用-v版本格式由 API 供應(yīng)商自定義。這種方式保持了 URL 的純凈但調(diào)試與緩存配置相對復(fù)雜。兩種方式都有各自的優(yōu)缺點(diǎn)社區(qū)對此爭論已久。Yii 官方文檔給出的結(jié)論是不要二選一而是取二者之長采用一種混合策略。Yii 2 推薦的混合版本化策略Yii 2 官方推薦的實(shí)踐是把兩種方式組合為兩層主版本Major放進(jìn) URL通過模塊隔離將每個主版本的 API 實(shí)現(xiàn)放進(jìn)一個獨(dú)立模塊中模塊 ID 就是主版本號例如v1、v2。這樣 API 的 URL 天然包含主版本號。次版本Minor放進(jìn)Accept頭通過條件代碼響應(yīng)在每個主版本模塊內(nèi)部用Accept請求頭確定次版本號并針對不同次版本編寫條件邏輯。這個分層思路的關(guān)鍵在于主版本的隔離是物理級的獨(dú)立目錄、獨(dú)立類因?yàn)橹靼姹局g允許破壞性變更而次版本必須保持 BC因此差異往往只是一些字段或行為上的細(xì)微條件判斷用Accept頭協(xié)商即可不需要復(fù)制整套代碼。按主版本組織代碼模塊隔離的目錄結(jié)構(gòu)針對每個服務(wù)主版本的模塊模塊內(nèi)應(yīng)包含服務(wù)于該版本的資源類Resource與控制器類。為了更好的職責(zé)分離文檔建議維護(hù)一套公共的基類然后在每個版本模塊內(nèi)對其子類化在子類中實(shí)現(xiàn)該版本特有的具體代碼例如覆寫Model::fields()來定制輸出字段。推薦的代碼組織方式如下api/ common/ controllers/ UserController.php PostController.php models/ User.php Post.php modules/ v1/ controllers/ UserController.php PostController.php models/ User.php Post.php Module.php v2/ controllers/ UserController.php PostController.php models/ User.php Post.php Module.php要點(diǎn)解讀common/存放跨版本共享的基類如UserController、User的基礎(chǔ)實(shí)現(xiàn)modules/v1/與modules/v2/分別存放兩個主版本的控制器與資源模型子類每個版本模塊擁有自己的Module.php入口類由于主版本之間代碼物理隔離v1 的破壞性改動不會影響 v2同時通過公共基類仍能跨模塊復(fù)用邏輯。關(guān)于fields()方法它是 Yii 資源模型輸出字段的開關(guān)framework/base/ArrayableTrait.phpframework/base/ArrayableTrait.php中默認(rèn)實(shí)現(xiàn)返回所有公共對象成員變量而framework/base/Model.phpframework/base/Model.php同樣聲明了該方法。在版本子類中覆寫它即可讓 v2 輸出比 v1 更多的字段或改名后的字段而不影響 v1 的既有輸出——這正是版本化資源模型的典型用法。應(yīng)用配置注冊版本模塊與 REST 路由規(guī)則有了目錄結(jié)構(gòu)還需要在應(yīng)用配置中注冊模塊并配置urlManager的路由規(guī)則才能讓https://example.com/v1/users這類 URL 真正路由到對應(yīng)模塊。文檔給出的完整配置如下return [ modules [ v1 [ class app\modules\v1\Module, ], v2 [ class app\modules\v2\Module, ], ], components [ urlManager [ enablePrettyUrl true, enableStrictParsing true, showScriptName false, rules [ [class yii\rest\UrlRule, controller [v1/user, v1/post]], [class yii\rest\UrlRule, controller [v2/user, v2/post]], ], ], ], ];關(guān)鍵點(diǎn)拆解modules段聲明v1、v2兩個模塊class指向各自的Module類模塊 ID 即 URL 中的主版本段。enablePrettyUrl與enableStrictParsing啟用美化 URL 與嚴(yán)格解析保證/v1/users形式的路由能被正確解析而不是落到index.php查詢串。yii\rest\UrlRule這是 REST 路由的核心。規(guī)則中的controller數(shù)組[v1/user, v1/post]中控制器 ID 以模塊 ID 為前綴v1/這正是framework/rest/UrlRule.phpframework/rest/UrlRule.php所要求的寫法——其文檔注釋明確說明控制器位于模塊內(nèi)時ID 必須以模塊 ID 作為前綴。UrlRule 底層機(jī)制自動生成整組 REST 路由從源碼看UrlRule繼承自CompositeUrlRule并內(nèi)置了一套 REST 端點(diǎn)模式$patterns屬性見 framework/rest/UrlRule.phpHTTP 動詞模式路由動作PUT, PATCH{id}update更新DELETE{id}delete刪除GET, HEAD{id}view查看詳情POST無create創(chuàng)建GET, HEAD無index列表任意{id}options預(yù)檢任意無options預(yù)檢同時它還具備兩項(xiàng)重要行為自動復(fù)數(shù)化$pluralize默認(rèn)trueinit()中通過Inflector::pluralize()把user變?yōu)閡sers、post變?yōu)閜osts見 framework/rest/UrlRule.php所以 URL 呈現(xiàn)為復(fù)數(shù)名詞按動詞拆分規(guī)則createRule()會解析模式中的 HTTP 動詞前綴為每個動作生成獨(dú)立的yii\web\UrlRule實(shí)例并綁定對應(yīng)的路由如v1/user/index。因此上述配置最終產(chǎn)生的效果正如文檔所言https://example.com/v1/users返回版本 1 的用戶列表https://example.com/v2/users返回版本 2 的用戶列表對應(yīng)地POST /v1/users創(chuàng)建、GET /v1/users/123查看詳情等 REST 語義端點(diǎn)也一并生效。得益于模塊機(jī)制不同主版本的代碼得以良好隔離同時通過公共基類與其他共享類模塊間依然可以復(fù)用代碼。倉庫中的單元測試 tests/framework/rest/UrlRuleTest.php 對 UrlRule 的路由生成與匹配行為有系統(tǒng)性覆蓋可作為深入理解其行為的參考。次版本處理ContentNegotiator 與 acceptParams主版本交給模塊隔離后次版本的處理依賴 Yii 的內(nèi)容協(xié)商Content Negotiation能力——即yii\filters\ContentNegotiator行為源碼見 framework/filters/ContentNegotiator.php。它有兩種掛載方式作為引導(dǎo)組件bootstrap作用于整個應(yīng)用或作為行為behavior掛在控制器/模塊上。在 REST 場景中yii\rest\Controller已經(jīng)在behaviors()里默認(rèn)配置了contentNegotiator見 framework/rest/Controller.php支持application/json與application/xml兩種格式。ContentNegotiator在判定支持的響應(yīng)格式時會順帶解析Accept頭中的參數(shù)并把它們寫入響應(yīng)對象的屬性。相關(guān)屬性定義在 framework/web/Response.php$acceptMimeType從請求Accept頭選中的 MIME 類型$acceptParams與該 MIME 類型關(guān)聯(lián)的參數(shù)名值對數(shù)組例如[q 1, version 1.0]。結(jié)合源碼中的negotiateContentType()framework/filters/ContentNegotiator.php可以還原完整流程若配置了formatParam默認(rèn)_format且請求攜帶該 GET 參數(shù)則直接以該參數(shù)決定格式否則遍歷$request-getAcceptableContentTypes()在formats中查找匹配的 MIME 類型命中后設(shè)置$response-format、$response-acceptMimeType $type并把解析出的參數(shù)如versionv1賦給$response-acceptParams。于是文檔中的示例得到了源碼層面的印證如果請求攜帶Accept: application/json; versionv1內(nèi)容協(xié)商完成后yii\web\Response::acceptParams將包含[version v1]。在業(yè)務(wù)代碼中消費(fèi)版本信息拿到acceptParams后就可以在動作action、資源類resource class、序列化器serializer等位置編寫條件代碼。例如在控制器動作中use Yii; public function actionIndex() { $params Yii::$app-response-acceptParams; $version $params[version] ?? v1; // 針對不同次版本返回不同字段或行為 // ... }若需要定制響應(yīng)序列化方式可以覆寫yii\rest\Serializerframework/rest/Serializer.php的serialize()在其中讀取acceptParams按版本輸出不同的數(shù)組結(jié)構(gòu)資源模型層面則可以像上文所述通過覆寫fields()實(shí)現(xiàn)字段差異。這三個落點(diǎn)動作、資源類、序列化器也正是文檔明確列舉的條件代碼寫入位置。版本檢查的度何時該開新的主版本文檔最后給出了一條重要的工程經(jīng)驗(yàn)次版本按定義必須保持向后兼容因此期望你的代碼中版本號檢查不會太多。如果發(fā)現(xiàn)代碼里充斥著大量版本判斷分支那么大概率意味著這些變更實(shí)際上已經(jīng)破壞了 BC——此時正確的做法不是繼續(xù)在acceptParams里堆條件而是開啟一個新的主版本模塊。這條建議背后是成本考量次版本協(xié)商只需要寫少量條件代碼維護(hù)成本低但條件分支過多會讓代碼難以閱讀和維護(hù)。主版本模塊隔離雖然成本更高需要復(fù)制/子類化控制器與資源類卻換來了清晰的邊界與絕對的兼容保障。在少量條件判斷與新開主版本之間文檔給出的取舍標(biāo)準(zhǔn)就是檢查過多就該升級主版本了。延伸閱讀REST 路由規(guī)則詳解docs/guide/rest-routing.md俄文版 docs/guide-ru/rest-routing.mdREST 控制器與動作體系docs/guide/rest-controllers.mdREST 資源與字段定制docs/guide/rest-resources.md響應(yīng)格式協(xié)商與序列化docs/guide/rest-response-formatting.md核心實(shí)現(xiàn)UrlRuleframework/rest/UrlRule.php、ContentNegotiatorframework/filters/ContentNegotiator.php、Response::acceptParamsframework/web/Response.php【免費(fèi)下載鏈接】yii2Yii 2: The Fast, Secure and Professional PHP Framework項(xiàng)目地址: https://gitcode.com/gh_mirrors/yi/yii2創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考