Русский
Русский
English
Статистика
Реклама

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

Введение

В структуре веб-сервисов типичным базовым набором операций над экземплярами сущностей(объектами) является 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> вместо POJO

Map<String, Object> является универсальной структурой для представления данных и десериализации. Практически любой JSON объект может быть десериализован в эту структуру. Но, как мы можем понять из типов обобщения, мы теряем контроль типов на этапе компиляции (а соответственно и на этапе написания исходного кода в IDE).

К достоинствам этого метода можно отнести универсальность представления данных и возможность корректной обработки значения null.

Недостатки же в данном случае глобальны: потеря контроля типов на этапе написания исходного кода и компиляции, необходимость явного приведения типов, высокая сложность рефакторинга, обнаружение практически всех ошибок, связанных с типами данных и именованием полей только в runtime(тесты или реальный запуск приложения).

Использование JSON Patch и JSON Merge Patch

JSON 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;}@ChangeLoggerpublic 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
К списку статей
Опубликовано: 17.02.2021 12:10:47
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Api

Patch

Rest

Controller

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru