Всем привет! На связи Владимир Колеснев и Владимир Беневоленский из ИТ-команды подразделения ДОМ.РФ Земли. Здесь мы уже рассказывали о том, чем мы занимаемся, но напомним: в ДОМ.РФ мы создаём систему автоматизации вовлечения в оборот неиспользуемого федерального имущества. Другими словами, мы разрабатываем продукт на Б24, в котором земельные участки (ЗУ) проходят долгий путь от появления в системе до реализации на торгах.
![](https://habrastorage.org/getpro/habr/upload_files/09d/474/38f/09d47438f1de78e69b5b1aa0510e8044.jpg)
Сокращённо мы СУЗФ – система управления земельным фондом. Почему наша система работает на Битрикс24? Да бог его знает, так было задумано свыше. Но, как говорится, от своей судьбы не уйдешь. Проблема, с которой мы столкнулись, случилась бы в любом случае, независимо от того, использовали бы мы Б24 или нет.
СУЗФ – огромная система с кучей сущностей и процессов, в которой, естественно, всё работает как швейцарские часики (спасибо нашему тимлиду). Мы оперируем 25-30 сущностями и ~900 свойствами, но основные сущности – это лоты, земельные участки, торги, документы (кстати, документов у нас около трёхсот тысяч). Каждая сущность имеет свою логику создания и изменения. Это могут быть различные источники модификаций. Например, земельный участок может создаваться/модифицироваться внутри системы логикой трансформации (межевания) участков / обновляя какие-либо данные через выписку ЕГРН (единый государственный реестр) или обновляя гео-данные через интеграцию с сервисами наподобие DaData и пр. Причем нужно заметить, что обновления так же могут происходить по событию (EventHandler) и по интервалу времени (Cron).
В среднем за день у нас происходит около 35 тысяч модификаций элементов.
Проблема
![](https://habrastorage.org/getpro/habr/upload_files/dd7/2de/11c/dd72de11ca0ddcd5fe86aec98d0eb84d.jpg)
И жили бы мы так долго и счастливо, но «бизнес» интересовался природой случайной модификации элемента. С ростом системы генерировать отделом разработки ответы с причинами изменений стало уже невозможно. Проблема возникла, когда мы конкретно перестали понимать, почему какой-либо элемент сущности создался или изменил свои параметры.
Идея
Возник вопрос: как бы нам красиво снять с себя ответственность за модификации элементов? Нам был нужен механизм: единая точка входа для создания/изменения элемента и разные данные в зависимости от источника.
![](https://habrastorage.org/getpro/habr/upload_files/377/9a2/c0f/3779a2c0fc9b6e39710c04c1f6609aa8.jpg)
Реализация
Чтобы решить эту проблему, мы разработали новый механизм сохранения источника модификации элемента в нашу систему. Этот механизм позволяет нам отслеживать и записывать все действия, которые привели к созданию или изменению элемента.
Как это работает? Каждый раз, когда происходит создание/обновление элемента, эта система логирует источник и список измененных полей. Например, если земельный участок был изменен через выписку ЕГРН, система сохранит эту информацию и привяжет ее к элементу участка. Таким образом, у нас всегда будет доступ к полной истории изменений элемента и понимание, почему он был создан или изменен, когда и кем.
![](https://habrastorage.org/getpro/habr/upload_files/064/e8c/eb0/064e8ceb014ba4e585a18dbd275c24a1.jpg)
У нас есть базовый абстрактный класс для добавления и изменения элемента (рис.1):
![Рис.1. Базовый класс Рис.1. Базовый класс](https://habrastorage.org/getpro/habr/upload_files/e9e/26d/f06/e9e26df06dda893139827b37444b7d71.png)
Внутри этого класса есть:
Метод getDescription(), который возвращает описание логики модификации/создания элемента
Метод getFields(), в котором подготавливаем поля для элемента, данные из этого метода будем получать в методе сохранения save() в базовом классе
Метод onBefore(), в котором мы можем валидировать переданные данные перед сохранением либо выполнять какую-то дополнительную логику, необходимую до сохранения
Метод onAfter() для дополнительной логики после сохранения элемента
Метод createOrUpdate(), который изменяет/создает элемент. В него мы можем передать ID элемента, если элемент нужно модифицировать.
![Рис.2. Метод createOrUpdate() Рис.2. Метод createOrUpdate()](https://habrastorage.org/getpro/habr/upload_files/f0b/0a4/690/f0b0a4690ed6779af100df45453aa6ed.png)
Внутри метода createOrUpdate() мы сохраняем элемент через метод save() и сохраняем в логи результат сохранения элемента (рис.3). Внутри метода save() мы только вызываем метод createOrUpdate(), но уже из класса для работы с элементами инфоблоков (инфоблок – одна из сущностей БД Б24).
![Рис.3. Обновление/создание элемента (метод из класса для работы с элементами инфоблоков) Рис.3. Обновление/создание элемента (метод из класса для работы с элементами инфоблоков)](https://habrastorage.org/getpro/habr/upload_files/912/01e/af8/91201eaf8996dec6655ef2123a472d88.jpg)
Таким образом выглядит структура источников для элементов (рис.5):
![Рис.4 Структура источников элементов Рис.4 Структура источников элементов](https://habrastorage.org/getpro/habr/upload_files/3e0/0f0/a87/3e00f0a87a2798c6a5aa8a5a68f3de61.png)
Классы источников мы наследуем от базового класса (рис.5):
![Рис.5 Класс источника Рис.5 Класс источника](https://habrastorage.org/getpro/habr/upload_files/112/067/d67/112067d6711dbaf3179540c620d65954.png)
Если в функцию createOrUpdate() мы передаём ID элемента, элемент обновляется, если не передаём – элемент создаётся.
Сохранение элемента происходит таким образом (рис.6):
![Рис.6 Сохранение элемента Рис.6 Сохранение элемента](https://habrastorage.org/getpro/habr/upload_files/822/c37/3a1/822c373a192e2802585e7e6a3b85f2b4.png)
Данные, которые мы передаем в конструктор должны содержать в себе максимальный набор «сырых» данных, а различная логика и преобразования должны содержаться уже внутри сорса. Таким образом вся логика изменения элемента для данного сорса содержится в одном едином классе.
![](https://habrastorage.org/getpro/habr/upload_files/058/ae0/522/058ae0522920e4ade34d34a644abdd37.jpg)
Сохранение элемента может быть вызвано каким-либо событием (EventHanlder) либо запущено по истечении интервала времени.
Для таких случаев у нас есть два интерфейса для класса источника: Eventable и Periodic. От Periodic добавляется метод, в котором определяется, пора ли нам запустить сохранение элемента. От Eventable – метод, в котором в класс-регистратор обработчиков событий мы возвращаем событие сразу с его обработчиком.
Интерфейс Eventable на рис.6. и Periodic на рис.7.
![Рис.7 Интерфейс Eventable Рис.7 Интерфейс Eventable](https://habrastorage.org/getpro/habr/upload_files/a99/66d/eee/a9966deee944a1008050c22905d175c1.png)
![Рис.8 Интерфейс Periodic Рис.8 Интерфейс Periodic](https://habrastorage.org/getpro/habr/upload_files/31c/875/9f6/31c8759f6d0c4aff8730b485d9fb88af.png)
На рис.8 мы регистрируем обработчики событий.
![Рис.9 Регистрация обработчиков событий Рис.9 Регистрация обработчиков событий](https://habrastorage.org/getpro/habr/upload_files/25c/782/85f/25c78285f1a0aa778eadceb233c7581c.png)
Итоги
Этот механизм позволяет нам решить несколько проблем.
Во-первых, он упрощает процесс отладки и исправления ошибок. Если у нас возникают проблемы с элементом, мы можем быстро определить источник модификации и найти причину проблемы. Во-вторых, он помогает повысить прозрачность работы системы. Мы можем предоставить пользователям доступ к информации о модификации элемента, чтобы они могли видеть, какие действия привели к изменению элемента и убедиться в его достоверности.
Кроме того, это позволяет анализировать данные и выявлять тенденции и паттерны в модификации элементов. Мы можем использовать эти данные для оптимизации процессов и повышения эффективности работы системы.
Дополнительным бонусом стало то, что теперь мы можем автоматически генерировать документацию по источникам создания и изменений элементов, основываясь на методе getDescription.
Поэтому можно смело сказать, что механизм сохранения источника модификации элемента – мощный инструмент для улучшения работы нашей системы. Он позволяет нам легко отслеживать и анализировать изменения элементов, обеспечивать прозрачность и надежность работы системы.
![](https://habrastorage.org/getpro/habr/upload_files/148/810/d2c/148810d2c86a0ead09fa4385cd8289c3.jpg)