3

Мы используем Objectify с Google App Engine для Java. Мы сохраняем множество констант перечисления в хранилище данных, используя поставляемый EnumTranslatorFactory, который просто сохраняет/загружает константу, используя Enum#name(). Это хорошо работает.Обратная совместимость дополнений Enum для Objectify/Appengine

Когда мы выпускаем новые версии нашего приложения для GAE, новая версия живет рядом со старой версией, одновременно обслуживая запросы клиентов. Это хорошо объясняется Google's traffic splitting docs.

Обновления системы вводят новые константы Enum, которые вызывают ошибки во время загрузки. Например:

Версия 1 имеет следующие перечисления:

enum Meal{BREAKFAST,LUNCH,DINNER} 

Version 2 имеет дополнительную константу добавляемые в перечислении для поддержки британских блюд:

enum Meal{BREAKFAST,LUNCH,TEA,DINNER} 

При тестировании версии 2 приложения , TEA будет сохраняться с некоторой Entity. Впоследствии версия 1 будет загружать этот Entity, Objectify будет пытаться преобразовать TEA в Enum, используя Enum # valueOf (...), который генерирует исключение во время выполнения.

Обозначает документы, объясняющие Data Migration для перечислений, но это не удовлетворяет вышеуказанной ситуации.

Меня интересуют предложения о том, как лучше всего справиться с этой ситуацией.

ответ

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

Version 2 развернутый с новым Enum:

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 хранится в хранилище данных, относящемся к некоторому объекту, ответ отличается при попадании в конечные точки двух версий.

Нажатие первой версии возвращает значение БУДУЩЕЕ позволяет клиенту представить соответствующее сообщение:

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

возвращается:

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

Hitting версия 2 обеспечивает новое перечисление:

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

возвращение:

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

Это решение позволяет добавлять и использовать дополнительные перечисления в более позднем выпуске, в то время как более старые версии представляют ценность для клиента по контракту, что «Будущее» возможно.

0

Напишите свой собственный EnumTranslatorFactory, который предоставляет нуль для любого значения, которое еще не известно.

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

     ... 

Это не идеальный вариант, поскольку это свойство может потребоваться, а другой код может потерпеть неудачу, если указан нуль. Все свойства, которые сохраняются в базе данных, должны быть @Nullable.

1

В общем, я бы предложил сделать два обновления для вашего приложения. Во-первых, сделайте обновление, которое понимает только новое значение enum (но никогда не записывает его) и распространяется по всей вашей системе. Затем создайте выпуск, который на самом деле пишет новые значения.

Перемещение данных затруднено, особенно если вы хотите использовать разделение трафика. Разбейте его на шаги и несколько разворачиваний.

+0

Возможно, ваше предложение представляет собой сценарий и тот, который мы используем в случае переименования перечисления для целей обслуживания кода. Однако для добавления мы хотим использовать новое перечисление с новыми функциями, и это больно, чтобы создать новую функцию, а не использовать ее. Мы надеемся, что эти несколько версий будут работать бок о бок в течение нескольких недель или месяцев, чтобы наши функции были заложниками этого ограничения перечисления. –

+1

Я не совсем уверен, какое решение вы ищете. Вы хотите написать некоторые новые данные в хранилище данных, которые не понимает старая версия вашего приложения. Магии нет, вам нужно сделать свою старую версию обработкой новых данных изящно, прежде чем вы сможете изменить структуру данных. Это относится к любой миграции, а не только к перечислениям. – stickfigure

Смежные вопросы