Модульное приложение
Одной из задач Проекта Доминикана было построение приложения, состоящего из множества модулей. Модули нужны в проекте для облегчения поддержки сложного решения и для привлечения сторонних разработчиков с целью продажи их модулей. Данная статья - это попытка выработать подход к построению модульного приложения на платформе 1С.
Принципы построения модульных приложений на примере .Net framework можно почитать здесь: http://habrahabr.ru/post/176851/
Модульный подход применим для приложений, которые будут активно развиваться во время своей жизни - для приложений, которые построены надолго и для изменений. По другую сторону от модульных приложений стоят монолитные приложения, которые трудно и неэффективно поддерживать. Слово "монолитное" указывает на приложение, в котором компоненты очень тесно связаны, и между ними нет четкого разделения.
Модульный подход позволяет разделить компоненты на слабосвязанные части и поддерживать их разными командами разработчиков. Слабая зависимость позволяет снизить конфликты между независимо добавленным функционалом в разные модули.
Несмотря на то, что 1С выпустила БСП, как шаг к модульности приложений, хотелось бы видеть несколько альтернативных подходов. В некоторых применениях БСП может не подойти.
К статье приложена выгрузка информационной базы с конфигурацией, реализующей несколько модулей и демонстрирующей процесс инициализации модулей. Выгрузка сделана для 1С версии 8.3.3.
Нахождение модулей
В предложенном решении за нахождение модулей отвечает загрузчик, который ищет модули в конфигурации и составляет из них список. Список хранится в параметре сеанса МодулиПриложения. Параметр сеанса позволяет контролировать список доступных модулей, например, в зависимости от прав пользователя или наличия подписки.
Принцип нахождения модулей предложен следующий. Рекурсивно анализируются все подсистемы. Если подсистема содержит подсистему МенеджерМодуля, то считается, что данная подсистема - это модуль. Подсистема МенеджерМодуля содержит 2 общих модуля, выполняющихся на клиенте и сервере (наличие серверного модуля обязательно). Серверный модуль должен содержать функцию ПолучитьИнтерфейсы(), которая вернет массив строк - поддерживаемые модулем интерфейсы. Интерфейсы - это перечисление функциональных возможностей данного модуля.
Проверить наличие зарегистрированного в системе модуля можно через вызов
Если МодульноеПриложение.МодульОпределен("CRM_БазаКлиентов") Тогда
Сообщить("Модуль CRM_БазаКлиентов определен");
КонецЕсли;
Наименование объектов конфигурации
Для того, чтобы при добавлении объектов конфигурации в составе модулей от сторонних разработчиков не было конфликта имен, нужно обеспечить уникальность названий в конфигурации. Так, например, у разных поставщиков может быть справочник Настройки. Невозможно добавить справочники с одинаковыми наименованиями в одну конфигурацию. Поэтому в название нужно добавить префикс или постфикс, отличающих его от аналогичных.
В предложенном подходе принято расширять имя объекта через постфикс. Строка, добавленная в конец названия, позволяет осуществлять сортировку и быстрее выбирать в коде нужный объект. Способ может быть непривычным, поскольку в большинстве случаев в 1С принято применять префикс. Но оба способа имеют право на существование.
В префикс кроме вендора-разработчика модуля может быть полезно зашифровать название модуля, потому что, скорее всего, менеджеры различных модулей в рамках одного поставщика будут иметь одинаковые названия.
Интерфейсы
При инициализации модуль возвращает список поддерживаемых интерфейсов. Интерфейс - это заявление о том, что модуль реализует набор процедур или функций с заданными именами и заданными параметрами. Разработчики могут сами придумать соглашение о том, какие интерфейсы определены и к каким методам и функциям относятся.
Система интерфейсов позволяет обеспечить слабую связанность модулей и подписку на глобальные события системы. Выгода в том, что вызывающий модуль может не знать, кто подписан на него, а вызываемый модуль не будет содержать статической ссылки на вызывающий модуль.
Сейчас для примера добавлены следующие интерфейсы. Каждому интерфейсу соответствует одна процедура или функция, но в других случаях их может быть несколько:
Название интерфейса | Описание | Расположение |
УстановкаПараметровСеанса | Вызываются процедуры модуля, соответствующие одноименным событиям системы. Позволяют выполнить инициализацию модулей. | Сервер |
ПередНачаломРаботыСистемы | Клиент | |
ПриНачалеРаботыСистемы | Клиент | |
ФормаПриСозданииНаСервере | Вызывается в форме при создании ее на сервере. В параметр передается ссылка на форму. Форму можно идентифицировать по имени Форма.ИмяФормы. | Сервер |
ФормаПриОткрытии | Вызывается при открытии формы. В параметры передается ссылка на форму. Форму можно идентифицировать по имени Форма.ИмяФормы. | Клиент |
При вызове события происходит поиск модулей, реализующих заданный интерфейс, и вызов последовательно каждого модуля. Например, можно добиться следующим образом, что событие ПриОткрытии формы будет транслироваться всем модулям, реализующим интерфейс ФормаПриОткрытии:
Для каждого Модуль из МодульноеПриложение.НайтиМодулиПоИнтерфейсу("ФормаПриОткрытии") цикл
ОбщийМодуль = Вычислить(Модуль.ОбработчикКлиент);
ОбщийМодуль.ФормаПриОткрытии(Форма, Отказ);
КонецЦикла;
Модуль же будет в своем общем клиентском модуле содержать определение:
Процедура ФормаПриОткрытии(Форма, Отказ) Экспорт
Если Форма.ИмяФормы = "Справочник.ФизическиеЛица.Форма.ФормаСписка" Тогда
...
КонецЕсли;
КонецПроцедуры
Планируется, что через глобальную подписку на события формы можно будет изменять форму из любого подписавшегося модуля. Пока это предположение, так как есть некоторые непонятные технологические моменты, связанные с указанием обработчиков для команд и элементов управления формы.
Инициализация модулей
Инициализация модулей сейчас проходит при реализации ими интерфейсов:
УстановкаПараметровСеанса()
ПередНачаломРаботыСистемы()
ПриНачалеРаботыСистемы()
Для этого в модуле должны быть определены процедуры:
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры) Экспорт
Процедура ПередНачаломРаботыСистемы(Отказ) Экспорт
Процедура ПриНачалеРаботыСистемы() Экспорт
В этот момент модули могут проверить свои версии и доступные обновления, прочитать настройки и установить параметры пользователя.
Взаимодействие между слабо связанными компонентами
При создании большого и сложного приложения обычным подходом является разделение функциональности по модулям. Желательно минимизировать число статических ссылок между ними. Благодаря этому можно будет независимо разрабатывать, тестировать , развертывать и обновлять компоненты.
Стандартная подписка на события от 1С позволяет компонентам подписаться на события менеджеров и модулей объектов. При этом можно указать, как конкретные объекты, так и группу объектов в целом, например, ДокументОбъект.
Предложенный в статье механизм определения интерфейсов при поиске модулей позволяет реализовать альтернативную подписку на события, не реализованные в стандартных подписках на события от 1С. Например, можно обеспечить вызов таких событий в каждой форме или обработчике элемента управления.
Если нет желания, чтобы модули связывались между собой непосредственно, можно сделать так, чтобы они связывались косвенно через совместно используемый ресурс, такой как регистр или журнал документов. При таком подходе один модуль записывает данные, другой - считывает их.
Составные интерфейсы пользователя
Модульные приложения часто должны предоставлять цельный интерфейс для пользователя, когда один модуль отвечает за одну часть элементов управления, а другой модуль - за другую часть.
Интерфейсная часть модульного подхода применительно к 1С усиленно не прорабатывалась на практике, есть только некоторые мысли на этот счет.
Подписка одного модуля на события создания и открытия формы из другого модуля через реализацию интерфейса ФормаПриСозданииНаСервере и ФормаПриОткрытии может позволить внедрить элементы формы динамически. Для обратной связи можно реализовать интерфейсы, реагирующие на закрытие формы. Хорошо бы было, если бы появился механизм копирования элементов из одной формы в другую. Но при этом необходимо решать проблему переноса обработчиков элементов. Похожий механизм был реализован для неуправляемых форм, например, здесь: http://kb.mista.ru/article.php?id=165
Интересным является обработчик переопределения открытия форм ОбработкаПолученияФорм в менеджере объекта . В случае, если есть главный и зависимый модуль, зависимый модуль может реализовать свою новую форму, включающую функциональность главного модуля и свой. Затем через ОбработкаПолученияФорм подменить своей формой форму главного модуля. Например, есть модуль продажи и зависимый от него модуль резервирования. Модуль продажи содержит свою форму документа реализации, которая показывается, если не подключен модуль резервирования. Как только в системе появляется модуль резервирования, он может переопределить форму продажи, дополнив ее своими реквизитами. При таком подходе модуль продаж может даже не догадываться о существовании модуля резервирования.