Разберем типичную проблему разработчиков мобильных решений на платформе «1С:Предприятие 8». Представим ситуацию: имеется одна центральная база и к ней подключается большое количество мобильных клиентов (от 50 и более). Если попытаться использовать схему, в которой для всех мобильных клиентов заведен только один узел в плане обмена центральной базы, то в процессе работы номера сообщений неизбежно начнут сбиваться. Это происходит из-за конкуренции мобильных устройств (race conditions), когда разные девайсы одновременно пытаются забрать данные и подтвердить их получение.
Выясним причину этой проблемы и подробно рассмотрим проверенные архитектурные решения, которые позволят организовать стабильный, быстрый и масштабируемый обмен данными.
Для начала проанализируем базовый механизм работы планов обмена в 1С. Когда мы регистрируем изменения для узла, платформа присваивает выгружаемому сообщению уникальный порядковый номер. При успешном получении сообщения мобильный клиент отправляет обратно подтверждение (номер принятого сообщения). Получив это подтверждение, центральная база вызывает метод ПланыОбмена.УдалитьРегистрациюИзменений для всех объектов, номера сообщений которых меньше или равны подтвержденному.
Если несколько мобильных приложений обмениваются через один и тот же узел:
Поэтому фундаментальное правило разработки на 1С гласит: каждая мобильная база должна иметь свой персональный узел в центральной базе данных. Сколько клиентов — столько и узлов!
Разберем, как правильно реализовать эту схему на практике, чтобы избежать ручного создания узлов администратором и не допустить раздувания базы данных.
Для автоматизации процесса настроим динамическое создание узлов на стороне сервера. При первом запуске мобильного приложения оно должно сгенерировать свой уникальный идентификатор (например, GUID) и передать его в центральную базу через веб-сервис или HTTP-сервис. На стороне сервера выполним следующий код:
Функция ПолучитьИлиСоздатьУзелОбмена(ИдентификаторКлиента, ИмяПользователя) Экспорт
УзелСсылка = ПланыОбмена.МобильныеОбмены.НайтиПоКоду(ИдентификаторКлиента);
Если Не ЗначениеЗаполнено(УзелСсылка) Тогда
НовыйУзел = ПланыОбмена.МобильныеОбмены.СоздатьУзел();
НовыйУзел.Код = ИдентификаторКлиента;
НовыйУзел.Наименование = "Мобильный клиент: " + ИмяПользователя;
НовыйУзел.ДатаПоследнейСинхронизации = ТекущаяДата();
НовыйУзел.Записать();
УзелСсылка = НовыйУзел.Ссылка;
КонецЕсли;
Возврат УзелСсылка;
КонецФункции
Рассмотрим проблему раздувания таблиц регистрации изменений (сообщение №4). Опасения о кратном увеличении базы данных беспочвенны при условии регулярного обмена: как только клиент подтверждает прием пакета, регистрация изменений для него моментально очищается. Однако критически важно обрабатывать ситуации с «мертвыми» узлами (когда сотрудник потерял телефон, уволился или просто перестал пользоваться приложением).
Для борьбы с раздуванием базы из-за неактивных клиентов обязательно внедрим регламентное задание, выполняющее очистку устаревших регистраций:
Процедура ОчиститьУстаревшиеРегистрацииИУзлы() Экспорт
// Определяем границу неактивности (например, 30 дней)
ГраницаДаты = ТекущаяДата() - (30 * 24 * 60 * 60);
Выборка = ПланыОбмена.МобильныеОбмены.Выбрать();
Пока Выборка.Следующий() Цикл
// Пропускаем предопределенный узел (этот узел центральной базы)
Если Выборка.Ссылка = ПланыОбмена.МобильныеОбмены.ЭтотУзел() Тогда
Продолжить;
КонецЕсли;
// Проверяем дату последней активности
Если Выборка.ДатаПоследнейСинхронизации < ГраницаДаты Тогда
// Удаляем регистрацию изменений, чтобы не копились записи в СУБД
ПланыОбмена.УдалитьРегистрациюИзменений(Выборка.Ссылка);
// Опционально: можно пометить узел на удаление или удалить программно
ОбъектУзла = Выборка.ПолучитьОбъект();
ОбъектУзла.УстановитьПометкуУдаления(Истина);
ОбъектУзла.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если объем передаваемых данных крайне мал (как упомянуто в сообщении №9: несколько небольших справочников и регистров сведений общим объемом до 2–3 мегабайт), то использование планов обмена может быть избыточным. Мы можем полностью отказаться от планов обмена и применить схему синхронизации по дате изменения.
Рассмотрим алгоритм реализации этого подхода по шагам:
ДатаИзменения (тип Дата, состав даты: ДатаВремя).ПередЗаписью) программно фиксируем текущую дату и время в этом реквизите.ДатаПоследнейУспешнойСинхронизации.ДатаИзменения строго больше полученной от клиента метки времени.Посмотрим на пример серверного запроса для получения измененных элементов номенклатуры:
Функция ПолучитьИзмененияНоменклатуры(ДатаПоследнейСинхронизации) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Цена КАК Цена,
| Номенклатура.Остаток КАК Остаток
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ДатаИзменения > &ДатаПоследнейСинхронизации
| И Номенклатура.ПометкаУдаления = ЛОЖЬ";
Запрос.УстановитьПараметр("ДатаПоследнейСинхронизации", ДатаПоследнейСинхронизации);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Важный нюанс: данный метод требует отдельной обработки удаления объектов. Поскольку физически удаленный элемент нельзя найти запросом по дате изменения, необходимо создать специальный регистр сведений (например, РегистрацияУдаленныхОбъектов), куда при удалении элементов (в подписке ПередУдалением) будут записываться тип объекта, его уникальный идентификатор (GUID) и дата удаления. При синхронизации мобильный клиент должен также запрашивать данные из этого регистра, чтобы удалить соответствующие записи у себя локально.
В сообщениях форума №11 и №15 упоминается использование XML и правил Конвертации Данных. Однако для мобильных приложений на платформе 1С золотым стандартом является использование встроенных HTTP-сервисов и сериализации в формат JSON вместо XML.
Проанализируем преимущества этого подхода:
Посмотрим на пример кода, формирующего JSON-пакет для передачи на мобильное устройство:
Функция СформироватьПакетДанныхВФорматеJSON(ТаблицаДанных) Экспорт
ЗаписьJSON = Новый ЗаписьJSON;
НастройкиСериализации = Новый НастройкиСериализацииJSON;
НастройкиСериализации.ВариантЗаписиДаты = ВариантЗаписиДатыJSON.ЛокальнаяДатаСМещением;
ЗаписьJSON.УстановитьСтроку();
// Записываем структуру в JSON
ЗаписатьJSON(ЗаписьJSON, ТаблицаДанных, НастройкиСериализации);
РезультатСтрокой = ЗаписьJSON.Закрыть();
Возврат РезультатСтрокой;
КонецФункции
Для максимальной экономии трафика полученную строку JSON перед отправкой можно сжать методами платформы 1С:
// Сжатие данных в формат Deflate или Gzip
СжатыеДанные = Новый СжатиеДанных(9); // Максимальный уровень сжатия
ДвоичныеДанныеСтроки = ПолучитьДвоичныеДанныеИзСтроки(РезультатСтрокой);
АрхивированныеДанные = СжатыеДанные.Сжать(ДвоичныеДанныеСтроки);
В первом сообщении темы автор упоминает, что заказчик категорически отказывается от стандартного мобильного клиента из-за необходимости постоянной авторизации при каждом входе.
Для решения этой проблемы идеально подходит технология «Мобильный клиент с автономным режимом». Она объединяет в себе лучшие черты тонкого клиента и автономного мобильного приложения:
Обратим внимание на сообщение №9: пользователи активно отправляют сжатые фотографии с мобильных устройств в центральную базу. Передача тяжелых бинарных данных напрямую в рабочую базу через веб-сервисы может стать «бутылочным горлышком» всей системы. Одновременная загрузка картинок от 50+ пользователей создаст транзакционные блокировки и приведет к лавинообразному росту размера SQL-базы.
Рекомендуется придерживаться следующей архитектуры работы с медиафайлами:
Резюмируя все вышесказанное: для построения отказоустойчивой системы обмена данными с мобильными клиентами всегда используйте индивидуальные узлы планов обмена на сервере под каждое устройство, переходите на HTTP-сервисы с JSON-сериализацией и не пренебрегайте возможностями современных версий платформы в части автономного режима мобильного клиента.