Интеграционный слой

Интеграционный слой #

Интеграционный слой предоставляет высокоуровневый 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
2
3
4
public interface IBmPlatformTask<T>
{
    T execute(IBmPlatformTransaction transaction);
}

Понятие контекста редактирования #

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

Основной метод интерфейса IBmPlatformEditingContextexecute. Он выполняет задачу БМ в данном контексте. При успешном выполнении задачи (если в процессе выполнения задачи не было выброшено исключение) данная задача добавляется в историю изменений данного контекста.

Контекст помечается как dirty. Это означает, что он содержит изменения, не сохраненные в ресурсах рабочей области. При этом стоит заметить, что модификации уже отражены в основном хранилище БМ и, таким образом, видны всем остальным пользователям БМ.

Также при успешном выполнении данного метода производится рассылка событий БМ:

1
2
3
4
public interface IBmPlatformEditingContext
{
    <T> T execute(String name, Object taskId, Object serviceId, IBmPlatformTask<T> task);
}

Параметры:

  • Параметр name (обязательный) — локализованное имя выполняемой задачи. Это имя будет фигурировать, например, рядом с пунктами Undo и Redo в меню Edit.
  • Параметры taskId и serviceId (опциональные) — необходимы для идентификации источника события (механизм событий будет описан далее). Они могут использоваться редакторами для фильтрации событий, вызванных выполненными ими же задачами.
  • Параметр task (обязательный) — выполняемая задача.

Локальный контекст редактирования #

Локальные контексты предназначены для использования при реализации редакторов Eclipse. Задачи, выполненные в локальном контексте, могут отменяться и повторяться (в случае, если нет конфликтующих изменений в других контекстах). Сохранение изменений в ресурсах рабочей области происходит только при явном вызове метода save. При вызове метода dispose все несохраненные изменения отменяются.

Локальный контекст представлен интерфейсом IBmPlatformLocalEditingContext, расширяющим IBmPlatformEditingContext:

  • canUndo, canRedo — проверяют возможность выполнения операций отмены и повтора, соответственно.
  • undo, redo — производят операции отмены и повтора, соответственно.
  • isDirty — проверяет наличие изменений, не отраженных в ресурсах рабочей области.
  • save — при наличии изменений, не отраженных в ресурсах рабочей области, производит сохранение изменений в ресурсах рабочей области.
  • dispose — уничтожает данный контекст редактирования.
  • addStateListener — добавляет подписчик, вызываемый при изменении состояния контекста редактирования.
  • removeStateListener — удаляет подписчик, вызываемый при изменении состояния контекста редактирования.
  • isDisposed — проверяет, является ли контекст редактирования уничтоженным.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface IBmPlatformLocalEditingContext
    extends IBmPlatformEditingContext
{
    boolean canUndo();
 
    boolean canRedo();
 
    void undo();
 
    void redo();
 
    boolean isDirty();
 
    void save();
 
    void save(boolean sync);
 
    void dispose();
 
    void addStateListener(IBmEditingContextStateListener listener);
 
    void removeStateListener(IBmEditingContextStateListener listener);
 
    boolean isDisposed();
}

Глобальный контекст редактирования #

Глобальный контекст редактирования предназначен для выполнения всех задач, не привязанных к определенному редактору, т. е. манипуляции в различных панелях, рефакторинг и др. После успешного выполнения задачи в глобальном контексте сохранение производится автоматически. Задачи, выполненные в глобальном контексте, в текущей версии не могут быть отменены или повторены. В будущих версиях такая возможность может быть добавлена.

Глобальный контекст представлен интерфейсом IBmPlatformGlobalEditingContext и предоставляет один дополнительный метод по сравнению с базовым интерфейсом контекста executeImportTask. Он выполняет задачу импорта в глобальном контексте, при этом происходит рассылка событий, сохранение всех несохраненных локальных контекстов и уничтожение их истории:

1
2
3
4
5
public interface IBmPlatformGlobalEditingContext
    extends IBmPlatformEditingContext
{
    <T> T executeImportTask(String name, Object taskId, Object serviceId, IBmPlatformTask<T> task);
}

Обработка отмены / повтора задачи #

Для обработки undo / redo какой-либо задачи используется интерфейс IBmPostUndoRedoHandler, который эта задача должна реализовывать. onUndo / onRedo выполняются по факту отмены и повтора задачи, соответственно. Эти методы предназначены для расположения в них нетранзакционной логики, не затрагивающей данные БМ. Например, для рассылки бизнес-событий:

1
2
3
4
5
public interface IBmPostUndoRedoHandler
{
    void onUndo();
    void 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.

Примеры #

Чтение данных справочника #

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

  1. Получить собственно справочник. В зависимости от ситуации, это может быть получение объекта по его FQN, либо получение объекта из Selection со стороны пользовательского интерфейса и т. п.
  2. Сформировать задачу (IBmPlatformTask), осуществляющую получение необходимых данных из объекта метаданных типа Catalog. В этой задаче:
    1. Полученный справочник актуализируется, подключаясь к транзакции, либо получается непосредственно из транзакции.
    2. Производится чтение данных справочника.
    3. Возвращается результат (опционально).
  3. Выполнить эту задачу в режиме “только чтение”.

Теперь реализуйте подготовленный план в виде программного кода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Пример получения данных из объекта метаданных типа Справочник с заданным FQN
 *
 * @param modelManager Менеджер модели данных EDT
 * @param namespace Пространство имен проекта, которому принадлежит справочник
 * @param catalogFqn FQN справочника
 */
public void processCatalogData(IBmModelManager modelManager, IBmNamespace namespace, String catalogFqn) {
    // В нашем примере мы получим справочник по его FQN. Поэтому его можно получить непосредственно в самом
    // теле IBmPlatformTask
 
    IBmPlatformTask<Integer> getCodeLengthTask = new IBmPlatformTask<>()
    {
        @Override
        public Integer execute(IBmPlatformTransaction transaction)
        {
            // Шаг 1: Получаем справочник по его FQN
            Catalog catalog = (Catalog)transaction.getTopObjectByFqn(namespace, catalogFqn);
  
            // Шаг 2: Получаем искомые данные из справочника
            Integer codeLength = catalog.getCodeLength();
  
            // Шаг 3: И возвращаем их в качестве результата работы IBmTask
            return codeLength;
        }
    };
 
    // Задача сформирована — остается ее выполнить. Поскольку мы знаем, что данная задача не модифицирует
    // данные, разумно выполнить ее в режиме "только чтение".
    // Результат, возвращаемый задачей после выполнения, будет получен в качестве результата выполнения
    // метода IBmModelManager#executeReadOnlyTask
    Integer codeLength = modelManager.executeReadOnlyTask(getCodeLengthTask);
 
    // Убеждаемся, что получили искомые данные
    System.out.println(codeLength);
}

Обратите внимание на следующие моменты:

  • IBmPlatformTask может возвращать значение в качестве результата выполнения. Тип данного значения задается параметром класса IBmPlatformTask. В данном примере вы использовали результат типа IntegerIBmPlatformTask<Integer>.
  • Само тело задачи задается в методе execute, который возвращает значение типа, указанного вами ранее через параметризацию задачи — public Integer execute(IBmPlatformTransaction transaction).
  • Все объекты модели, используемые внутри задачи, должны быть присоединены к транзакции. Для этого все объекты модели должны быть получены через объект IBmPlatformTransaction, передаваемый в качестве параметра метода executepublic Integer execute(IBmPlatformTransaction transaction). Также объекты модели могут быть получены путем считывания значения ссылки другого объекта модели, уже присоединенного к данной транзакции.
  • В случае если вам не нужно возвращать значение из вашей задачи, можно использовать тип Void. Задача в этом случае создается так: IBmPlatformTask<Void> someTask = new IBmPlatformTask<>() с соответствующим типом в методе executepublic 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.