日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

万字长文 - 解读功能开关 | IDCF

發布時間:2023/12/4 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 万字长文 - 解读功能开关 | IDCF 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文:https://martinfowler.com/articles/feature-toggles.html

作者:Pete Hodgson

譯者:冬哥

功能開關Feature Toggle(通常也稱為功能標志Feature Flag)是一種強大的技術,允許團隊在不更改代碼的情況下修改系統行為。包括各種使用類別,在實施和管理開關時考慮該方式非常重要。開關引入了復雜性。我們可以通過使用智能開關實現實踐和適當的工具來管理我們的開關配置來控制這種復雜性,但我們還應該致力于限制系統中開關的數量。

作者 Pete Hodgson是舊金山灣區的一名獨立軟件交付顧問。他擅長幫助初創工程團隊改進他們的工程實踐和技術架構。Pete 之前曾在 Thoughtworks 擔任過六年的顧問,領導其西海岸業務的技術實踐。他還曾在舊金山多家初創公司擔任技術主管。

內容

  • 一個開關的故事

  • 開關的類別

  • 管理不同類別的開關

  • 實施技術

  • 開關配置

  • 使用帶有特征開關的系統


  • “功能開關”是一組模式,可以幫助團隊快速但安全地向用戶提供新功能。在這篇關于功能開關的文章中,我們將從一個簡短的故事開始,展示功能開關有用的一些典型場景。然后我們將深入研究細節,涵蓋有助于團隊通過功能開關取得成功的特定模式和實踐。

    功能開關也稱為功能標志、功能位或功能翻轉器。這些都是同一組技術的同義詞。在本文中,我將交替使用功能開關和功能標志。

    一、一個開關的故事

    場景描繪:你隸屬于從事復雜城市規劃模擬游戲的多個團隊之一,你的團隊負責核心模擬引擎,你的任務是提高網絡渲染算法的效率。你知道這將需要對實施進行相當大的檢修,這將需要數周時間。同時,你團隊的其他成員將需要在代碼庫的相關領域繼續一些正在進行的工作。

    根據過去合并長期存在的分支的痛苦經歷,如果可能的話,你希望避免為這項工作打分支。相反,你決定整個團隊將繼續在主干上工作,但致力于網絡渲染算法改進的開發人員將使用功能開關來防止他們的工作影響團隊的其他成員或破壞代碼庫的穩定性。

    1.1 功能標志的誕生

    以下是研究算法的兩人引入的第一個變化:

    加入開關之前

    function reticulateSplines () {// 當前的實現在這里}

    這些示例都使用 JavaScript ES2015

    加入開關之后

    function reticulateSplines(){var useNewAlgorithm = false;// useNewAlgorithm = true; // UNCOMMENT IF YOU ARE WORKING ON THE NEW SR ALGORITHMif( useNewAlgorithm ){return enhancedSplineReticulation();}else{return oldFashionedSplineReticulation();}}function oldFashionedSplineReticulation(){// current implementation lives here}function enhancedSplineReticulation(){// TODO: implement better SR algorithm}function oldFashionedSplineReticulation () {// 當前的實現在這里}function enhancedSplineReticulation () {// TODO:實現更好的 SR 算法}

    這對已經將當前的算法實現移動到一個?oldFashionedSplineReticulation?函數中,并將?reticulateSplines設置為一個開關點。現在,如果有人正在研究新算法,他們可以通過取消注釋來啟用“使用新算法” 功能useNewAlgorithm = true?。

    1.2 使標志動態化

    幾個小時過去了,這對搭檔準備好通過一些模擬引擎的集成測試來運行他們的新算法。他們還想在同一集成測試運行中使用舊算法。他們需要能夠動態地啟用或禁用該功能,這意味著是時候擺脫注釋或取消注釋該useNewAlgorithm = true?行的笨拙機制了:

    function reticulateSplines () {if ( featureIsEnabled( "use-new-SR-algorithm" ) ){return enhancedSplineReticulation();} else {return oldFashionedSplineReticulation();} }

    我們現在介紹了一個featureIsEnabled功能,一個開關路由器,它可以用來動態控制哪個代碼路徑是活動的。實現開關路由器的方法有很多,從簡單的內存存儲到具有精美 UI 的高度復雜的分布式系統。

    現在我們將從一個非常簡單的系統開始:

    function createToggleRouter(featureConfig){return {setFeature(featureName,isEnabled){featureConfig[featureName] = isEnabled;},featureIsEnabled(featureName){return featureConfig[featureName];}}; }

    請注意,我們使用的是 ES2015 的方法簡寫

    我們可以基于一些默認配置創建一個新的開關路由器——也許從配置文件中讀取——但我們也可以動態地打開或關閉一個功能。這允許自動化測試來驗證開關功能的兩側:

    describe( 'spline reticulation', function(){let toggleRouter;let simulationEngine;beforeEach(function(){toggleRouter = createToggleRouter();simulationEngine = createSimulationEngine({toggleRouter:toggleRouter});});it('works correctly with old algorithm', function(){// GiventoggleRouter.setFeature("use-new-SR-algorithm",false);// Whenconst result = simulationEngine.doSomethingWhichInvolvesSplineReticulation();// ThenverifySplineReticulation(result);});it('works correctly with new algorithm', function(){// GiventoggleRouter.setFeature("use-new-SR-algorithm",true);// Whenconst result = simulationEngine.doSomethingWhichInvolvesSplineReticulation();// ThenverifySplineReticulation(result);}); });

    1.3 準備發布

    更多的時間過去了,團隊相信他們的新算法功能齊全。為了確認這一點,他們一直在修改他們的更高級別的自動化測試,以便他們在功能關閉和打開的情況下運行系統。該團隊還希望進行一些手動探索性測試,以確保一切按預期工作——畢竟,樣條網狀結構是系統行為的關鍵部分。

    要對尚未被驗證為可用于一般用途的功能執行手動測試,我們需要能夠為生產中的一般用戶群關閉該功能,但能夠為內部用戶打開它。有很多不同的方法可以實現這個目標:

    • 讓開關路由器根據開關設置做出決策,并針對特定環境進行配置。僅在預生產環境中啟用新功能。

    • 允許通過某種形式的管理 UI 在運行時修改開關配置。使用該管理 UI 在測試環境中啟用新功能。

    • 教開關路由器如何根據請求做出動態的開關決策。這些決策將開關上下文考慮在內,例如通過查找特殊的 cookie 或 HTTP 標頭。通常開關上下文用作識別發出請求的用戶的代理。

    (稍后我們將更詳細地研究這些方法,所以如果其中一些概念對你來說是新的,請不要擔心。)

    團隊決定使用按請求開關路由器,因為它為他們提供了很大的靈活性。該團隊特別感激,這將使他們能夠在不需要單獨的測試環境的情況下測試他們的新算法。相反,他們可以在生產環境中簡單地打開算法,但僅限于內部用戶(通過特殊 cookie 檢測到)。團隊現在可以自己打開該 cookie 并驗證新功能是否按預期執行。

    1.4 金絲雀發布

    基于迄今為止所做的探索性測試,新的網絡渲染算法看起來不錯。然而,由于它是游戲模擬引擎的重要組成部分,因此仍然有些人不愿意為所有用戶啟用此功能。團隊決定使用他們的功能開關基礎設施來執行金絲雀發布,只為他們總用戶群的一小部分——“金絲雀”群組開啟新功能。

    該團隊通過向開關路由器傳授用戶群組的概念來增強開關路由器 - 用戶群組始終體驗始終開啟或關閉的功能。一組金絲雀用戶是通過 1% 的用戶群的隨機抽樣創建的——可能使用用戶 ID 的模數。這個金絲雀隊列將始終啟用該功能,而其他 99% 的用戶群仍使用舊算法。監控兩組的關鍵業務指標(用戶參與度、總收入等),以確保新算法不會對用戶行為產生負面影響。一旦團隊確信新功能沒有不良影響,他們就會修改他們的開關配置,以便為整個用戶群啟用它。

    1.5 A/B 測試

    團隊的產品經理了解到這種方法,非常興奮。她建議團隊使用類似的機制來執行一些 A/B 測試。關于修改犯罪率算法以考慮污染水平是否會增加或降低游戲的可玩性,一直存在長期爭論。他們現在有能力使用數據來解決爭論。他們計劃推出一個廉價的實現,它可以捕捉到這個想法的本質,并通過一個功能標志來控制。他們將為相當多的用戶群啟用該功能,然后研究這些用戶與“控制”群相比的行為方式。這種方法將允許團隊根據數據解決有爭議的產品辯論,

    這個簡短的場景旨在說明功能開關的基本概念,同時也強調該核心功能可以有多少不同的應用程序。現在我們已經看到了這些應用程序的一些示例,讓我們更深入地研究一下。我們將探索不同類別的開關,看看是什么讓它們與眾不同。我們將介紹如何編寫可維護的開關代碼,最后分享實踐以避免功能開關系統的一些陷阱。

    二、開關的類別

    我們已經看到了功能開關提供的基本功能 - 能夠在一個可部署單元中提供替代代碼路徑并在運行時在它們之間進行選擇。上述場景還表明,該工具可以在各種情況下以各種方式使用。將所有功能開關集中到同一個桶中可能很誘人,但這是一條危險的道路。不同類別開關的設計驅動是完全不同的,并且以相同的方式管理它們可能會導致痛苦。

    功能開關可以分為兩個主要維度:功能開關將存在多長時間,以及開關決策必須有多動態。還有其他因素需要考慮——例如,誰來管理功能開關——但我認為生存周期和動態性是兩個重要因素,可以幫助指導如何管理開關。

    讓我們通過這兩個維度的視角考慮各種類別的開關,看看它們適合什么。

    2.1 發布開關

    發布開關允許將不完整和未經測試的代碼路徑作為可能永遠不會打開的潛在代碼交付到生產環境。

    這些是用于為實踐持續交付的團隊啟用基于主干的開發的功能標志。它們允許將正在進行的功能檢入到共享集成分支(例如 master 或trunk)中,同時仍然允許隨時將該分支部署到生產中。發布開關允許將不完整和未經測試的代碼路徑作為可能永遠不會打開的潛在代碼交付到生產環境。

    產品經理也可以使用同樣方法的以產品為中心的版本來防止半成品的產品功能暴露給最終用戶。例如,電子商務網站的產品經理可能不想讓用戶看到僅適用于網站的一物流合作伙伴的新預計物流日期功能,而是希望等到該功能已為所有物流合作伙伴實施。產品經理可能有其他原因不想公開功能,即使它們已經完全實現和測試。例如,功能發布可能與營銷活動相協調。以這種方式使用發布開關是實現“將 [功能] 發布與 [代碼] 部署分開”的持續交付原則的最常見方式。

    發布開關本質上是過渡的。盡管以產品為中心的開關可能需要保留更長的時間,但它們通常不應停留超過一兩周。發布開關的開關決定通常是非常靜態的。給定發布版本的每個開關決策都是相同的,通過推出具有開關配置更改的新版本來更改開關決策通常是完全可以接受的。

    2.2 實驗開關

    實驗開關用于執行多變量或 A/B 測試。系統的每個用戶都被放置在一個群組中,并且在運行時,開關路由器將根據他們所在的群組始終如一地將給定的用戶發送到一個或另一個代碼路徑。通過跟蹤不同群組的聚合行為,我們可以比較效果不同的代碼路徑。這種技術通常用于對電子商務系統的購買流程或按鈕上的宣傳性用語等事物進行數據驅動的優化。

    實驗開關需要在相同配置下保持足夠長的時間以產生具有統計意義的結果。取決于可能意味著生命周期數小時或數周的流量模式。更長的時間不太可能有用,因為對系統的其他更改可能會使實驗結果無效。就其性質而言,實驗開關是高度動態的 - 每個傳入請求都可能代表不同的用戶,因此可能會以不同于上一個的方式路由。

    2.3 運維開關

    這些標志用于控制我們系統行為的操作方面。我們可能會在推出具有不確定性能影響的新功能時引入運維開關,以便系統操作員可以在需要時在生產中快速禁用或降級該功能。

    大多數運維開關的壽命都相對較短——一旦對新功能的操作方面獲得信心,就應該停用該標志。然而,系統擁有少量長壽命的“終止開關”并不少見,它們允許生產環境的操作員在系統承受異常高負載時優雅地降低非重要系統功能。

    例如,當我們處于繁重的負載下時,我們可能希望禁用我們主頁上的推薦面板,該面板的生成成本相對較高。我咨詢了一家維護運維開關的在線零售商,這可能會在高需求產品發布之前故意禁用其網站主要采購流程中的許多非關鍵功能。這種長期存在的運維開關可以視作是一種手工管理的斷路器。

    如前所述,這些標志中的許多只存在很短的時間,但一些關鍵控件可能幾乎無限期地保留給操作員。由于這些標志的目的是讓操作員對生產問題做出快速反應,因此他們需要非??焖俚刂匦屡渲?- 需要推出新版本以翻轉運維開關,不太可能讓操作人員滿意。

    2.4 許可開關

    這些標志用于更改某些用戶收到的功能或產品體驗。例如,我們可能有一組“高級”功能,只為付費客戶啟用。或者,也許我們有一組僅供內部用戶使用的“alpha”功能和另一組僅供內部用戶和 beta 用戶使用的“beta”功能。我將這種為一組內部或測試版用戶打開新功能的技術稱為香檳早午餐 - 一個“喝自己的香檳”的早期機會。

    香檳早午餐在很多方面與金絲雀發布相似。兩者之間的區別在于,金絲雀發布的功能面向隨機選擇的一組用戶,而香檳早午餐功能面向一組特定用戶。

    當用作管理僅向高級用戶公開的功能時,與其他類別的功能開關相比,許可開關的壽命可能非常長 - 以多年為規模。由于權限是特定于用戶的,因此許可開關的開關決定將始終按請求進行,因此這是一個非常動態的開關。

    三、管理不同類別的開關

    現在我們有了一個開關分類方案,我們可以討論動態性和壽命這兩個維度如何影響我們使用不同類別的特征標志的方式。

    3.1 靜態與動態開關

    做出運行時路由決策的開關必然需要更復雜的開關路由器,以及這些路由器的更復雜配置。

    對于簡單的靜態路由決策,開關配置可以是每個功能的簡單開或關,開關路由器僅負責將靜態開/關狀態中繼到開關點。正如我們之前所討論的,其他類別的開關更加動態并且需要更復雜的開關路由器。例如,實驗開關的路由器為給定用戶動態地做出路由決策,可能使用某種基于該用戶 id 的一致群組算法。這個開關路由器不需要從配置中讀取靜態開關狀態,而是需要讀取某種隊列配置,定義諸如實驗隊列和控制隊列應該有多大。

    稍后我們將深入探討管理此開關配置的不同方法。

    3.2 長期開關與瞬態開關

    我們還可以將開關類別劃分為本質上是短暫的類別與長期存在且可能存在多年的類別。這種區別應該對我們實現功能的開關點的方法有很大的影響。如果我們要添加一個將在幾天后刪除的發布開關,那么我們可能會使用一個開關點,它對 開關路由器進行簡單的 if/else 檢查。這就是我們之前使用樣條網格示例所做的:

    function reticulateSplines () {if ( featureIsEnabled( "use-new-SR-algorithm" ) ){return enhancedSplineReticulation();} else {return oldFashionedSplineReticulation();} }

    但是,如果我們要創建一個帶有開關點的新許可開關,我們希望它會持續很長時間,那么我們當然不希望通過不加選擇地散布 if/else 檢查來實現這些開關點。我們需要使用更易于維護的實現技術。

    四、實施技術

    功能標志似乎會產生相當混亂的開關點代碼,并且這些開關點也有在整個代碼庫中擴散的趨勢。保持這種趨勢檢查代碼庫中的任何功能標志很重要,如果標志將長期存在,這一點至關重要。有一些實現模式和實踐有助于減少這個問題。

    4.1 將決策點與決策邏輯解耦

    功能開關的一個常見錯誤是將做出開關決策的位置(開關點)與決策背后的邏輯(開關路由器)結合起來。

    讓我們看一個例子。我們正在開發下一代電子商務系統。我們的一項新功能將允許用戶通過單擊訂單確認電子郵件(又稱發票電子郵件)中的鏈接輕松取消訂單。我們正在使用功能標志來管理我們所有下一代功能的推出。我們最初的特征標記實現如下所示:

    invoiceEmailer.jsconst features = fetchFeatureTogglesFromSomewhere();function generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);if( features.isEnabled("next-gen-ecomm") ){ return addOrderCancellationContentToEmail(baseEmail);}else{return baseEmail;}}

    在生成發票電子郵件時,我們的 InvoiceEmailler 會檢查該next-gen-ecomm功能是否已啟用。如果是,那么電子郵件發送者會在電子郵件中添加一些額外的訂單取消內容。

    雖然這看起來是一種合理的方法,但它非常脆弱。關于是否在我們的發票電子郵件中包含訂單取消功能的決定直接與next-gen-ecomm功能綁定 - 使用魔術字符串。為什么發票電子郵件代碼需要知道訂單取消內容是下一代功能集的一部分?如果我們想在不暴露訂單取消的情況下打開下一代功能的某些部分會怎樣?或反之亦然?如果我們決定只向某些用戶推出訂單取消功能怎么辦?隨著功能的開發,這種“開關范圍”更改很常見。還要記住,這些開關點往往會在整個代碼庫中激增。?

    令人高興的是,軟件中的任何問題都可以通過添加一個間接層來解決。我們可以將開關決策點與該決策背后的邏輯解耦,如下所示:

    featureDecisions.jsfunction createFeatureDecisions(features){return {includeOrderCancellationInEmail(){return features.isEnabled("next-gen-ecomm");}// ... additional decision functions also live here ...};} invoiceEmailer.jsconst features = fetchFeatureTogglesFromSomewhere();const featureDecisions = createFeatureDecisions(features);function generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);if( featureDecisions.includeOrderCancellationInEmail() ){return addOrderCancellationContentToEmail(baseEmail);}else{return baseEmail;}}

    我們引入了一個FeatureDecisions對象,它充當任何功能開關決策邏輯的收集點。我們為代碼中的每個特定開關決策在此對象上創建一個決策方法 - 在這種情況下,“我們是否應該在發票電子郵件中包含訂單取消功能”由?includeOrderCancellationInEmail決策方法表示。

    現在,決策“邏輯”是微不足道的通過傳遞next-gen-ecomm檢查狀態,但現在隨著邏輯的發展,我們有一個統一的地方來管理它。每當我們想修改特定開關決策的邏輯時,我們只需要去一個地方。

    我們可能想要修改決策的范圍——例如哪個特定的功能標志控制決策?;蛘?#xff0c;我們可能需要修改做出決定的原因——從由靜態開關配置驅動到由 A/B 實驗驅動,或者由操作問題驅動,例如我們的一些訂單取消基礎設施的中斷。在所有情況下,我們的發票電子郵件發送者都可能會很高興地不知道如何或為什么做出該開關決定。

    4.2 反轉決定

    在前面的示例中,我們的發票電子郵件發送器負責詢問功能標記基礎設施應該如何執行。這意味著我們的發票電子郵件發送器有一個需要注意的額外概念 - 功能標記 - 以及與之耦合的額外模塊。這使得發票電子郵件更難單獨使用和思考,包括使其更難測試。隨著功能標記在系統中變得越來越普遍,我們將看到越來越多的模塊作為全局依賴項與功能標記系統耦合。不是理想的情況。

    在軟件設計中,我們通??梢酝ㄟ^應用控制反轉來解決這些耦合問題。在這種情況下確實如此。以下是我們如何將我們的發票電子郵件與我們的功能標記基礎設施分離:

    invoiceEmailer.jsfunction createInvoiceEmailler(config){return {generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);if( config.includeOrderCancellationInEmail ){return addOrderCancellationContentToEmail(email);}else{return baseEmail;}},// ... other invoice emailer methods ...};} featureAwareFactory.jsfunction createFeatureAwareFactoryBasedOn(featureDecisions){return {invoiceEmailler(){return createInvoiceEmailler({includeOrderCancellationInEmail: featureDecisions.includeOrderCancellationInEmail()});},// ... other factory methods ...};}

    現在,不是我們InvoiceEmailler接觸它,而是在構建時通過一個對象?FeatureDecisions將這些決定注入它?,F在對功能標記一無所知。它只知道可以在運行時配置其行為的某些方面。這也使得 testing的行為更容易 - 我們可以通過在測試期間傳遞不同的配置選項來測試它生成帶有和不帶有訂單取消內容的電子郵件的方式:configInvoiceEmaillerInvoiceEmailler

    describe( 'invoice emailling', function(){it( 'includes order cancellation content when configured to do so', function(){// Given const emailler = createInvoiceEmailler({includeOrderCancellationInEmail:true});// Whenconst email = emailler.generateInvoiceEmail();// ThenverifyEmailContainsOrderCancellationContent(email);};it( 'does not includes order cancellation content when configured to not do so', function(){// Given const emailler = createInvoiceEmailler({includeOrderCancellationInEmail:false});// Whenconst email = emailler.generateInvoiceEmail();// ThenverifyEmailDoesNotContainOrderCancellationContent(email);}; });

    我們還引入了一個FeatureAwareFactory集中創建這些決策注入對象的方法。這是一般依賴注入模式的應用。如果控制反轉系統在我們的代碼庫中發揮作用,那么我們可能會使用該系統來實現這種方法。

    4.3 避免條件

    到目前為止,在我們的示例中,我們的開關點是使用 if 語句實現的。這對于一個簡單的、短暫的開關可能是有意義的。但是,在某個功能需要多個開關點或你希望開關點長期存在的地方,不建議使用點條件。一種更易于維護的替代方法是使用某種策略模式來實現替代代碼路徑:

    invoiceEmailler.jsfunction createInvoiceEmailler(additionalContentEnhancer){return {generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);return additionalContentEnhancer(baseEmail);},// ... other invoice emailer methods ...};} featureAwareFactory.jsfunction identityFn(x){ return x; }function createFeatureAwareFactoryBasedOn(featureDecisions){return {invoiceEmailler(){if( featureDecisions.includeOrderCancellationInEmail() ){return createInvoiceEmailler(addOrderCancellationContentToEmail);}else{return createInvoiceEmailler(identityFn);}},// ... other factory methods ...};}

    在這里,我們通過允許我們的發票電子郵件程序配置內容增強功能來應用策略模式。FeatureAwareFactory在創建發票電子郵件時選擇一種策略,由FeatureDecision. 如果訂單取消應該在電子郵件中,它會傳遞一個增強功能,將內容添加到電子郵件中。否則,它會傳入一個identityFn增強器——它沒有任何效果,只是簡單地將電子郵件傳回而不做任何修改。

    五、開關配置

    5.1 動態路由與動態配置

    早些時候,我們將功能開關分為對于給定的代碼部署而言開關路由決策基本上是靜態的,與那些決策在運行時動態變化的。需要注意的是,標志的決定可能會在運行時以兩種方式發生變化,這一點很重要。

    • 首先,像運維開關這樣的東西可能會被動態重新配置從 On 到 Off 以響應系統中斷。

    • 其次,某些類別的開關(例如許可開關和實驗開關)根據某些請求上下文(例如哪個用戶發出請求)為每個請求做出動態路由決策。

    前者是通過重新配置動態實現的,而后者則在本質上就是動態的。這些固有的動態開關可能會做出高度動態的決策,但仍然具有配置動作,這是相當靜態的,也許只能通過重新部署來改變。實驗開關是此類功能標志的一個示例——我們實際上并不需要能夠在運行時修改實驗的參數。事實上,這樣做可能會使實驗在統計上無效。

    5.2 首選靜態配置

    如果特性標志的性質允許的話,最好通過源代碼控制和重新部署來管理開關配置。通過源代碼控制管理開關配置給我們帶來的好處,與我們通過將源代碼控制用于基礎設施即代碼之類的東西所獲得的好處相同。

    它可以允許開關配置與正在開關的代碼庫一起存在,這提供了一個非常大的好處:開關配置將以與代碼更改或基礎架構更改完全相同的方式在你的持續交付流水線中移動。這可以充分發揮 CD 的優勢 - 可重復的構建,這些構建以一致的方式跨環境進行驗證。它還大大減少了功能標志的測試負擔。很少需要驗證版本將如何通過開關關閉和打開來執行,因為該狀態已被打包到版本中并且不會更改(至少對于較少動態的標志)。開關配置在源代碼控制中并行存在的另一個好處是,我們可以輕松查看以前版本中開關的狀態,并在需要時輕松重新創建以前的版本。

    5.3 管理開關配置的方法

    雖然靜態配置更可取,但在某些情況下,例如運維開關,需要更動態的方法。讓我們看一下用于管理開關配置的一些選項,從簡單但動態性較低的方法到一些高度復雜但具有許多額外復雜性的方法。

    5.4 硬編碼開關配置

    最基本的技術——也許基本到不被認為是功能標志——是簡單地注釋或取消注釋代碼塊。例如:

    function reticulateSplines(){//return oldFashionedSplineReticulation();return enhancedSplineReticulation(); }

    比注釋方法稍微復雜一點的是使用預處理器的#ifdef功能,如果可用的話。?

    因為這種類型的硬編碼不允許動態重新配置開關,它僅適用于我們愿意遵循部署代碼模式以重新配置標志的功能標志。

    5.5 參數化開關配置

    硬編碼配置提供的構建時配置對于許多用例(包括許多測試場景)來說不夠靈活。至少允許在不重新構建應用程序或服務的情況下,重新配置功能標志的簡單方法,是通過命令行參數或環境變量指定開關配置。這是一種簡單且歷史悠久的開關方法,早在有人將該技術稱為功能開關或特征標記之前就已經存在。但是,它有局限性??绱罅窟M程協調配置可能會變得不方便,而且開關配置的更改需要重新部署或者至少重新啟動進程(并且可能需要重新配置開關的人對服務器進行特權訪問)。

    5.6 開關配置文件

    另一種選擇是從某種結構化文件中讀取開關配置。這種開關配置的方法很常見,它作為更通用的應用程序配置文件的一部分開始使用。

    使用開關配置文件,你現在可以通過簡單地更改該文件而不是重新構建應用程序代碼本身來重新配置功能標志。但是,盡管在大多數情況下你不需要重新構建應用程序來開關功能,但你可能仍需要執行重新部署以重新配置標志。

    5.7 開關應用數據庫中的配置

    一旦達到一定規模,使用靜態文件來管理開關配置可能會變得很麻煩。通過文件修改配置相對繁瑣。確保一組服務器的一致性成為一項挑戰,使更改始終如一地更是如此。作為對此的回應,許多組織將開關配置轉移到某種類型的集中式存儲中,通常是現有的應用程序數據庫。這通常伴隨著某種形式的管理 UI 的構建,它允許系統操作員、測試人員和產品經理查看和修改功能標志及其配置。

    5.8 分布式開關配置

    使用已經是系統架構一部分的通用數據庫來存儲開關配置非常普遍;一旦引入功能標志并開始獲得牽引力,這是一個明顯的方式。然而,現在有一種特殊用途的分層鍵值存儲更適合管理應用程序配置 - 像 Zookeeper、etcd 或 Consul 這樣的服務。這些服務形成一個分布式集群,它為連接到集群的所有節點提供環境配置的共享源??梢栽谛枰獣r動態修改配置,并且集群中的所有節點都會自動收到更改通知——這是一個非常方便的附加功能。

    其中一些系統(例如 Consul)帶有一個管理 UI,它提供了一種管理開關配置的基本方法。然而,在某些時候,通常會創建一個用于管理開關配置的小型自定義應用程序。

    5.9 覆蓋配置

    到目前為止,我們的討論假設所有配置都由單一機制提供。許多系統的實際情況更為復雜,配置的覆蓋層來自各種來源。使用開關配置,具有默認配置以及特定于環境的覆蓋是很常見的。這些覆蓋可能來自像附加配置文件這樣簡單的東西,也可能來自像 Zookeeper 集群這樣復雜的東西。

    請注意,任何特定于環境的覆蓋都與持續交付的理想背道而馳,即在交付管道中始終擁有完全相同的位和配置流。通常實用主義要求使用一些特定于環境的覆蓋,但是努力使你的可部署單元和你的配置盡可能與環境無關,這將導致更簡單、更安全的管道。當我們談論測試功能開關系統時,我們將很快重新討論這個主題。

    • 針對每個請求覆蓋

    環境特定配置覆蓋的另一種方法是允許通過特殊 cookie、查詢參數或 HTTP 標頭在每個請求的基礎上覆蓋開關的開/關狀態。與完整的配置覆蓋相比,這有一些優勢。如果服務是負載平衡的,你仍然可以確信無論你點擊哪個服務實例,都會應用覆蓋。你還可以在生產環境中覆蓋功能標志而不影響其他用戶,并且你不太可能意外地留下覆蓋。

    這種按請求方法的缺點是它引入了一種風險,即好奇或惡意的最終用戶可能會自己修改功能開關狀態。一些組織可能對某些未發布的功能可能對足夠堅定的一方公開訪問的想法感到不舒服。對覆蓋配置進行加密簽名是緩解這種擔憂的一種選擇,但無論如何這種方法都會增加功能開關系統的復雜性和攻擊面。

    六、使用帶有特征標記的系統

    雖然功能開關絕對是一種有用的技術,但它也帶來了額外的復雜性。在使用帶有特征標記的系統時,有一些技術可以幫助簡化你的生活。

    6.1 公開當前功能開關配置

    將構建/版本號嵌入到已部署的工件中并在某處公開該元數據一直是一種有用的做法,以便開發人員、測試人員或操作員可以找出在給定環境中運行的特定代碼。相同的想法應該應用于功能標志。任何使用功能標志的系統都應該為操作員提供某種方式來發現開關配置的當前狀態。在面向 HTTP 的 SOA 系統中,這通常是通過某種元數據 API 端點或端點來完成的。參見例如 Spring Boot 的 Actuator endpoints。

    6.2 利用結構化的開關配置文件

    通常將基本開關配置存儲在某種結構化的、人類可讀的文件(通常為 YAML 格式)中,通過源代碼控制進行管理,我們可以從此類文件中獲得額外的好處。為每個開關包含人類可讀的描述非常有用,特別是對于由核心交付團隊以外的人管理的開關。

    在嘗試決定是否在生產中斷事件期間啟用運維開關時,你希望看到什么:basic-rec-algo,還是“使用簡單的推薦算法。這很快并且在后端系統上產生的負載更少,但更少比我們的標準算法準確。”? 一些團隊還選擇在他們的開關配置文件中包含額外的元數據,例如創建日期、主要開發人員聯系人,甚至是短期開關的到期日期。

    6.3 以不同方式管理不同的開關

    如前所述,具有不同特征的功能開關有多種類別。應該接受這些差異,并以不同的方式管理不同的開關,即使所有不同的開關都可以使用相同的技術機器進行控制。

    讓我們回顧一下我們之前的電子商務網站示例,該網站在主頁上有一個推薦產品部分。最初,我們可能會在開發時將該部分放在發布開關后面。然后,我們可能會將其移至實驗開關的背后,以驗證它是否有助于增加收入。最后,我們可能會將它移到運維開關 后面,以便在我們處于極端負載下時可以將其關閉。如果我們遵循先前關于從開關點中解耦決策邏輯的建議,那么開關類別中的這些差異應該對開關點代碼沒有任何影響。

    然而,從功能標志管理的角度來看,這些轉換絕對應該產生影響。作為從 發布開關過渡到實驗開關的一部分,配置開關的方式會發生變化,并且可能會移動到不同的區域 - 可能會進入管理 UI 而不是源代碼管理中的 yaml 文件。產品人員現在可能會管理配置而不是開發人員。同樣,從實驗開關過渡到運維開關 將意味著開關的配置方式、配置所在的位置以及誰管理配置的另一個變化。

    6.4 功能開關引入驗證復雜性

    使用帶有功能標記的系統,我們的持續交付過程變得更加復雜,尤其是在測試方面。當同一個工件通過 CD 管道時,我們經常需要測試多個代碼路徑。為了說明原因,假設我們正在交付一個系統,該系統可以在啟用開關時使用新的優化稅收計算算法,或者繼續使用我們現有的算法。在給定的可部署工件正在通過我們的 CD 管道移動時,我們無法知道開關是否會在生產中的某個時候打開或關閉 - 畢竟這就是功能標志的全部意義所在。因此,為了驗證可能最終在生產中運行的所有代碼路徑,我們必須在兩種狀態:開關打開并關閉。

    我們可以看到,通過一個單一的開關,這引入了至少在我們的一些測試中加倍的要求。隨著多個開關的發揮,我們有可能開關狀態的組合爆炸。驗證每個狀態的行為將是一項艱巨的任務。這可能會導致以測試為重點的人們對功能標志產生一些健康的懷疑。

    令人高興的是,情況并沒有一些測試人員最初想象的那么糟糕。雖然帶有功能標記的候選版本確實需要使用一些開關配置進行測試,但沒有必要測試“每個”可能的組合。大多數功能標志不會相互交互,并且大多數版本不會涉及對多個功能標志的配置進行更改。

    一個好的約定是在功能標志關閉時啟用現有的或舊有的行為,而在它打開時啟用新的或未來的行為。

    那么,團隊應該測試哪些功能開關配置?測試你希望在生產中生效的開關配置是最重要的,這意味著當前的生產開關配置加上你打算發布的所有開關都已打開。測試回退配置也是明智之舉,你打算釋放的那些開關也會被關閉。為了避免在未來的版本中出現任何意外的回歸,許多團隊還會在所有開關都打開的情況下執行一些測試。請注意,僅當你堅持開關語義的約定時,此建議才有意義,其中在功能關閉時啟用現有或舊有行為,而在功能開啟時啟用新行為或未來行為。

    如果你的功能標志系統不支持運行時配置,那么你可能必須重新啟動你正在測試的進程才能觸發開關,或者更糟糕的是,將工件重新部署到測試環境中。這會對驗證過程的周期時間產生非常不利的影響,進而影響 CI/CD 提供的所有重要反饋循環。為避免此問題,請考慮公開一個端點,該端點允許對功能標志進行動態內存重新配置。當你使用諸如實驗開關之類的東西時,這些類型的覆蓋變得更加必要,在這種情況下,使用開關的兩個路徑更加繁瑣。

    這種動態重新配置特定服務實例的能力是一個非常鋒利的工具。如果使用不當,可能會在共享環境中造成很多痛苦和混亂。這個工具應該只被自動化測試使用,并且可能作為手動探索性測試和調試的一部分。如果需要在生產環境中使用更通用的開關控制機制,最好使用真正的分布式配置系統構建,如上面開關配置部分所述。

    6.5 在哪里放置你的開關

    • 在邊緣開關

    對于需要每個請求上下文的開關類別(實驗開關、許可開關),將開關點放置在系統的邊緣服務中是有意義的——即向最終用戶展示功能的公開 Web 應用程序。這是你的用戶的個人請求首先進入你的域的地方,因此你的開關路由器有最多的上下文可用于根據用戶及其請求做出開關決策。將開關點放置在系統邊緣的一個附帶好處是,它可以將繁瑣的條件開關邏輯排除在系統核心之外。在許多情況下,你可以將開關點放置在你正在呈現 HTML 的位置,如以下 Rails 示例所示:

    someFile.erb<%= if featureDecisions.showRecommendationsSection? %><%= render 'recommendations_section' %><% end %>

    當你控制對尚未準備好發布的面向用戶的新功能的訪問時,將開關點放置在邊緣也很有意義。在這種情況下,你可以再次使用簡單地顯示或隱藏 UI 元素的開關來控制訪問。例如,也許你正在構建使用 Facebook 登錄應用程序的功能,但還沒有準備好將其推廣給用戶。此功能的實現可能涉及架構各個部分的更改,但你可以通過隱藏“使用 Facebook 登錄”按鈕的 UI 層的簡單功能開關來控制功能的公開。有趣的是,使用其中一些類型的功能標志,大部分未發布的功能本身可能實際上是公開的,但位于用戶無法發現的 url 上。

    • 在核心開關

    還有其他類型的較低級別的開關必須放置在你的架構中更深的位置。這些開關通常在本質上是技術性的,并且控制著某些功能如何在內部實現。一個示例是發布開關,它控制是在第三方 API 前使用新的緩存基礎設施,還是直接將請求路由到該 API。在這些情況下,在功能被開關的服務中本地化這些開關決策是唯一明智的選擇。

    6.6 管理功能開關的持有成本

    功能標志有快速增加的趨勢,特別是在首次引入時。它們有用且創建成本低,因此通常會創建很多。然而,開關確實會帶來持有成本。它們要求你在代碼中引入新的抽象或條件邏輯。它們還引入了顯著的測試負擔。騎士資本集團的4.6 億美元錯誤是一個警示故事,說明當你沒有正確管理功能標志時(除其他外)會出現什么問題。

    精明的團隊將他們的功能開關視為帶有持有成本的庫存,并努力將庫存保持在盡可能低的水平。

    精明的團隊將其代碼庫中的功能開關視為帶有持有成本的庫存,并尋求將庫存保持在盡可能低的水平。為了使功能標志的數量保持可管理,團隊必須主動刪除不再需要的功能標志。一些團隊的規則是,每當首次引入發布開關時,總是將開關刪除任務添加到團隊的待辦事項中。其他團隊將“到期日期”放在他們的開關按鈕上。有些人甚至會制造“定時炸彈”,如果功能標志在其到期日期之后仍然存在,它將無法通過測試(甚至拒絕啟動應用程序!)。

    我們還可以采用精益方法來減少庫存,對系統在任何時候允許擁有的功能標志的數量進行限制。一旦達到該限制,如果有人想要添加新的開關,他們首先需要完成刪除現有開關標識的工作。

    總結

    以上是生活随笔為你收集整理的万字长文 - 解读功能开关 | IDCF的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。