在得到了GoogleMapService這個抽象層之后,就可以實現新的GoogleMapV3Service,并且使用它替換原有的GoogleMapV2Service。最后如果有必要的話再將接口去除掉。
這時候就可以使用上面提到的方法,在IOC中使用功能開關基礎設施來控制SearchLocationService 使用的到底是V2的還是V3的服務。具體的代碼如下:
type="com.service.GoogleMapService"
code="feature.googleV3Service.isActivated">
其中,ns:runtime-conditionl利用了Spring的自定義命名空間技術來侵入到bean的裝配過程中。整個Tag其實是定義了一個名為googleMapService的bean。在這個自定義Tag的實現中,它同樣會去讀取功能開關 (feature.googleV3Service.isActivated)的狀態,然后根據這個狀態來把正確的bean賦給 googleMapService這個bean。別人只需要直接使用googleMapService這個bean就可以了。使用這種方式,我們就可以輕易而安全的在兩個bean之間切換功能。
功能開關如何支持持續集成
上面兩個例子給大家展示了在一個Spring應用程序中如何使用功能開關。通過這種方式我們所有的代碼,不管有沒有完全做完,都存在與一個分支上了,從而也就解決了上面所提到的各種與合并相關的問題。同時還可以方便的通過改變配置文件的方式來更改應用程序的狀態,如果在線上發現了問題,也可以快速的關閉該功能。
但是這里面有個問題,在某次應用程序啟動的時候,某功能的配置項的值必須是確定的,要么是開啟,要么是關閉。那么在持續集成服務器上跑自動化驗收測試的時候到底應該如何設置配置項的值呢?
假設我下次發布期望該功能是關閉的,那么我每次跑測試的時候就讓該功能關閉,這樣可以保證本次發布是安全的。但是在開關開的時候才生效的那些功能是沒有辦法被自動化測試覆蓋的。如果該功能出了問題,持續集成服務器也沒法發現。那么在未來準備發布該功能的時候,我們還要再打開開關,做一些自動或者手動的測試,出現bug(很有可能,因為持續集成服務器并沒有保護到我這個功能不被破壞)了再集中修復。這樣還是沒能完全解決功能分支所帶來的問題。
為了解決這個問題,我們引入了兩個CI Job:Regression和Progression
針對開關開的時候寫一系列的自動化測試,使用加tag的方式標記它們為@feature_on。
當開關打開的時候,因為系統行為變化了,所以之前存在的某些相關的自動化測試就沒法再通過,我們把這些因為開關打開而失敗的測試標記為@feature_off,意為只有該開關關掉的時候這些測試才有效。
創建一個叫做Regression Tests的CI Job,即上圖黃色的那個圈。其包含了加@feature_off tag的測試和沒有tag的測試。啟動應用程序的時候把功能開關關閉,然后跑這個圈里面所有的測試。
創建一個叫做Progression Tests的CI Job,即上圖灰色的那個圈。其包含了加@feature_on tag的測試,同樣也包含了沒有tag的測試。啟動應用程序的時候把功能開關打開,然后跑這個圈里面所有的測試。
每次代碼提交都會觸發Regression和Progression兩個Job的構建。
使用這種方式,我們可以保證應用程序的不同狀態在每次提交都是被測試到的,如果有任何問題,可以第一時間發現、修復。增強了對應用程序正確性的信心,也為在產品環境切換開關狀態提供了足夠的信心。
功能開關的陷阱
上面我們提到的是只有一個功能開關的情況,那么如果有兩個,會是什么情形呢?假設兩個功能分別叫A,B。那么理論上來說我們應該測試到A_on, B_on; A_on, B_off, A_off, B_on; A_off, B_off這四種情況。也就是說需要有四個CI Jobs與之對應,如果有3個功能就需要8個Jobs。這顯然是不可接受的。
這里就涉及到使用功能開關的一個很重要的原則,功能開關之間不要有依賴。也就是說不管別的開關狀態如何,只要A的開關是開的,那么所有的 @featureA_on的測試就一定可以通過,所有@featureA_off的測試就一定不能通過。如果保證每個功能都是相互獨立的,我們就不需要測試所有的組合,還是只需要測試兩種就可以了。比如這次發布需要的狀態時A_on, B_off,那么在Regression中就按照這個狀態起應用程序,并跑相關的測試;在Progression中按照A_off, B_on的狀態起應用程序,并跑相關的測試。
因此需要限制項目中功能開關的數量,并且嚴格避免開關之間的依賴,才能真正從功能開關中獲益。
總結
可以看到,功能開關相比功能分支確實能給我們帶來很多的好處:減少合并,真正持續集成,在產品環境可以打開或者關閉某個功能,等等。但是這些好處也不是免費得到的??偨Y一下使用功能開關需要做的:
創建使用功能開關的基礎設施。在本例中就是在JSP中訪問資源文件,通過自定義的Spring namespace來動態確定bean的值等等。
在代碼中根據功能開關的狀態來決定代碼的行為。很有可能會引入比較多的if/else在Java代碼,JSP代碼中。
需要跑兩個CI Jobs。不過在時間上其實并沒有太多的損耗,因為我們可以通過加一個CI slave并行跑就可以了。
在某功能已經穩定地在產品環境下跑了一段時間之后,要及時拆掉跟這個開關相關的配置,if/else判斷等等代碼,減少系統中并存的開關數量。
設計一個開關的時候要非常小心,不要和其他開關有依賴關系。
總的來說,功能開關對于持續發布,平穩發布,甚至提高代碼質量都有著積極的作用,是一個值得嘗試的實踐。
原文轉自:http://www.anti-gravitydesign.com/deltestingadmindd/