利用Spring的Conditional注解来实现FeatureToggle
<p>最近一个使用Spring的项目中需要进行性能调优。方式基本上是编写新的代码实现原来一样的业务逻辑,只是实现方式有一些调整,例如增加cache,优化算法等等。</p> <p>一开始大家希望直接在原有代码基础上修改,但是这样一来,就要跟上每周一次的发布节奏,一周搞定难度太大。于是决定拷贝出的package来重构。在没启用之前这个package下都是dead code。这样做的好处有几点:</p> <ul> <li>在调优后的code启用前,业务至少不会受影响。</li> <li>利用docker的特性,可以实现灰度发布,比如启动两个docker,一个是老的code,一个启用新的code,利用nginx实现分流。</li> <li>灰度发布后发现有紧急bug,只需要devOps修改一点配置,重启docker可以再切回老的code。</li> </ul> <h3><strong>出发点</strong></h3> <p>既然要实现上述第三点,也就是利用配置来实现切换,那么这个Enable的flag就不应该写到代码里,甚至是配置文件里,因为项目启动都是在docker中通过spring-boot的cmd直接启动的。DevOps是不允许进入docker进行操作的。</p> <h3><strong>实现</strong></h3> <p>想到我们的整个部署架构是基于Kubernetes的,可以通过修改工程的deployment.yaml文件来实现。原理就是deployment里面设置一个docker的Env,Key是 FeatureToggle ,Value可以是这样 FeatureA,FeatureB ,当docker启动时,JVM(Java代码)可以通过 System.getenv() 来获得环境变量,从来知道这个Feature是需要启用还是不启用。如上的写法表示FeatureA和FeatureB是启用的。</p> <p>我们可以写一个简单的接口实现来判断:</p> <pre> <code class="language-java">publicbooleanisFeatureEnable(String featureName){ // 用System.getenv("FeatureToggle")读取环境变量判断是否包含参数的featureName // ... } </code></pre> <h3><strong>进一步使用</strong></h3> <p>虽然我们有了判断方法,但是因为项目组的人都有洁癖,我们不希望代码中到处都是</p> <pre> <code class="language-java">if(isFeatureEnable(featureA)) { // new code } else{ // old code } </code></pre> <p>这样实在是太ugly了。</p> <p>我们需要利用spring的IoC特性来切换implementations。Spring从4.0开始提供 <a href="/misc/goto?guid=4959723312263837316" rel="nofollow,noindex">Conditional</a> 的注解。结合 @Configuration 就可以实现app启动时的不同Bean的注入。</p> <p><strong>写一个FeatureA的Condition Class</strong></p> <pre> <code class="language-java">publicclassFeatureAConditionimplementsCondition{ @Override publicbooleanmatches(ConditionContext context, AnnotatedTypeMetadata metadata){ returnisFeatureEnable("featureA") } } </code></pre> <p><strong>再写一个Spring的Configuration来使用这个Condition</strong></p> <pre> <code class="language-java">@Configuration @Conditional(FeatureACondition.class) publicclassFeatureAConfiguration{ @Bean(name="bizService") publicBizServicebizService(){ returnnewEnhancedBizService(); } } </code></pre> <p>当然如果要实现互斥的切换,即启用FeatureA另一个Bean就不能加载的话,那么再写一个NotFeatureA的Configuration就可以了。</p> <pre> <code class="language-java">@Configuration @Conditional(NotFeatureACondition.class) publicclassNotFeatureAConfiguration{ @Bean(name="bizService") publicBizServicebizService(){ returnnewOldBizService(); } } </code></pre> <p>这样一来,当FeatureA启用时BizService这个interface的实现就是EnhancedBizService,反之它的实现就是OldBizService。</p> <p>当然你在configuration上用 @ComponentScan , @Import 等等都是没问题的,在启动时都会最先判断Conditional,如果不满足spring根本不会继续下面的扫描或者加载操作。</p> <p><strong>最后启用这两个Config</strong></p> <p>在项目启动入口</p> <pre> <code class="language-java">@SpringBootApplication @Import({NotFeatureAConfiguration.class, FeatureAConfiguration.class}) publicclassApplication{ publicstaticvoidmain(String[] args){ SpringApplication.run(Application.class, args); } } </code></pre> <h3><strong>小结</strong></h3> <p>通过上述几步,在spring项目启动时通过conditional注解的条件判断,实现不同Bean的装配,从而启用不同的Feature。</p> <p>对于Devops而言,只需要在deployment里面修改Env的内容,再重启deploy这个app就可以实现Feature Toggle了。即使你不使用Kubernetes,docker-compose也是一样的道理。</p> <p>通过修改 docker-compose.yml 实现:</p> <pre> <code class="language-java">environment: - FeatureToggle=FeatureA,FeatureB - SESSION_SECRET </code></pre> <p>总而言之就是充分利用OO语言的优势,实现可拔插的FeatureToggle。接下来我们还会继续研究如何Runtime的启用Feature,我也发现了一个已有的轮子 togglz 。</p> <p> </p> <p>来自:http://www.deanwangpro.com/2016/10/30/spring-featuretoggle/</p> <p> </p>
本文由用户 TiffinyPEJJ 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!