用于物化/播放的枚举加法的向后兼容性

时间:2013-07-19 11:41:45

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

我们正在使用Objectify与Google App Engine for Java。我们使用提供的EnumTranslatorFactory在数据存储区中持久化各种枚举常量,Enum#name()只使用Google's traffic splitting docs存储/加载常量。这很有效。

当我们向GAE发布我们的应用程序的新版本时,新版本位于旧版本旁边,同时向客户端提供请求。 Data Migration解释了这一点。

升级到系统会引入新的Enum常量,这些常量会在加载过程中导致错误。例如:

版本1具有以下枚举:

enum Meal{BREAKFAST,LUNCH,DINNER}

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

enum Meal{BREAKFAST,LUNCH,TEA,DINNER}

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

Objectify docs为Enums解释{{3}},但它不满足上述情况。

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

3 个答案:

答案 0 :(得分:2)

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

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' />

此解决方案允许在以后的版本中添加和使用其他枚举,而旧版本根据可能“未来”的合同向客户端提供值。

答案 1 :(得分:1)

一般情况下,我建议您对应用进行两次升级。首先,进行升级,只能理解新的枚举值(但绝不会写入)并在整个系统中传播。然后创建一个实际写入新值的版本。​​

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

答案 2 :(得分:0)

编写您自己的自定义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。