У нас есть вариант использования: мы храним глубоко вложенные объекты в MongoDB. Определенные поля в этом объекте могут иметь разные типы. Например, предположим, что объект может иметь поле, представляющее собой набор InterfaceType, и существуют разные реализации этого, скажем: ConcreteTypeA, ConcreteTypeB с разными полями.
package foo.bar
sealed interface InterfaceType
data class ConcreteTypeA(val aField1: String, val aField2: Int) : InterfaceType
data class ConcreteTypeB(val bField: List<String>) : InterfaceType
При использовании Spring Data Mongo DB для сохранения скажите:
package bar.foo
@Document("myCollection")
data class MyDocument(
@Id id: String,
val value: InterfaceType
)
Spring автоматически добавит поля с именем _class, которые помогут создать экземпляр правильного класса при чтении. См. также: Сопоставление типов :: Spring Data MongoDB
{
"value" : {
"aField1" : "some value",
"aField2" : 1234,
"_class" : "foo.bar.ConcreteTypeA"
},
"_class" : "bar.foo.MyDocument"
}
Все это работает нормально, но, конечно, это не очень безопасно для рефакторинга… переименование класса или перемещение его в другой пакет не удастся при чтении документа. Мы могли бы использовать @TypeAlias и зарегистрировать псевдонимы классов в конфигурации, что позволит переименовывать/перемещать класс (при условии, что мы не затрагиваем постоянное значение в аннотации). Или, альтернативно, создайте классы Document с большим количеством полей nullable и обрабатывайте все в репозитории.
Какой рекомендуемый подход к обработке этого типа структуры данных в MongoDB/Spring?
Тот же вопрос существует на Форуме сообщества MongoDB
Я использую это поведение по умолчанию _class со Spring и Mongo.
По моему опыту, переупаковка/переименование объектов — нечастая проблема. Гораздо более распространенной проблемой является добавление новых полей, не допускающих значение NULL, или переименование/удаление/изменение типа поля.
Таким образом, даже если вы решите проблему _class, у вас все еще останется много других проблем с данными, которые уязвимы для рефакторинга. Я считаю это неизбежным, и поэтому вам необходимо строго контролировать объекты вашей предметной области: управлять ими в процессе проектирования (/изменения) данных.
У нас есть сценарии, которые будут обновлять (но не понижать) БД каждый раз, когда мы вносим структурные изменения и быстро просматриваем, и я вижу <3% изменений _class. Эти сценарии написаны вручную; нет ничего более сложного, чем Liquidbase или один из этих менеджеров схемы базы данных.
В случае изменения местоположения класса это так же просто, как:
db.channel.updateMany({"_class" : "old.package.MemberChannel"}, {$set:{"_class" : "new.package.MemberChannel"}})
Спасибо AndrewL и qsi за ваш вклад. Мы решили пойти по маршруту @TypeAlias.
Мы также добавили тест с использованием ArchUnit, который гарантирует, что классы, реализующие интерфейс и используемые внутри аннотированного @Document класса, помечены @TypeAlias.
Это не очень сложно, но, хотя для нас не будет проблемой перенести базу данных и исправить полные имена классов в _class, мы больше боимся случайно пропустить эти изменения и затем столкнуться с проблемами во время выполнения.
Большим недостатком этого является то, что перед развертыванием переупакованных/рефакторизованных объектов требуется миграция базы данных, что приводит к простоям. Тогда даже малейшая ошибка при миграции приведет к ошибкам во время выполнения. По моему личному мнению,
@TypeAliasбудет гораздо более безопасным подходом.