【问题标题】:Backwards Compatibility of Enum Additions for Objectify/AppengineObjectify/Appengine 的枚举添加的向后兼容性
【发布时间】:2013-07-18 16:33:49
【问题描述】:

我们将 Objectify 与 Google App Engine for Java 一起使用。我们使用提供的EnumTranslatorFactory 在数据存储中持久化各种枚举常量,它使用Enum#name() 简单地存储/加载常量。这很好用。

当我们向 GAE 发布应用的新版本时,新版本与旧版本相邻,两者同时向客户端提供请求。 Google's traffic splitting docs 很好地解释了这一点。

对系统的升级引入了新的枚举常量,这会在加载过程中导致错误。例如:

版本 1 具有以下枚举:

enum Meal{BREAKFAST,LUNCH,DINNER}

第 2 版在枚举中添加了额外的常量以支持英国餐:

enum Meal{BREAKFAST,LUNCH,TEA,DINNER}

在测试应用程序的第 2 版时,TEA 将与某些实体保持一致。随后版本 1 将加载该实体,Objectify 将尝试使用 Enum#valueOf(...) 将 TEA 转换为 Enum,这会引发运行时异常。

Objectify docs为Enums解释Data Migration,但它不满足上述情况。

我对如何最好地处理这种情况的建议感兴趣。

【问题讨论】:

    标签: java google-app-engine enums google-cloud-datastore objectify


    【解决方案1】:

    首先,提供一个接口,如果枚举未知,该接口将提供默认值。

    public interface EnumWithDefault<E extends Enum<E>> {
        E getDefault();
    }
    

    未来可能添加的枚举应实现此接口:

    public enum MyEnum implements EnumWithDefault<MyEnum>{
      ENUM_IN_VERSION_1, FUTURE;
    
      public MyEnum getDefault(){ return FUTURE; }
    }
    

    注册一个 TranslatorFactory,如果实现,它将提供默认值:

           return new ValueTranslator<Enum<?>, String>(path, String.class) {
        @Override
        public Enum<?> loadValue(String value, LoadContext ctx) {
            try{
               return Enum.valueOf((Class<Enum>)type, value.toString());
            }catch(Exception e){
               if (EnumWithDefault.class.isAssignableFrom(enumType)) {
                    EnumWithDefault<E> any = (EnumWithDefault<E>) enumType.getEnumConstants()[0];
                    result = any.getDefault();
               }else{
                  throw e;
               } 
            }
        }
    

    使用新 Enum 部署的版本 2:

    public enum MyEnum implements EnumWithDefault<MyEnum>{
      ENUM_IN_VERSION_1, ENUM_IN_VERSION_2, FUTURE;
    
      public MyEnum getDefault(){ return FUTURE; }
    }
    

    当应用程序的版本 2 被部署并且 ENUM_IN_VERSION_2 存储在与某些实体相关的数据存储中时,在命中两个版本的端点时响应不同。

    点击第一个版本返回值 FUTURE 允许客户端显示适当的消息:

    http://1.myapi.appspot.com/entities
    

    返回:

    <myEntity id='xyz' category='FUTURE' />
    

    第 2 版提供了新的枚举:

    http://2.myapi.appspot.com/entities
    

    返回:

    <myEntity id='xyz' category='ENUM_IN_VERSION_2' />
    

    此解决方案允许在以后的版本中添加和使用额外的枚举,而旧版本根据合同向客户展示“未来”是可能的价值。

    【讨论】:

      【解决方案2】:

      一般来说,我建议对您的应用进行两次升级。首先,进行仅了解新枚举值(但从不编写)的升级并将其传播到整个系统。然后发布一个真正写入新值的版本。​​

      数据迁移很困难,尤其是当您想使用流量拆分时。将其分解为步骤和多次部署。

      【讨论】:

      • 您的建议很可能是场景,也是我们在重命名枚举以进行代码维护的情况下使用的建议。但是,对于添加,我们希望使用具有新功能的新枚举,并且构建新功能而不使用它是很痛苦的。我们希望让这些多个版本同时运行几周或几个月,这样我们的功能就会受到这个枚举限制的影响。
      • 我不太确定您正在寻找什么样的解决方案。您想将一些旧版本的应用程序无法理解的新数据写入数据存储区。没有什么神奇的,你需要让你的旧版本优雅地处理新数据,然后才能改变数据结构。这适用于任何类型的迁移,而不仅仅是枚举。
      【解决方案3】:

      编写您自己的自定义 EnumTranslatorFactory,为任何未知的值提供 null

              ....
                  return new ValueTranslator<Enum<?>, String>(path, String.class) {
              @Override
              public Enum<?> loadValue(String value, LoadContext ctx) {
                  try{
                     return Enum.valueOf((Class<Enum>)type, value.toString());
                  }catch(Exception e){
                      return null;
                  }
              }
      
              ...
      

      这并不理想,因为该属性可能是必需的,如果提供 null,其他代码可能会失败。代码库中持久化枚举的所有属性都必须是@Nullable。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-19
        相关资源
        最近更新 更多