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