Интеграционный слой #
Интеграционный слой предоставляет высокоуровневый API, позволяющий реализовать редакторы и панели Eclipse, используя в качестве основного хранилища данных платформу БМ. В частности, интеграционный слой предоставляет следующую функциональность:
- разделение контекстов редактирования;
- отслеживание истории изменений и выполнение операций отмены / повтора;
- отслеживание изменений, которые не отражены в ресурсах рабочей области;
- сохранение изменений в ресурсах рабочей области;
- рассылка событий об изменениях в объектной модели.
Интерфейсы и реализация #
Интерфейсы компонентов интеграционного слоя располагаются в пакетах
com._1c.g5.v8.bm.integration и
com._1c.g5.v8.bm.integration.event плагина com._1c.g5.v8.bm.integration
; реализации данных компонентов, а также сервисы, обеспечивающие доступ к ним, располагаются в плагине com._1c.g5.v8.dt.core
. Хотя такое разделение и является скорее результатом эволюционного развития БМ и 1С:EDT, нежели объективной необходимости, оно имеет, однако, свои преимущества. Например, такой подход позволяет реализовать интеграцию более эффективно за счет учета специфики 1С:EDT.
Платформа #
Как говорилось в документе, посвященном объектному хранилищу, при разработке плагинов для 1C:EDT, платформа БМ создается инфраструктурой 1C:EDT автоматически. Вам достаточно получить платформу путем вызова метода getBmPlatform()
сервиса IBmModelManager
из пакета
com._1c.g5.v8.dt.core.platform.
Пространства имен и проекты #
Также инфраструктура 1C:EDT при создании в рабочей области проекта автоматически создает пространство имен и хранилище для него, а при удалении, соответственно, — удаляет. Имя пространства имен всегда совпадает с именем проекта. Для получения пространства имен проекта интерфейс IBmModelManager
предоставляет методы getBmNamespace(IProject)
и getBmNamespace(IDtProject)
. При закрытии проекта соответствующее пространство имен деактивируется. В случае обращения к деактивированному пространству имен БМ выбрасывает исключения. При открытии проекта пространство имен активируется.
Понятие задачи #
Задача БМ — unit of work, объединяющий некоторый набор операций по редактированию данных БМ, выполнение которых должно приводить модель в согласованное (англ. consistent — см. принцип ACID) с точки зрения прикладного решения состояние.
Для выполнения задачи БМ необходимо создать класс, реализующий интерфейс IBmPlatformTask
и содержащий логику по редактированию данных, и передать экземпляр этого класса в качестве параметра при вызове одного из следующих методов:
IBmModelManager.executeReadOnlyTask
,IBmModelManager.executeReadWriteTask
,IBmPlatformEditingContext.execute
,IBmPlatformGlobalEditingContext.executeImportTask
.
Интерфейс IBmPlatformTask
декларирует единственный метод execute
, в котором реализации этого интерфейса размещают логику, отвечающую непосредственно за работу с данными БМ. Работа с данными должна производиться в рамках транзакции, которая передается в данный метод в качестве параметра. При этом в данном методе не должны выполняться фиксация или откат транзакции. Компонент IBmModelManager
сам выполняет фиксацию транзакции в случае, если задача была выполнена успешно, и откат, если при выполнении задачи было выброшено исключение. Также IBmModelManager
при выбросе BmDeadlockDetectedException
выполняет повторные попытки выполнения задачи:
|
|
Понятие контекста редактирования #
Контексты редактирования предоставляют возможность отделения модификаций произведенных в разных редакторах. Таким образом предоставляется возможность применения и отката задач, выполненных в данном контексте, независимо от других контекстов (естественно, с ограничениями, связанными с особенностями редактирования конфигураций 1С).
Основной метод интерфейса IBmPlatformEditingContext
— execute
. Он выполняет задачу БМ в данном контексте. При успешном выполнении задачи (если в процессе выполнения задачи не было выброшено исключение) данная задача добавляется в историю изменений данного контекста.
Контекст помечается как dirty
. Это означает, что он содержит изменения, не сохраненные в ресурсах рабочей области. При этом стоит заметить, что модификации уже отражены в основном хранилище БМ и, таким образом, видны всем остальным пользователям БМ.
Также при успешном выполнении данного метода производится рассылка событий БМ:
|
|
Параметры:
- Параметр
name
(обязательный) — локализованное имя выполняемой задачи. Это имя будет фигурировать, например, рядом с пунктами Undo и Redo в меню Edit. - Параметры
taskId
иserviceId
(опциональные) — необходимы для идентификации источника события (механизм событий будет описан далее). Они могут использоваться редакторами для фильтрации событий, вызванных выполненными ими же задачами. - Параметр
task
(обязательный) — выполняемая задача.
Локальный контекст редактирования #
Локальные контексты предназначены для использования при реализации редакторов Eclipse. Задачи, выполненные в локальном контексте, могут отменяться и повторяться (в случае, если нет конфликтующих изменений в других контекстах). Сохранение изменений в ресурсах рабочей области происходит только при явном вызове метода save
. При вызове метода dispose
все несохраненные изменения отменяются.
Локальный контекст представлен интерфейсом IBmPlatformLocalEditingContext
, расширяющим IBmPlatformEditingContext
:
canUndo
,canRedo
— проверяют возможность выполнения операций отмены и повтора, соответственно.undo
,redo
— производят операции отмены и повтора, соответственно.isDirty
— проверяет наличие изменений, не отраженных в ресурсах рабочей области.save
— при наличии изменений, не отраженных в ресурсах рабочей области, производит сохранение изменений в ресурсах рабочей области.dispose
— уничтожает данный контекст редактирования.addStateListener
— добавляет подписчик, вызываемый при изменении состояния контекста редактирования.removeStateListener
— удаляет подписчик, вызываемый при изменении состояния контекста редактирования.isDisposed
— проверяет, является ли контекст редактирования уничтоженным.
|
|
Глобальный контекст редактирования #
Глобальный контекст редактирования предназначен для выполнения всех задач, не привязанных к определенному редактору, т. е. манипуляции в различных панелях, рефакторинг и др. После успешного выполнения задачи в глобальном контексте сохранение производится автоматически. Задачи, выполненные в глобальном контексте, в текущей версии не могут быть отменены или повторены. В будущих версиях такая возможность может быть добавлена.
Глобальный контекст представлен интерфейсом IBmPlatformGlobalEditingContext
и предоставляет один дополнительный метод по сравнению с базовым интерфейсом контекста executeImportTask
. Он выполняет задачу импорта в глобальном контексте, при этом происходит рассылка событий, сохранение всех несохраненных локальных контекстов и уничтожение их истории:
|
|
Обработка отмены / повтора задачи #
Для обработки undo / redo какой-либо задачи используется интерфейс IBmPostUndoRedoHandler
, который эта задача должна реализовывать. onUndo
/ onRedo
выполняются по факту отмены и повтора задачи, соответственно. Эти методы предназначены для расположения в них нетранзакционной логики, не затрагивающей данные БМ. Например, для рассылки бизнес-событий:
|
|
Редактирование вне контекста #
Иногда возникает необходимость выполнения задач, которые не должны попадать в историю изменений, а модификации модели, которые они производят, не должны отражаться в ресурсах рабочей области. Например, к такому классу задач относится расчет производных данных объектов.
Специально для этих целей на интерфейсе IBmModelManager
был введен метод executeReadWriteTask
. При успешном выполнении данного метода производится сохранение произведенных изменений в объектном хранилище БМ, производится рассылка событий, но не производится добавление задачи в историю изменений, и не производится сохранение изменений в ресурсах рабочей области.
События #
Интерфейс IBmModelManager
предоставляет методы для подписки на синхронную и асинхронную рассылку событий. Асинхронная рассылка событий производится в фоновом потоке. Синхронная рассылка производится в потоке выполнения задачи сразу после ее успешного завершения. Поскольку синхронная рассылка событий удерживает рабочий поток, не следует подписываться на синхронную рассылку событий без крайней на то необходимости.
В обработчик события БМ (и в синхронный, и в асинхронный) в качестве параметра передается экземпляр класса BmEvent
(см. пакет
com._1c.g5.v8.bm.core.event), содержащий информацию обо всех изменениях в одном определенном пространстве имен, произведенных в рамках задачи БМ. Если в рамках задачи БМ были модифицированы данные нескольких пространств имен, то будет разослано по событию на каждое пространство имен. Класс BmEvent
предоставляет следующие методы:
getTimestamp()
— данный метод возвращает логическую временную отметку. Логические временные отметки позволяют устанавливать частичный порядок событий. Так, порядок может быть установлен только в том случае, если в рамках задач были модифицированы только пересекающиеся множества ресурсов.getOperationId()
— данный метод возвращает идентификатор операции, вызвавшей данное событие (см. методIBmPlatformEditingContext.execute
).getServiceId()
— данный метод возвращает идентификатор сервиса, вызвавшего данное событие (см. методIBmPlatformEditingContext.execute
).getNamespace()
— данный метод возвращает пространство имен, к которому относятся изменения.BmAssociationEvent getAssociationEvent()
— данный метод возвращает экземплярBmAssociationEvent
в том случае, если в рамках выполнения задачи производились операции по добавлению или удалению объектов БМ. В противном случае возвращаетсяnull
. ОбъектBmAssociationEvent
содержит информацию обо всех добавленных и удаленных объектах.IBmLongMap<BmChangeEvent> getChangeEvents()
— данный метод возвращаетmap
, в котором в роли ключа выступает целочисленный идентификатор модифицированного объекта, а в роли значения — экземплярBmChangeEvent
, содержащий информацию о том, какие features и свойства были изменены. Если в рамках выполненной задачи БМ не было модификаций, то возвращаетсяnull
.getBlobEvent()
— данный метод возвращает экземплярBmBlobEvent
в том случае, если в рамках задачи производились операции по добавлению, редактированию или удалению бинарных данных. В противном случае возвращаетсяnull
.
Примеры #
Чтение данных справочника #
Для чтения данных справочника вам необходимо выполнить следующие действия:
- Получить собственно справочник. В зависимости от ситуации, это может быть получение объекта по его FQN, либо получение объекта из
Selection
со стороны пользовательского интерфейса и т. п. - Сформировать задачу (
IBmPlatformTask
), осуществляющую получение необходимых данных из объекта метаданных типаCatalog
. В этой задаче:- Полученный справочник актуализируется, подключаясь к транзакции, либо получается непосредственно из транзакции.
- Производится чтение данных справочника.
- Возвращается результат (опционально).
- Выполнить эту задачу в режиме “только чтение”.
Теперь реализуйте подготовленный план в виде программного кода:
|
|
Обратите внимание на следующие моменты:
IBmPlatformTask
может возвращать значение в качестве результата выполнения. Тип данного значения задается параметром классаIBmPlatformTask
. В данном примере вы использовали результат типаInteger
—IBmPlatformTask<Integer>
.- Само тело задачи задается в методе
execute
, который возвращает значение типа, указанного вами ранее через параметризацию задачи —public Integer execute(IBmPlatformTransaction transaction)
. - Все объекты модели, используемые внутри задачи, должны быть присоединены к транзакции. Для этого все объекты модели должны быть получены через объект
IBmPlatformTransaction
, передаваемый в качестве параметра методаexecute
—public Integer execute(IBmPlatformTransaction transaction)
. Также объекты модели могут быть получены путем считывания значения ссылки другого объекта модели, уже присоединенного к данной транзакции. - В случае если вам не нужно возвращать значение из вашей задачи, можно использовать тип
Void
. Задача в этом случае создается так:IBmPlatformTask<Void> someTask = new IBmPlatformTask<>()
с соответствующим типом в методеexecute
—public Void execute(IBmPlatformTransaction transaction)
. - Для
Void
в конце задачи необходимо вернутьnull
в качестве результата —return null
.
Поддержка старого API #
Как уже упоминалось в документе, посвященном платформе БМ, до появления версии 1С:EDT 2023.1 на каждый проект создавался отдельный экземпляр БМ, представляемый интерфейсом IBmEngine
. Соответственно, на интеграционном слое на каждый проект создавался отдельный экземпляр IBmModel
, представлявший собой фасад, посредством которого пользователь API получал доступ к основной функциональности интеграционного слоя. С помощью IBmModel
осуществлялось создание контекстов редактирования (IBmEditingContext
), подписка на события, выполнение задач, результаты которых не должны были отражаться в ресурсах рабочей области, и прочее. В новом же решении, как было показано в данном документе, все эти действия выполняются посредством вызова методов, предоставляемых компонентом IBmModelManager
.
Однако, так же как и в случае с платформой, с целью упрощения миграции было принято решение не упразднять старый API, а оставить его как средство временной адаптации старых решений. Так, реализации IBmModel
и IBmEditingContext
являются всего лишь обертками, делегирующими выполнение задач и других функций новым компонентам.
Получить информацию по старому API вы можете, изучив документацию, посвященную версиям EDT, предшествующим 1С:EDT 2023.1. Здесь мы только отметим сходства и отличия старого и нового решений:
- В новом решении реализации всех компонентов скрыты. Соответственно, вы не можете сами создать экземпляр
IBmModel
, а можете только получить его, воспользовавшись одним из методовgetModel
компонентаIBmModelManager
. - Интерфейс
IBmEditingContext
и его наследники аналогичны интерфейсуIBmPlatformEditingContext
за исключением того, что метод выполнения задачIBmEditingContext
принимает экземплярIBmTask
, а неIBmPlatformTask
. - Интерфейс
IBmTask
, в свою очередь, отличается отIBmPlatformTask
тем, что принимает экземплярIBmTransaction
, а неIBmPlatformTransaction
. - Также интерфейс
IBmTask
, в отличие отIBmPlatformTask
, помимо методаexecute
определяет методыgetName
,getId
иgetServiceId
, т. е. не является функциональным. Таким образом, в методы, принимающие экземплярIBmTask
, не может быть передана лямбда. - В старом решении рекомендуется не реализовывать интерфейс
IBmTask
с нуля, а расширять базовый классAbstractBmTask
.