[Java, API] Partial Update library. Частичное обновление сущности в Java Web Services

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
17-Фев-2021 14:35

ВведениеВ структуре веб-сервисов типичным базовым набором операций над экземплярами сущностей(объектами) является CRUD (Create, Read, Update и Delete). Этим операциям в REST соответствуют HTTP методы POST, GET, PUT и DELETE. Но зачастую у разработчика возникает необходимость частичного изменения объекта, соответствующего HTTP методу PATCH. Смысл его состоит в том, чтобы на стороне сервера изменить только те поля объекта, которые были переданы в запросе. Причины для этого могут быть различные:
  • большое количество полей в сущности;
  • большая вероятность одновременного изменения одного и того же объекта при высокой нагрузке, в результате которого произойдет перезапись не только модифицируемых полей;
  • невозможность или более высокая сложность изменения полей в нескольких или всех объектах в хранилище(bulk update);
Эти и, возможно, другие причины побуждают разработчика реализовать стек операций по частичному изменению объекта.Рассмотрим наиболее часто применяемые варианты решения задачи частичного обновления.Использование обычного контроллера и DTOОдин из наиболее часто встречаемых вариантов реализации метода PATCH. В контроллере пришедший объект десериализуется в обычный DTO, и далее по стеку слоев приложения считается, что все поля в DTO со значением null не подлежат обработке.К плюсам данного метода можно отнести "привычность" реализации.К минусам относится во-первых потеря валидности значения null для обработки (после десериализации мы не знаем отсутствовало ли это поле в передаваемом объекте или оно пришло нам со значением null).Вторым минусом является необходимость явной обработки каждого поля при конвертировании DTO в модель и далее по стеку в сущность. Особенно сильно это чувствуется в случае обработки сущностей с большим количеством полей, сложной структурой. Частично вторую проблему возможно решить с использованием ObjectMapper(сериализация/десериализация POJO, аннотированных @JsonInclude(Include.NON_NULL) ) ,а так же библиотекой MapStruct, генерирующей код конвертеров.Использование Map<String, Object> вместо POJOMap<String, Object> является универсальной структурой для представления данных и десериализации. Практически любой JSON объект может быть десериализован в эту структуру. Но, как мы можем понять из типов обобщения, мы теряем контроль типов на этапе компиляции (а соответственно и на этапе написания исходного кода в IDE).К достоинствам этого метода можно отнести универсальность представления данных и возможность корректной обработки значения null.Недостатки же в данном случае глобальны: потеря контроля типов на этапе написания исходного кода и компиляции, необходимость явного приведения типов, высокая сложность рефакторинга, обнаружение практически всех ошибок, связанных с типами данных и именованием полей только в runtime(тесты или реальный запуск приложения).Использование JSON Patch и JSON Merge PatchJSON Patch и JSON Merge Patch являются стандартизованными и наиболее универсальными методами описания частичного изменения объекта. Спецификация Java EE содержит интерфейсы, описывающие работу с обоими этими форматами: JsonPatch и JsonMergePatch. Существуют реализации этих интерфейсов, одной из которых является библиотека json-patch. Оба формата кратко описаны в статье Michael Scharhag REST: Partial updates with PATCH.Достоинства метода: стандартизация, универсальность, возможность реализовать не только изменение значения в объекте, но и добавление, удаление, перемещение, копирование и проверку значений полей в объекте.К недостаткам метода можно отнести несколько большую сложность структуры данных, описывающих изменения в объекте и ее визуальное несоответствие типовому DTO объекта, более высокую сложность передачи данных об изменениях между слоями приложения, усложнение процесса валидации, усложненный процесс преобразования структуры описания изменений к запросам множественного обновления объектов в БД, etc.Partial Update libraryОсновной целью создания библиотеки стало объединение положительных и исключение отрицательных сторон первых двух методов из описанных: использование классических DTO в фасадах и гибкости структуры Map<String, Object> "под капотом".Ключевыми элементами библиотеки являются интерфейс ChangeLogger и класс ChangeLoggerProducer.Класс ChangeLoggerProducer предназначен для создания "оберток" POJO, перехватывающих вызовы сеттеров и реализующих интерфейс ChangeLogger для получения изменений, произведенных вызовами сеттеров в виде структуры Map<String, Object>.Для дальнейших примеров будут использоваться вот такие POJO:
public class UserModel {
  private String login;
  private String firstName;
  private String lastName;
  private String birthDate;
  private String email;
  private String phoneNumber;
}
@ChangeLogger
public class UserDto extends UserModel {
}
Вот пример работы с такой "оберткой":
ChangeLoggerProducer<UserDto> producer = new ChangeLoggerProducer<>(UserDto.class);
UserDto user = producer.produceEntity();
user.setLogin("userlogin");
user.setPhoneNumber("+123(45)678-90-12");
Map<String, Object> changeLog = ((ChangeLogger) user).changelog();
/*
    changeLog in JSON notation will contains:
    {
        "login": "userlogin",
        "phoneNumber": "+123(45)678-90-12"
    }
*/
Суть "обертки" состоит в следующем: при вызове сеттера его имя добавляется в Set<String>, при дальнейшем вызове метода Map<String, Object> changelog() он вернет ассоциативный список, ключом в котором будет имя поля, а соответствующим ключу значением будет объект, возвращенный соответствующим геттером. В случае, если объект, возвращаемый геттером реализует интерфейс ChangeLogger, то в значение поля пойдет результат вызова метода Map<String, Object> changelog().Для сериализации/десериализации "оберток" реализован класс ChangeLoggerAnnotationIntrospector. Это класс представляет собой Annotation Introspector для ObjectMapper. Основной задачей класса является создание "обертки" при десериализации класса, аннотированного @ChangeLogger аннотацией библиотеки и сериализацией результата вызова метода Map<String, Object> changelog() вместо обычной сериализации всего объекта. Примеры использования ObjectMapper с ChangeLoggerAnnotationIntrospector приведены ниже.Сериализация:
ObjectMapper mapper = new ObjectMapper.setAnnotationIntrospector(new ChangeLoggerAnnotationIntrospector());
ChangeLoggerProducer<UserDto> producer = new ChangeLoggerProducer<>(UserDto.class);
UserDto user = producer.produceEntity();
user.setLogin("userlogin");
user.setPhoneNumber("+123(45)678-90-12");
String result = mapper.writeValueAsString(user);
/*
    result should be equal
    "{"login": "userlogin","phoneNumber": "+123(45)678-90-12"}"
*/
Десериализация:
ObjectMapper mapper = new ObjectMapper.setAnnotationIntrospector(new ChangeLoggerAnnotationIntrospector());
String source = "{"login": "userlogin","phoneNumber": "+123(45)678-90-12"}";
UserDto user = mapper.readValue(source, UserDto.class);
Map<String, Object> changeLog = ((ChangeLogger) user).changelog();
/*
    changeLog in JSON notation will contains:
    {
        "login": "userlogin",
        "phoneNumber": "+123(45)678-90-12"
    }
*/
Используя ObjectMapper с ChangeLoggerAnnotationIntrospector мы можем десериализовать пришедший нам в контроллер JSON с полями для частичного апдейта и далее передавать эти данные в подлежащие слои сервисов для реализации логики. В библиотеке присутствует инфраструктура для реализации мапперов DTO, Model, Entity с использованием "оберток". Пример полного стека приложения реализован в тестовом проекте Partial Update Example.ИтогPartial Update library позволяет с рядом ограничений реализовать наиболее типичную задачу частичного изменения объекта, используя максимально близкий к типовому способ организации стека приложения. При этом универсальность ассоциативного списка объединена с сохранением контроля типов данных в процессе написания исходного кода и компиляции, что позволяет избежать большого числа ошибок в runtime.В настоящее время функционал имеет ряд ограничений:
  • реализован только простейший маппинг "поле в поле", что не позволяет автоматизировать ситуации с разными именами одного и того же поля в DTO, Model, Entity;
  • не реализован модуль интеграции со Spring, в связи с чем для реализации сериализации/десериализации "оберток" DTO необходимо реализовать конфигурацию(как в примере), добавляющую ChangeLoggerAnnotationIntrospector в стандартный ObjectMapper контроллера приложения;
  • не реализованы утилиты формирования SQL/HQL запросов для bulk update операций с БД;
В последующих версиях планируется добавление недостающего функционала.Формат данной статьи не позволяет более детально рассмотреть инфраструктуру для создания мапперов и показать применение библиотеки в типичном стеке приложения. В дальнейшем я могу более детально разобрать Partial Update Example и уделить больше внимания описанию внутренней реализации библиотеки.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_java, #_api, #_patch, #_rest, #_api, #_controller, #_java, #_api
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 21:08
Часовой пояс: UTC + 5