Почему при обмене с мобильным приложением затираются измененные данные в центральной базе 1С и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработка двустороннего обмена данными между мобильным приложением и центральной базой (ЦБ) на платформе 1С — задача, с которой рано или поздно сталкивается любой современный разработчик. Часто на начальном этапе проектирования обмена через веб-сервисы и планы обмена возникает крайне неприятная ситуация: вы успешно настроили синхронизацию в обе стороны, но при изменении документа в центральной базе при следующем сеансе связи эти изменения бесследно исчезают. Вместо новой версии документа из мобильного приложения снова "прилетает" старая версия и затирает актуальные данные на сервере.

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

Выясняем причину: почему происходит затирание данных?

Описанная проблема является классической для распределенных систем и связана с возникновением коллизий (конфликтов изменений), эффектом "эха" (повторной регистрации), а также с ошибками в механизме квитирования (подтверждения приема сообщений).

Давайте проанализируем типовой жизненный цикл документа при некорректно настроенном обмене:

  1. Документ создается в мобильном приложении и отправляется в центральную базу. На стороне ЦБ документ успешно создается.
  2. Пользователь в центральной базе вносит изменения в этот документ (например, корректирует сумму или количество). Так как в плане обмена ЦБ включена авторегистрация, этот документ регистрируется к отправке на мобильное устройство.
  3. В это же время на мобильном устройстве документ не менялся. Однако из-за отсутствия правильного подтверждения получения (квитирования) или из-за избыточной авторегистрации на мобильном клиенте этот документ продолжает числиться измененным и на стороне мобильного приложения.
  4. При очередном сеансе связи мобильное приложение отправляет свой (старый, неизмененный) вариант документа на веб-сервис ЦБ.
  5. Веб-сервис центральной базы принимает XML/JSON-пакет, находит существующий документ по уникальному идентификатору (GUID), перезаписывает его старыми данными и... удаляет регистрацию изменений для мобильного узла.
  6. Итог: изменения, внесенные в центральной базе, стерты старыми данными с мобильного телефона, а регистрация изменений очищена. Система считает, что базы синхронизированы, хотя на самом деле произошла потеря данных.

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

---

Шаг 1. Реализация механизма разрешения коллизий на стороне сервера

Когда обмен реализуется вручную через веб-сервисы (без использования стандартных механизмов РИБ), платформа 1С не умеет автоматически определять, чьи изменения важнее. Нам необходимо явно научить сервер обрабатывать ситуации, когда один и тот же объект изменен и в центральной базе, и на мобильном устройстве.

Для этого перед записью полученного от мобильного приложения объекта в ЦБ мы должны проверить: а не зарегистрировано ли уже изменение по этому объекту для данного мобильного узла? В этом нам поможет метод ПланыОбмена.ИзменениеЗарегистрировано().

Если изменение зарегистрировано в центральной базе, это означает, что в ЦБ есть более актуальные (или просто другие) изменения, которые еще не были отправлены на мобильный клиент. В таком случае сервер должен проигнорировать пришедшие от мобильного приложения данные по этому конкретному объекту и оставить регистрацию изменений в силе. При следующей отправке данных от сервера к мобильному устройству обновленный документ уйдет на мобильный клиент и обновит его базу.

Рассмотрим пример реализации этой проверки на стороне веб-сервиса в ЦБ:


Функция ЗаписатьОбъектСПроверкойКоллизий(ПолученныйОбъект, МобильныйУзел) Экспорт
    
    СсылкаНаОбъект = ПолученныйОбъект.Ссылка;
    
    // Если объект уже существует в базе, проверяем наличие зарегистрированных изменений
    Если ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
        
        Если ПланыОбмена.ИзменениеЗарегистрировано(МобильныйУзел, СсылкаНаОбъект) Тогда
            // Обнаружена коллизия! В ЦБ документ изменен и ожидает отправки.
            // Игнорируем версию мобильного приложения, отдавая приоритет серверу.
            Возврат Ложь; 
        КонецЕсли;
        
    КонецЕсли;
    
    // Если коллизий нет, готовим объект к записи
    ПолученныйОбъект.ОбменДанными.Загрузка = Истина;
    ПолученныйОбъект.ОбменДанными.Отправитель = МобильныйУзел;
    
    Попытка
        ПолученныйОбъект.Записать();
        Возврат Истина;
    Исключение
        ЗаписатьВЛогОшибок(ОписаниеОшибки());
        Возврат Ложь;
    КонецПопытки;
    
КонецФункции

---

Шаг 2. Борьба с эффектом "эха" (ОбменДанными.Загрузка)

Частая ошибка начинающих разработчиков — запись объектов при обмене в "обычном" режиме. Если в плане обмена для объекта включена авторегистрация, любая запись объекта (даже та, которая происходит в процессе загрузки из обмена) приводит к его автоматической регистрации для узла-отправителя. В результате объект начинает бесконечно "бегать" между базами.

Чтобы избежать этого эффекта, при записи полученных данных (и на сервере, и на мобильном клиенте) необходимо всегда устанавливать свойство ОбменДанными.Загрузка = Истина.

Дополнительно рекомендуется заполнять свойство ОбменДанными.Отправитель. Это указывает платформе 1С, из какого именно узла пришли данные, и предотвращает их повторную регистрацию обратно на этот же узел.

Посмотрим на пример правильной записи объекта:


НовыйДокумент = Документы.ПродажаТоваров.СоздатьДокумент();
// Заполнение реквизитов из полученного сообщения обмена
// ...
НовыйДокумент.ОбменДанными.Загрузка = Истина;
НовыйДокумент.ОбменДанными.Отправитель = УзелОтправитель;
НовыйДокумент.Записать();

Важное примечание: При установке свойства Загрузка = Истина в объекте отключаются стандартные платформенные проверки и обработчики событий (такие как ПередЗаписью, ПриЗаписи в модуле объекта). Если у вас там написана важная бизнес-логика, убедитесь, что вы обрабатываете ее корректно, либо делайте выборочное отключение регистрации в подписках на события.

---

Шаг 3. Реализация надежного механизма квитирования (подтверждения)

Для того чтобы данные не гонялись по кругу, мобильное приложение должно очищать регистрацию изменений только после того, как получит от сервера гарантированное подтверждение: "Я принял твое сообщение номер N, все данные успешно записаны".

Этот процесс называется квитированием. Разберем по шагам, как его организовать:

  1. При формировании сообщения на мобильном клиенте фиксируется текущий номер отправляемого сообщения (метод ПланыОбмена.ЗаписатьИзменения() автоматически увеличивает номер отправленного сообщения для узла).
  2. Мобильный клиент передает этот номер сообщения серверу вместе с телом сообщения.
  3. Сервер считывает данные, успешно записывает их и в ответном пакете возвращает этот же номер сообщения мобильному клиенту в качестве подтверждения.
  4. Мобильный клиент, получив успешный ответ от веб-сервиса с подтвержденным номером сообщения, выполняет удаление зарегистрированных изменений с помощью метода ПланыОбмена.УдалитьРегистрациюИзменений(ЦентральныйУзел, НомерПринятогоСообщения).

Проанализируем пример схемы работы на мобильном клиенте:


Процедура ВыполнитьСинхронизациюНаМобильномКлиенте()
    
    ЦентральныйУзел = ПланыОбмена.МобильныйОбмен.ЭтотУзел(); // Ваш узел
    НастройкиПодключения = ПолучитьПараметрыПодключения();
    
    // 1. Создаем запись сообщения обмена
    ЗаписьСообщения = Новый ЗаписьСообщенияОбмена;
    ЗаписьXML = Новый ЗаписьXML;
    ЗаписьXML.УстановитьСтроку();
    
    ЗаписьСообщения.НачатьЗапись(ЗаписьXML, ЦентральныйУзел);
    
    // Записываем изменения в XML
    ПланыОбмена.ЗаписатьИзменения(ЗаписьСообщения);
    
    НомерОтправленного = ЗаписьСообщения.НомерСообщения;
    ЗаписьСообщения.ЗакончитьЗапись();
    
    СтрокаДанных = ЗаписьXML.Закрыть();
    
    // 2. Отправляем данные на веб-сервис
    Попытка
        Прокси = ПодключитьсяКВебСервису(НастройкиПодключения);
        
        // Передаем и данные, и номер сообщения, которое мы сформировали
        ОтветСервера = Прокси.ВыполнитьСинхронизацию(СтрокаДанных, НомерОтправленного);
        
        // 3. Обрабатываем ответ сервера. 
        // Сервер должен вернуть структуру, содержащую подтвержденный номер сообщения
        // и новые данные для мобильного приложения.
        Если ОтветСервера.Успешно Тогда
            
            // Записываем пришедшие от сервера изменения в мобильную базу
            ЗагрузитьИзмененияНаМобильном(ОтветСервера.ДанныеСервера);
            
            // 4. Квитируем (удаляем регистрацию) только после успешной загрузки!
            ПланыОбмена.УдалитьРегистрациюИзменений(ЦентральныйУзел, НомерОтправленного);
            
            Сообщить("Синхронизация успешно завершена!");
        КонецЕсли;
        
    Исключение
        Сообщить("Ошибка обмена: " + ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

---

Шаг 4. Отказ от авторегистрации на мобильном устройстве

В мобильных приложениях автоматическая регистрация изменений платформой (свойство "Авторегистрация = Разрешить" в плане обмена) часто ведет себя непредсказуемо. Проведение документов, создание движений по регистрам могут вызывать повторную регистрацию самих документов, даже если пользователь их не менял интерактивно.

Рекомендуемое решение: перевести план обмена на мобильном устройстве в ручной режим управления регистрацией ("Авторегистрация = Запретить").

Регистрируйте изменения объектов к обмену только тогда, когда это действительно необходимо — например, при интерактивной записи документа пользователем на мобильном устройстве. Для этого можно использовать подписки на события ПередЗаписью или ПриЗаписи соответствующих объектов:


Процедура РегистрироватьИзмененияДляОбменаПриЗаписи(Источник, Отказ) Экспорт
    
    // Проверяем, что запись происходит интерактивно, а не в процессе загрузки обмена
    Если Источник.ОбменаДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    ЦентральныйУзел = НайтиЦентральныйУзелПланаОбмена();
    
    Если ЗначениеЗаполнено(ЦентральныйУзел) Тогда
        ПланыОбмена.ЗарегистрироватьИзменения(ЦентральныйУзел, Источник.Ссылка);
    КонецЕсли;
    
КонецПроцедуры

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

---

Резюме лучших практик

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

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

← На главную