[Java, API] Partial Update library. Частичное обновление сущности в Java Web Services
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
ВведениеВ структуре веб-сервисов типичным базовым набором операций над экземплярами сущностей(объектами) является 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
===========
Похожие новости:
- [Разработка веб-сайтов, JavaScript, Работа с 3D-графикой, Canvas, IT-компании] Шейдеры, Three.js и киберпанк. Как мы делали лендинг в неоново-античной стилистике
- [Интерфейсы, Конференции, IT-компании] Я ❤︎ Фронтенд 2021: доклады, воркшопы, CTF
- [JavaScript, Учебный процесс в IT, Управление персоналом, Карьера в IT-индустрии] Как проходит собеседование Junior фронтенд-разработчика
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] 5 подходов к стилизации React-компонентов на примере одного приложения
- [JavaScript, Программирование, Учебный процесс в IT] Мои рассуждения на тему Как учиться программировать на JavaScript
- [JavaScript, Node.JS, Профессиональная литература] Книга «Веб-разработка с применением Node и Express. Полноценное использование стека JavaScript. 2-е издание »
- [Java] Code evaluation как средство отладки
- [Анализ и проектирование систем, .NET, Проектирование и рефакторинг, Микросервисы] Взаимодействия. RPC vs REST vs MQ
- [Java, Микросервисы] Микросервисы: от CRUD до Native Image. Часть первая
- [Тестирование IT-систем, Программирование, Тестирование веб-сервисов, Карьера в IT-индустрии] Перспективы разработчика в автоматизации тестирования ПО
Теги для поиска: #_java, #_api, #_patch, #_rest, #_api, #_controller, #_java, #_api
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 02:58
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
ВведениеВ структуре веб-сервисов типичным базовым набором операций над экземплярами сущностей(объектами) является CRUD (Create, Read, Update и Delete). Этим операциям в REST соответствуют HTTP методы POST, GET, PUT и DELETE. Но зачастую у разработчика возникает необходимость частичного изменения объекта, соответствующего HTTP методу PATCH. Смысл его состоит в том, чтобы на стороне сервера изменить только те поля объекта, которые были переданы в запросе. Причины для этого могут быть различные:
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" } */ 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" } */
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 02:58
Часовой пояс: UTC + 5