Я написал свой собственный механизм частичного обновления с использованием Reflection, но потом нашел JsonPatch. Если вы работаете с веб-API и еще не знаете о JsonPatch — обязательно прочитайте эту статью.

Если вас интересует Reflection и то, как мы можем ускорить Reflection, я предлагаю вам также прочитать первую часть статьи, которая также является отличным стартом для понимания необходимости стандартизированного способа частичного обновления объектов.

Обновлениечастично

Для меня было очевидно, что частичное обновление объектов не является чем-то уникальным для определенных объектов, а должно быть общим, чтобы его можно было повторно использовать для каждого объекта. Вот почему я решил использовать Reflection — получение и установку значений свойств объекта динамически во время выполнения без жесткого кодирования свойств. Но поскольку известно, что Reflection работает медленно, я создал помощник FastReflection, который преобразует GetMethod и SetMethod PropertyInfo в делегат, а затем сохраняя его в статической переменной класса. Это означает, что отражение выполняется медленно только при первом запуске и почти так же быстро, как обновление без отражения при следующих запусках, поскольку делегаты кэшируются.

Выполнение

Интерфейс, показанный ниже, используется для получения делегатов get/set свойства. Обратите внимание, что для указания типа необходим переключатель сопоставления с образцом, поскольку универсальные типы должны быть жестко запрограммированы при вызове метода. Это также большой недостаток этого подхода, поскольку всякий раз, когда метод UpdatePartial реализуется для какого-либо объекта, все типы его свойств должны быть в переключателе сопоставления с образцом.

Статический класс Fast Reflection реализует метод UpdatePartial и использует IPropertyDelegateService для создания словаря, содержащего все обновления свойств.

PropertyMode(Include, Exclude) используется, чтобы указать, должны ли быть обновлены записи в словаре или все свойства объекта, кроме тех, которые находятся в словаре.

Это основа метода UpdatePartial. Чтобы его использовать, необходимо создать конкретную реализацию IPPropertyDelegateService. В принципе, все необходимые методы уже реализованы в интерфейсе, но если объект, который необходимо обновить, имеет специальные типы, переключатель сопоставления с образцом должен быть расширен, как показано ниже, чтобы также включить тип (в данном случае CustomClass):

И теперь остается только класс, который хранит делегаты свойств в статической переменной. Поскольку с точки зрения бизнеса это все очень низкий уровень, я создал CustomClassHelper, который действует как оболочка, скрывающая беспорядочные детали.

Плюсы:

  • довольно общий
  • почти так же быстро, как без использования отражения

Минусы:

  • пользовательские типы должны быть расширены
  • вложенные объекты не могут быть частично обновлены
  • сложный

Хотя я очень гордился тем, что у меня вообще заработало это чудовище, вскоре я пришел к выводу, что эффективный и сложный код — это нехорошо, потому что мои коллеги не могли понять это решение, не изучив его глубоко. Это было далеко не самоочевидно и не интуитивно понятно с сопоставлением шаблонов пользовательского типа, которое почти всегда должно быть расширено. Через несколько недель я получил запрос на частичное обновление объектов внутри CustomClass и, честно говоря, понятия не имел, как я могу расширить эту штуку для этого. Я также не хотел касаться этого слишком сильно, потому что тем временем я уже забыл детали реализации, и у меня самого, создателя этого, были проблемы с обдумыванием этого.

Вскоре я понял, что должен отказаться от него. Это было просто невозможно поддерживать вообще, и вместо того, чтобы тратить слишком много времени на его адаптацию, нужно было найти новое решение. И на этот раз он должен быть стандартизирован. Что-то, что мне не пришлось бы придумывать самому. И действительно, вскоре я нашел лучшее решение.

JsonPatch

В REST ключевое различие между PUT и PATCH заключается в том, что PUT полностью обновляет объект, а это означает, что экземпляр объекта полностью заменяется новым. С другой стороны, Patch указывает только несколько свойств объекта, которые должны быть обновлены/добавлены/удалены. Это уже звучит очень похоже на UpdatePartial, который я реализовал ранее, но с той разницей, что PATCH — это стандартизированная методология в области разработки программного обеспечения.

Всякий раз, когда какое-то частичное обновление выполняется спокойно через json, строка json должна выглядеть так:

Операции:

  • Заменять
  • Добавлять
  • Удалять
  • Копировать
  • Двигаться

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

Например, вы можете использовать этот путь для замены четвертого элемента в списке следующим образом:

Или вы можете углубиться во вложенные объекты и обновить только свойства, не изменяя все вложенные объекты, например:

Вы видите, что с помощью PATCH очень просто делать сложные вещи, но как вы конструируете эти операции? Делать это самостоятельно было бы утомительно, но, к счастью, ASP.Net Core поставляется с готовым решением.

JsonPatchDocument‹T›

JsonPatchDocument позволяет очень удобно создавать операции, вызывая методы (Replace, Add, Remove, …) для самого себя и передавая Expression‹Func‹CustomClass, TProp› и новое значение для каждого метода. В конце концов, вы можете просто использовать JsonConvert, чтобы сериализовать его в строку json, которая выглядит точно так же, как одно из изображений выше. Я написал метод расширения под названием Serialize, который принимает старый и измененный CustomClass в качестве параметров, создает JsonPatchDocument и возвращает сериализованный объект в виде строки. потому что мне пришлось отправить спокойный запрос на исправление в микросервис. Этот микросервис имеет конечную точку PATCH, которая получает JsonPatchDocument‹CustomClass›, который используется для обновления объекта.

По сравнению с UpdatePartial это намного проще, потому что нам не нужно беспокоиться о пользовательских типах и реализовывать вспомогательную службу.

Чтобы выполнить частичное обновление, вы можете использовать метод applyTo, который берет объект и применяет к нему все операции, хранящиеся в JsonPatchDocument .

patch.applyTo(customClassInstance);

Плюсы:

  • простой
  • стандартизированный
  • универсальный

Минусы:

  • никто

Заключение

Благодаря этому путешествию я не только узнал, что JsonPatch великолепен и его следует использовать чаще, но и что хороший код прост и надежен.

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