利用Spring的Conditional註解來實現FeatureToggle

最近一個使用Spring的項目中須要進行性能調優。方式基本上是編寫新的代碼實現原來同樣的業務邏輯,只是實現方式有一些調整,例如增長cache,優化算法等等。html

一開始你們但願直接在原有代碼基礎上修改,可是這樣一來,就要跟上每週一次的發佈節奏,一週搞定難度太大。因而決定拷貝出的package來重構。在沒啓用以前這個package下都是dead code。這樣作的好處有幾點:java

  • 在調優後的code啓用前,業務至少不會受影響。
  • 利用docker的特性,能夠實現灰度發佈,好比啓動兩個docker,一個是老的code,一個啓用新的code,利用nginx實現分流。
  • 灰度發佈後發現有緊急bug,只須要devOps修改一點配置,重啓docker能夠再切回老的code。

出發點

既然要實現上述第三點,也就是利用配置來實現切換,那麼這個Enable的flag就不該該寫到代碼裏,甚至是配置文件裏,由於項目啓動都是在docker中經過spring-boot的cmd直接啓動的。DevOps是不容許進入docker進行操做的。nginx

實現

想到咱們的整個部署架構是基於Kubernetes的,能夠經過修改工程的deployment.yaml文件來實現。原理就是deployment裏面設置一個docker的Env,Key是FeatureToggle,Value能夠是這樣FeatureA,FeatureB ,當docker啓動時,JVM(Java代碼)能夠經過System.getenv()來得到環境變量,歷來知道這個Feature是須要啓用仍是不啓用。如上的寫法表示FeatureA和FeatureB是啓用的。算法

咱們能夠寫一個簡單的接口實現來判斷:spring

public boolean isFeatureEnable(String featureName) {
    // 用System.getenv("FeatureToggle")讀取環境變量判斷是否包含參數的featureName
    // ...
}

進一步使用

雖然咱們有了判斷方法,可是由於項目組的人都有潔癖,咱們不但願代碼中處處都是docker

if(isFeatureEnable(featureA)) {
    // new code
} else {
    // old code
}

這樣實在是太ugly了。api

咱們須要利用spring的IoC特性來切換implementations。Spring從4.0開始提供Conditional的註解。結合@Configuration就能夠實現app啓動時的不一樣Bean的注入。架構

寫一個FeatureA的Condition Classapp

public class FeatureACondition implements Condition{
 
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    return isFeatureEnable("featureA")
  }
}

再寫一個Spring的Configuration來使用這個Conditionide

@Configuration
@Conditional(FeatureACondition.class)
public class FeatureAConfiguration {
 
  @Bean(name="bizService")
  public BizService bizService(){
      return new EnhancedBizService();
  }
 
}

固然若是要實現互斥的切換,即啓用FeatureA另外一個Bean就不能加載的話,那麼再寫一個NotFeatureA的Configuration就能夠了。

@Configuration
@Conditional(NotFeatureACondition.class)
public class NotFeatureAConfiguration {
 
  @Bean(name="bizService")
  public BizService bizService(){
      return new OldBizService();
  }
 
}

這樣一來,當FeatureA啓用時BizService這個interface的實現就是EnhancedBizService,反之它的實現就是OldBizService。
固然你在configuration上用@ComponentScan,@Import等等都是沒問題的,在啓動時都會最早判斷Conditional,若是不知足spring根本不會繼續下面的掃描或者加載操做。

最後啓用這兩個Config
在項目啓動入口

@SpringBootApplication
@Import({NotFeatureAConfiguration.class, FeatureAConfiguration.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

小結

經過上述幾步,在spring項目啓動時經過conditional註解的條件判斷,實現不一樣Bean的裝配,從而啓用不一樣的Feature。
對於Devops而言,只須要在deployment裏面修改Env的內容,再重啓deploy這個app就能夠實現Feature Toggle了。即便你不使用Kubernetes,docker-compose也是同樣的道理。
經過修改docker-compose.yml實現:

environment:
  - FeatureToggle=FeatureA,FeatureB
  - SESSION_SECRET

總而言之就是充分利用OO語言的優點,實現可拔插的FeatureToggle。接下來咱們還會繼續研究如何Runtime的啓用Feature,我也發現了一個已有的輪子togglz。若是有朋友用過,歡迎反饋使用感覺。

相關文章
相關標籤/搜索