4

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

当我们向 GAE 发布应用程序的新版本时,新版本与旧版本相邻,两者同时向客户端提供请求。谷歌的流量分割文档很好地解释了这一点。

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

版本 1 具有以下枚举:

enum Meal{BREAKFAST,LUNCH,DINNER}

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

enum Meal{BREAKFAST,LUNCH,TEA,DINNER}

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

Objectify 文档解释了 Enums 的数据迁移,但它不满足上述情况。

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

4

3 回答 3

2

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

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

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

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;
           } 
        }
    }

使用新枚举部署的版本 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' />

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

于 2014-06-02T10:22:52.700 回答
1

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

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

于 2013-07-19T15:10:46.387 回答
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。

于 2013-07-19T11:41:44.953 回答