抽象Optimizely SDK —第2部分:保持清洁

在本系列的第二部分中,我们将概述如何将Optimizely SDK实现建模为轻量级依赖注入(DI)框架。

在上一篇文章中,我们描述了如何扩展Optimizely SDK以在进行API调用时从线程本地对象(特别是映射诊断上下文(MDC))获取用户状态。 包装SDK的动机是通过使开发人员不需要在每次尝试一些新功能或推出时都提供这些详细信息来简化界面。 这是一个很大的改进,使SDK易于集成并且更加一致,但是在我们的代码中仍然留下了条件语句块。

 差异差异= OptimizelyClientFactory.getClient() 
.activate(“ exp_1”);
  MyInterface myInterface; 
 开关(variation.getKey()){ 
案例“ firstImplementation”:
myInterface = new FirstImplementation();
打破;
案例“ secondImplementation”:
myInterface = new SecondImplementation();
打破;
默认:
myInterface = new DefaultImplementation();
}

尽管这是一项临时的一次性实验,但它很快开始使我们的代码库变得混乱,使读者从核心业务逻辑中分散了注意力。 实验也证明,进行单元测试更加困难,并且需要定期维护以消除这种逻辑,一旦实验开始进行就可以了。

迭代到更好的东西

显而易见的是,主要业务逻辑并不关心基础实现,而仅关心实现提供的接口(或合同)。 通过订阅此依赖关系反转原则,我们立即开始考虑术语“依赖关系注入(DI)”框架。 我们希望Optimizely 提供一个实现的实例,并隐藏有关选择该实现的原因或方式的机制。

考虑到这一概念,我们抛弃了上面的漏洞抽象,并设想了一个API,该API会删除实现者的所有决策和实验意识,并将其委托给OptimizelyClient ,例如:

  MyInterface接口= OptimizelyClientFactory.getClient() 
.getFeature(MyInterface.class);

为了使之成为可能,我们开发了一个自定义注释框架,用于使用对Optimizely SDK进行调用所需的适当API密钥标记我们的类。 这种模式从核心逻辑中删除了实验的参数和变体,并将其直接移到它们所代表的类实现中。

这是我们从上面的示例中注释interface和具体class定义的方式:

  @OptimizelyFeature(“ myFeature”) 
公用接口MyInterface {…}
  @OptimizelyVariation(“ firstImplementation”) 
公共类FirstImplementation实现MyInterface {…}
  @OptimizelyVariation(“ secondImplementation”) 
公共类SecondImplementation实现MyInterface {…}

为什么要注释?

注释是元数据的一种形式,可以添加到实例变量,构造函数,方法,类等中。正确配置后,可以在运行时使元数据可用,以围绕其构建自定义逻辑。 作为使用反射添加横切功能的一种方式,注释在Java中相当普遍,它们通常在应用程序,测试和序列化框架中找到。

怎么运行的

首次实例化OptimizelyClient ,注释处理器会扫描类路径,以@OptimizelyFeature使用@OptimizelyFeature@OptimizelyVariation注释的任何类, @OptimizelyFeature类定义及其注释一起注册。 当随后对特定类的getFeature进行调用时, OptimizelyClient查找已注释的api密钥(在本例中为“ myFeature”),并调用SDK返回各自的Optimizely变异密钥。

然后,将变体键用于映射到相应实现的@OptimizelyVariation名称。 然后将该实现实例化并返回给调用方。

实例变量呢?

能够尝试单个具体类的实现非常棒,可以解决我们的许多内部用例,但不能充分利用SDK的全部功能,即优化功能变量。 功能变量提供了另一级可配置性,可以将其定义为功能的一部分并进行试验。

为了支持这些变量,注释框架提供了一个新的注释@OptimizelyVariable ,用于将实例变量标记为Optimizely Feature下的参数。

例如,假设我们有一个存储数据库连接配置的类:

 公共类DbConfiguration { 
  public static String URL =“ database.dz.optly.com”; 
public static int端口= 4321;
公共静态长TIMEOUT_MS = 30000;
public static int MAX_RETRIES = 3;
  } 

照原样,这是静态配置,需要更新应用程序部署,但是作为Optimizely Feature,我们可以有一个动态,可测试和可测量的配置。

  @OptimizelyFeature(“ DB_CONFIG”); 
公共类DbConfiguration {
  @OptimizelyVariable(“ URL”); 
public String url =“ database.dz.optly.com”;
  @OptimizelyVariable(“ PORT”); 
public int端口= 4321;
  @OptimizelyVariable(“ TIMEOUT_MS”); 
公共长超时Ms = 30000;
  @OptimizelyVariable(“ MAX_RETRIES”); 
public int maxRetries = 3;
  } 

与之前类似,当从OptimizelyClient调用getFeature时,该请求将映射为Optimizely SDK中名为“ DB_CONFIG”的功能的API调用。 但是,由于此类具有通过@OptimizelyVariable定义的成员,因此框架会从SDK进行对getFeatureVariable附加调用,并覆盖类实例中的各个值。