Платформа БМ #
Платформа БМ предоставляет следующие возможности, отсутствующие в EMF:
- транзакционное редактирование,
- блокировки,
- быстрый поиск объекта по идентификатору,
- индексирование,
- контроль за расходом памяти,
- эффективное использование файловой системы,
- парциальная загрузка данных.
Краткий пример использования #
Предположим, вы разрабатываете собственную систему управления задачами. Центральной сущностью такой системы будет задача: у задачи есть заголовок, описание, статус, ссылка на автора, ссылка на исполнителя, ссылки на блокирующие и блокируемые задачи и список комментариев. Комментарий к задаче также представляет собой отдельную сущность: у комментария есть текст, ссылка на автора и список комментариев-ответов. Кроме того, есть пользователи, которые выступают в роли авторов и исполнителей задач. Данные сущности и их взаимоотношения показаны на UML-диаграмме ниже:
Для наглядности ниже показана логическая структура фрагмента модели данных системы управления задачами в формате JSON:
|
|
Обратите внимание на следующие моменты:
- Задача и ее комментарии образуют дерево. При этом задача является корневым объектом дерева, а комментарий — вложенным объектом задачи или комментария, ответом на который он является. Таким образом, в терминах объектно-ориентированного программирования объекты в родительских и дочерних узлах находятся в отношении композиции. Это отношение на диаграмме классов UML обозначается стрелкой с черным ромбом.
- Хотя модель древовидная, в ней присутствуют и горизонтальные связи. Так, задача ссылается на блокирующие и блокируемые задачи, на создавшего задачу пользователя, а также на ответственного за ее выполнение. В данном случае они закодированы с помощью некоторых символических имен. Такие ссылки в EMF называются кросс-ссылками, а их семантика определяется разработчиком. На диаграммах классов они обозначаются стрелкой с белым ромбом.
Теперь, когда у вас есть концептуальное описание модели, вам необходимо описать ее в терминах понятных EMF. Прежде всего не забудьте включить в список зависимостей своего проекта пакеты из плагина org.eclipse.emf.ecore, а также пакет com._1c.g5.v8.bm.core из одноименного плагина. Для декларативного описания EMF-классов используйте язык Xcore. Создайте файл task-tracker.xcore и скопируйте в него код, представленный ниже.
|
|
Здесь вы видите стандартный подход к описанию EMF-модели с использованием Xcore. Единственный момент, на котором стоит заострить внимание, — это настройки генератора:
- БМ поддерживает только рефлективное делегирование (стр. 8).
- Классы модели должны расширять класс
BmObject
(стр. 9) и реализовывать интерфейсIBmObject
(стр. 10).
Когда вы сохраните описание модели в файле xcore, EMF автоматически сгенерирует модель. При этом на выходе вы получите:
- несколько классов и интерфейсов, реализующих собственно модель,
- фабрику объектов модели,
- а также классы, предоставляющие различную метаинформацию.
После того как модель описана, и по ней сгенерированы Java-классы, можете приступать к конфигурированию и старту платформы БМ:
|
|
Затем необходимо создать пространство имен и хранилище данных для пространства имен, после чего связать их. Пространства имен и хранилища данных используются в БМ для разделения объектов. Например, в случае с системой управления задачами вы можете создать пространство имен и хранилище для каждого проекта.
|
|
Забегая вперед, скажем, что при разработке плагинов для EDT создавать платформу БМ, пространства имен и хранилища данных вам не придется: платформа EDT создаст их сама. Вам достаточно получить их, используя сервис IBmModelManager
из пакета
com._1c.g5.v8.dt.core.platform. Самостоятельное их создание может понадобиться при разработке тестов, не использующих
общую тестовую инфраструктуру.
Теперь, когда все готово к работе, рассмотрите, как осуществляется процесс редактирования персистентной модели. Во-первых, для осуществления редактирования модели необходимо создать транзакцию. Во-вторых, чтобы сделать объект персистентным, его необходимо подключить к БМ.
Подключить объект к БМ можно двумя способами:
- если объект является объектом верхнего уровня (top object в терминах БМ или root object в терминах EMF), то его подключение осуществляется посредством вызова метода
IBmPlatformTransaction.attachTopObject
; - если объект является вложенным (contained), то его нужно добавить в объект-контейнер, подключенный к модели.
Рассмотрите процесс создания пользователя:
|
|
В этом отрывке кода вы видите следующие действия:
-
В стр. 3 вы начинаете транзакцию на чтение и запись посредством вызова метода
beginReadWriteTransaction
. -
В стр. 9 вы создаете экземпляр класса
User
. -
В стр. 10 вы подключаете экземпляр класса
User
к БМ как объект верхнего уровня, располагая его в созданном выше пространстве имен. Третьим параметром в методattachTopObject
передается FQN подключаемого объекта. FQN уникальным образом адресует объект верхнего уровня в пространстве имен. Таким образом, вы можете добавить объекты с одинаковым FQN в разных пространствах имен, но при попытке добавить объекты с одинаковым FQN в одном пространстве имен БМ выбросит исключение. -
В стр. 12-16 вы наполняете объект
User
данными. -
В стр. 18 вы получаете идентификатор объекта
User
, который назначила ему платформа БМ при подключении. -
В стр. 26 вы фиксируете транзакцию, сохраняя, тем самым, произведенные изменения в долговременном хранилище.
-
В стр. 28 вы возвращаете идентификатор объекта, который затем может быть использован клиентом метода для получения созданного объекта.
-
В стр. 22 вы откатываете транзакцию в случае возникновения ошибки. Например, вызов метода
attachTopObject
в стр. 9 может выброситьBmFqnAlreadyInUseException
в случае, если объект с указанным FQN уже существует.
Далее вы можете в любое время получить добавленные объекты, отредактировать их, добавить новые, связать их с уже существующими и так далее.
|
|
В этом отрывке кода вы видите следующие действия:
- производится получение ранее созданного объекта
User
(стр. 9); - поскольку предполагается, что в системе управления задачами пользователи не удаляются, то, если пользователь не найден, производится выброс ошибки (стр. 10);
- создается объект
Task
и подключается к БМ (стр. 12, 13); - объект
Task
наполняется данными (стр. 15, 17); - объекту
Task
в полеcreator
устанавливается ссылка на существующий экземпляр классаUser
(стр. 19).
Обратите внимание, что в стр. 13 FQN для объекта Task
генерируется на основе строки, полученной путем удаления из заголовка всех символов, не являющихся буквами и цифрами. Стоит заметить, что это не очень хорошее решение, т. к.: во-первых, оно не гарантирует уникальность, во-вторых, пользы от такого FQN нет никакой, ведь его по очевидным причинам вряд ли получится использовать для поиска задач по заголовку (для этого лучше использовать отдельный индекс полнотекстового поиска). Соответственно, в реальной системе логичным решением было бы вообще не назначать FQN объекту Task
.
Платформа #
Инициация любого взаимодействия клиентского кода с платформой БМ всегда осуществляется посредством вызова одного из методов класса BmPlatform
. Таким образом, класс BmPlatform
можно представить как точку входа, через которую вы получаете доступ к данным, находящимся под управлением платформы.
Создание платформы #
Создать экземпляр BmPlatform
можно только посредством вызова статического метода BmPlatform.createPlatform(BmPlatformConfiguration)
. Данный метод создает платформу с настройками, переданными в объекте BmPlatformConfiguration
. Следующие настройки являются обязательными и должны быть явно указаны пользователем:
path
— путь к каталогу, в котором будут располагаться файлы платформы,ePackages
— коллекция EMF-пакетов, с объектами которых может работать БМ.
Остальные настройки являются опциональными. Таким образом, если вы не укажете какой-либо параметр, то БМ будет использовать стандартное значение. Вы можете указать следующие настройки:
lockWaitTimeout
— максимальное время ожидания установки блокировки объекта в транзакции в миллисекундах.checkpointPeriod
— максимальное время между выполнением контрольных точек в миллисекундах.walSizeThreshold
— размер журнала предзаписи (англ. WAL, write-ahead log) в байтах, при превышении которого выполняется контрольная точка и журнал сбрасывается.forceSync
— включает режим принудительной синхронизации, в котором все записи в файлы хранилищ платформы сразу отражаются на диске.enableMonitoring
— включает режим мониторинга.indexedAttributes
— индексируемые атрибуты.containedObjectIndexingRules
— правила индексации вложенных объектов.executorService
— экземплярjava.util.concurrent.ExecutorService
для выполнения служебных фоновых процессов.failureListener
— подписчик, который уведомляется при обнаружении сбоя в хранилище платформы.logger
— логгер.unfinishedCommitProcessor
— подписчик, который уведомляется о фиксациях транзакций, выполнение которых было прервано аварийным завершением предыдущей сессии работы БМ, и которые были завершены в рамках текущей сессии.uriBuildContributors
— компоненты, посредством поставки которых клиенты БМ могут настраивать процесс построения URI объектов БМ.crossReferenceFilters
— компоненты, посредством поставки которых клиенты БМ могут настраивать процесс фильтрации обратных ссылок.externalUriResolvers
— компоненты, посредством которых клиенты БМ могут обеспечивать возможность разрешения любых EMF-объектов, не хранящихся в БМ.referencePersistenceContributors
— компоненты для эффективного хранения и разрешения ссылок на любые EMF-объекты, не хранящиеся в БМ. Данный механизм является улучшенной альтернативой механизму externalUriResolvers.attributeSerializers
— сериализаторы атрибутов. Значение использованных терминов будет приведено далее в этом документе.
Обзор основных методов #
-
void close()
— производит останов платформы. В самом начале этого метода устанавливается признак неактивности платформы: любые обращения к ней, как в этом потоке, так и в других не допускаются. -
IBmNamespaceDataStore getNamespaceDataStore(String name)
— получает хранилище данных по имени. -
IBmNamespaceDataStore createNamespaceDataStore(String name)
— создает хранилище данных с указанным именем. -
void deleteNamespaceDataStore(IBmNamespaceDataStore store)
— удаляет хранилище данных с указанным именем. -
IBmNamespace getNamespace(String name)
— получает пространство имен по имени. -
IBmNamespace createNamespace(String name, BmNamespaceConfiguration configuration)
— создает пространство имен с указанными именем и настройками. -
void assignStore(IBmNamespace namespace, IBmNamespaceDataStore store)
— назначает хранилище пространству имен. -
void unassignStore(IBmNamespace namespace)
— отменяет назначение хранилища пространству имен. -
void deleteNamespace(IBmNamespace namespace)
— удаляет пространство имен. -
IBmPlatformTransaction beginReadWriteTransaction()
— начинает транзакцию на редактирование. -
IBmPlatformTransaction beginReadOnlyTransaction()
— начинает транзакцию на чтение. -
IBmPlatformTransaction getCurrentTransaction()
— получает транзакцию, открытую в текущем потоке.
Пространства имен и хранилища данных #
Как уже упоминалось в примере использования БМ в начале этого документа, при добавлении объектов в БМ необходимо указывать пространство имен, в котором будет размещен объект, а при получении — в котором он находится. Таким образом, пространства имен позволяют осуществлять разделение более или менее независимых групп объектов. Так, например, в 1С:EDT для каждого проекта создается собственное пространство имен. Такой подход позволяет: во-первых, решить проблему наличия объектов с одинаковыми именами (и, следовательно, именами FQN) в разных проектах, во-вторых, при выполнении операции удаления проекта удалять все данные разом вместо удаления объектов по одному (как в случае, если бы данные всех проектов хранились бы в одном пространстве имен). При этом пространства имен не являются полностью изолированными, так как объекты из одного пространства имен могут ссылаться на объекты из другого пространства имен. В 1С:EDT это используется, например, для обеспечения возможности ссылаться на объекты проекта конфигурации из проектов внешних отчетов, внешних обработок и расширений.
Также в примере было упомянуто, что, прежде чем начать работу с созданным пространством имен, его необходимо связать с хранилищем данных. Здесь у вас может возникнуть вопрос: зачем вообще разделять эти сущности? Почему недостаточно только пространств имен? Такое разделение было осуществлено для обеспечения возможности мгновенного выполнения подмены содержимого пространств имен. Например, это можно использовать для реализации быстрого переключения веток. Так, данные каждой ветки можно размещать в разных хранилищах, а при переключении веток отвязывать пространство имен от хранилища текущей ветки и привязывать к хранилищу целевой ветки. Таким образом, пространства имен можно рассматривать как средство логического разделения данных, а хранилища — физического.
Идентификация объектов #
Целочисленные идентификаторы #
Каждый объект БМ имеет свой собственный уникальный идентификатор типа long
, который ему назначается в процессе добавления объекта в модель. Этот идентификатор не меняется на протяжении всего времени жизни объекта. Идентификаторы генерируются в соответствии с внутренней логикой БМ и на процесс их формирования вы не можете оказать влияния. Для получения объектов по целочисленному идентификатору интерфейс транзакции IBmPlatformTransaction
предоставляет метод getObjectById(IBmNamespace, long)
.
FQN #
Кроме этого, у всех объектов верхнего уровня есть еще один идентификатор — FQN. «FQN» — не самое удачное название, но оно привычно для разработчиков 1C:EDT. FQN представляет собой обычную строку, которую вы задаете при добавлении объекта в модель посредством вызова метода attachTopObject(IBmNamespace, IBmObject, String)
интерфейса IBmPlatformTransaction
. В отличие от внутренних идентификаторов, FQN можно менять посредством вызова метода updateTopObjectFqn(IBmObject, String)
. Для получения объекта верхнего уровня по FQN интерфейс IBmPlatformTransaction
также предоставляет метод getTopObjectByFqn(IBmNamespace, String)
.
URI #
Помимо целочисленного идентификатора и FQN, у объектов БМ есть еще URI. URI не являются полноценными идентификаторами, и для этого есть две причины:
- Во-первых, URI не являются полностью самостоятельными, т. к. генерируются на базе FQN соответствующего объекта верхнего уровня.
- Во-вторых, URI не предназначены для активного использования вами с целью получения объектов, хотя такая возможность есть (см. метод
getObjectByUri(URI)
на интерфейсеIBmPlatformTransaction
). В первую очередь URI необходимы для кодирования кросс-ссылок при сохранении объектов в основном хранилище. Кросс-ссылки это все ссылки, не помеченные как containment.
URI объектов БМ строятся по общим правилам построения URI в EMF, т. е. URI объекта формируется путем добавления фрагмента к URI ресурса, в котором этот объект расположен.
С ресурсами в БМ связана одна особенность: БМ не оперирует понятием ресурсов, а оперирует только объектами. В связи с этим в БМ ресурсы являются некоторой синтетической сущностью, которая введена лишь для обеспечения минимально необходимой совместимости с EMF. Ресурс БМ связан один-к-одному с соответствующим объектом верхнего уровня. В терминах EMF можно сказать, что при добавлении в модель объекта верхнего уровня БМ автоматически создает для этого объекта ресурс и размещает объект в этом ресурсе. При этом вы не можете каким-либо образом повлиять на то, в каком ресурсе будет размещен объект БМ.
URI ресурса БМ строится по следующему шаблону:
bm://<пространство имен>/<FQN соответствующего объекта верхнего уровня>
Фрагменты URI формируются по следующим правилам:
- Фрагмент URI объекта верхнего уровня всегда содержит один символ — «/».
- Фрагмент URI вложенного объекта формируется из фрагмента контейнера и сегмента, однозначно идентифицирующего вложенный объект в контейнере, разделенных символом «/». Если контейнер является объектом верхнего уровня, то разделитель не добавляется.
- Сегмент фрагмента вложенного объекта содержит:
- Имя feature контейнера, в которой содержится данный объект, если feature неколлекционная.
- Имя feature и идентификатор объекта внутри коллекции, разделенные двоеточием, в случае если feature коллекционная.
- Идентификатором объекта внутри коллекции является строковое представление значения идентифицирующей feature объекта.
- Поиск идентифицирующей feature объекта производится по следующему алгоритму:
- Проверяется, помечена ли коллекция аннотацией
http://www.1c.ru/v8/bm/CollectionItemId
: - Если да, то идентифицирующей feature является feature объекта, имя которой указано в аннотации в атрибуте feature. - Если нет, то проверяется, являются ли элементы коллекции экземплярами классаjava.util.Map.Entry
: - Если да, то идентифицирующей feature является feature с именемkey
. - Если нет, то проверяется, содержит ли объект feature с именемname
: - Если да, то она считается идентифицирующей. - Если нет, то считается, что у объекта нет идентифицирующей feature, и URI для этого объекта построена быть не может.
В случае возникновения необходимости построения URI объекта БМ в прикладном коде (например, для создания прокси-объекта) крайне нежелательно производить построение URI вручную. Для этих целей стоит пользоваться методами, предоставляемыми утилитарным классом BmUriUtil
.
Таким образом, у объектов User
в приведенном в начале документа примере будут следующие URI:
bm://corporatewebsite/User.iivanov#/
bm://corporatewebsite/User.epetrov#/
bm://corporatewebsite/User.jsmith#/
У объектов Task
— следующие:
bm://corporatewebsite/Task.Интернационализациявебсайтакомпании#/
bm://corporatewebsite/Task.Переводвебсайтанаанглийский#/
bm://corporatewebsite/Task.Переводвебсайтанаиспанский#/
А у объектов Comment
URI нет, поскольку в классе Comment
нет feature, по которой можно было бы отличить объекты внутри коллекций comments
и replies
.
Для демонстрации работы с URI вложенных объектов, вы можете расширить модель:
|
|
Теперь у объекта Comment
есть поле creationTimestamp
, которое может использоваться для различения элементов внутри коллекции (естественно, с оговоркой, что в данном тестовом приложении мы не будем создавать комментарии с интервалом меньшим, чем период срабатывания таймера операционной системы). Таким образом у объектов Comment
могут быть следующие URI:
bm://corporatewebsite/Task.Интернационализациявебсайтакомпании#/comments:1675078229613
bm://corporatewebsite/Task.Интернационализациявебсайтакомпании#/comments:1675078229613/replies:1675078291614
bm://corporatewebsite/Task.Интернационализациявебсайтакомпании#/comments:1675078229613/replies:1675078310390
bm://corporatewebsite/Task.Интернационализациявебсайтакомпании#/comments:1675078229613/replies:1675078310390/replies:1675078333230
Также у объекта Task
появилась еще одна коллекция вложенных объектов — attachments
. Объекты Attachment
могут использоваться для прикрепления к задаче каких-либо важных файлов, например скриншотов или логов. URI объектов Attachment
могут выглядеть следующим образом:
bm://corporatewebsite/Task.Ошибкаприпоиске#/attachments:Скриншот
bm://corporatewebsite/Task.Ошибкаприпоиске#/attachments:Логи
Кроме того, у объекта User
появилось поле address
. Нужен ли адрес пользователя в системе управления задачами — вопрос спорный, но в данном случае у объекта Address могут быть URI вроде следующих:
bm://corporatewebsite/User.iivanov#/address
bm://corporatewebsite/User.epetrov#/address
Транзакции #
Механизм транзакций является основным инструментом обеспечения доступа к данным БМ. В обязанности данного механизма входит обеспечение целостности данных, координация конкурентного доступа, управление видимостью модификаций и т. п.
Редактирование данных #
В текущем решении редактирование данных допустимо только в транзакциях. Далее описаны основные сценарии редактирования данных.
Создание нового объекта #
Создание объекта в хранилище сводится к созданию экземпляра EMF-объекта и подключению ее к модели. Объекты верхнего уровня подключаются посредством вызова метода IBmPlatformTransaction.attachTopObject
, а вложенные объекты подключаются посредством добавления в подключенный к модели объект-контейнер:
|
|
При подключении объекта к модели (неважно, является ли он объектом верхнего уровня или вложенным объектом) происходит подключение и всех его вложенных объектов. Таким образом, предыдущий пример можно переписать так, как показано ниже. Но при этом стоит отметить, что первый способ предпочтителен с точки зрения производительности.
|
|
Редактирование существующего объекта #
Для редактирования существующего объекта, необходимо получить этот объект в транзакции и изменить интересующие features и БМ-свойства. Объект можно получить одним из следующих способов:
- Объект верхнего уровня можно получить по его FQN с помощью метода
IBmPlatformTransaction.getTopObjectByFqn
. - Любой объект можно получить по его внутреннему целочисленному идентификатору с помощью метода
IBmPlatformTransaction.getObjectById
. - Объект можно получить путем считывания значения одной из ссылок, указывающих на него (как containment, так и не containment),
- Путем получения соответствующего транзакционного экземпляра по экземпляру, не привязанному к какой-либо транзакции.
Следующий пример демонстрирует описанные выше приемы:
|
|
Удаление существующего объекта #
Как и при создании объектов, способ удаления объекта зависит от того, является ли он объектом верхнего уровня или вложенным объектом. Объекты верхнего уровня удаляются с помощью метода IBmPlatformTransaction.detachTopObject
, а вложенные объекты удаляются путем удаления из контейнера. При удалении объекта (неважно, является ли он объектом верхнего уровня или вложенным объектом) рекурсивно удаляются все его вложенные объекты.
|
|
Блокировки #
В обязанности механизма транзакций входит координация конкурентного доступа к данным. Для предотвращения неконтролируемого редактирования объектов параллельными транзакциями при получении объекта (А) в транзакции (любым из перечисленных ранее способов) автоматически устанавливается блокировка на всё containment-дерево, которому принадлежит объект А. Другими словами, блокируется объект верхнего уровня, соответствующий объекту А, и все его прямые и непрямые дочерние объекты. Таким образом, ни одна из параллельно выполняющихся транзакций не может помешать работе с полученным в транзакции объектом.
Снятие блокировок осуществляется по завершении транзакции. Транзакция может быть завершена с помощью методов commit
или rollback
.
Взаимные блокировки транзакций #
При таком подходе к блокированию объектов с ростом нагрузки неизбежно будут возникать взаимные блокировки транзакций. Поэтому при получении объекта, захваченного другой транзакций, перед вызовом метода ожидания освобождения данного объекта производится проверка, не приведет ли ожидание данного объекта к взаимной блокировке. Если приведет, то выбрасывается исключение BmDeadlockDetectedException
. Важно понимать, что выброс BmDeadlockDetectedException
— нормальное явление, сигнализирующее не об ошибке, а о необходимости завершить текущую транзакцию, чтобы дать беспрепятственно выполниться другим транзакциям. Таким образом, стандартным способом обработки BmDeadlockDetectedException
является откат транзакции, затем повтор операции после небольшой задержки, включающей случайную составляющую.
|
|
Обратите внимание на следующие моменты:
- Если где-то внутри метода
doSomethingInTransaction
исключениеBmDeadlockDetectedException
будет перехвачено и не проброшено выше, то процедура отката и повтора не будет выполнена. - Время задержки включает случайную составляющую, чтобы в самом неудачном случае, когда все блокирующие друг друга транзакции выбрасывают
BmDeadlockDetectedException
, их повторное выполнение было разнесено во времени для уменьшения вероятности повтора взаимной блокировки. - Исключение
BmDeadlockDetectedException
не содержит stack trace, т. к. оно не предназначено для записи в лог.
Следует заметить, что хотя выбросы BmDeadlockDetectedException
в общем случае не сигнализируют об ошибках, они могут означать, что объектная модель спроектирована не очень удачно, либо, что не очень удачно организовано распараллеливание алгоритмов, осуществляющих работу с ней. Для уменьшения количества ситуаций взаимной блокировки транзакций следует придерживаться следующих правил:
- Модель должна быть такой, чтобы работающим с ней алгоритмам не приходилось захватывать большое количество containment-деревьев.
- В модели не должно быть таких объектов, доступ к которым требуется большинству алгоритмов.
- Распараллеливание алгоритмов должно осуществляться таким образом, чтобы минимизировать пересечение множеств containment-деревьев, захватываемых параллельно выполняющимися ветвями алгоритма.
Таймауты #
Если при попытке получения объекта, захваченного другой транзакцией, время ожидания освобождения данного объекта превысило значение, указанное в настройках (см. настройку lockWaitTimeout
в BmPlatformConfiguration
), то выбрасывается исключение BmLockWaitTimeoutException
. В отличие от BmDeadlockDetectedException
, данное исключение сигнализирует об ошибке и причины его должны расследоваться. В частности, выброс данного исключения может говорить о следующих ошибках:
- Зацикливание кода, захватившего объект.
- Подвисшие транзакции (такая ситуация может возникнуть, если в коде нет ни блока catch, ни блока finally, которые осуществляют откат транзакции в случае возникновения ошибки).
- Неадекватно малое значение
lockWaitTimeout
.
Поскольку данное исключение должно расследоваться, то его следует записывать в лог. Для обеспечения возможности расследования BmLockWaitTimeoutException
включает подробное сообщение о проблеме и stack trace.
Транзакции на чтение #
Если предполагается, что в транзакции будет производиться только чтение данных, то имеет смысл создавать транзакцию на чтение. Для этого необходимо вызвать один из методов BmPlatform.beginReadOnlyTransaction
. Использование транзакций на чтение обладает следующими преимуществами:
- Во-первых, при попытке выполнения операции записи будет выброшено исключение, что позволяет на ранней стадии выявить ошибки программирования.
- Во-вторых, работа в транзакции на чтение немного эффективнее, при этом и дальше будет вестись работа по оптимизации на данном направлении.
Легковесные транзакции на чтение #
Все виды транзакций БМ при первом получении объекта добавляют его в свой локальный кэш. При каждом запросе объекта сначала производится его поиск в локальном кэше транзакции (таким образом, локальный кэш транзакции является кэшем первого уровня), и, только если объект там не обнаружен, осуществляется поиск в хранилище. За счет этого повторные запросы объекта выполняются существенно быстрее. Однако, если в транзакции осуществляется обход больших графов объектов, локальный кэш транзакции может потреблять неприемлемо много памяти, что может привести к отказу приложения.
Чтобы решить эту проблему, в БМ были реализованы легковесные транзакции на чтение. Для того чтобы открыть легковесную транзакцию на чтение, необходимо вызвать метод beginReadOnlyTransaction(boolean)
класса BmPlatform
, передав в качестве параметра значение true
. В легковесных транзакциях количество объектов, которые могут находиться в кэше одновременно, строго ограничено. При добавлении очередного объекта, в случае если кэш уже заполнен, производится вытеснение данных объекта (но не самого объекта), который не использовался дольше всех (англ. LRU, least recently used). При этом блокировка с вытесненного объекта не снимается. Таким образом, при повторном запросе объекта, данные которого были вытеснены, можно быть уверенным, что он все еще существует и состояние его не изменилось.
При этом все же не стоит злоупотреблять возможностью получать в транзакции большое количество объектов, поскольку, как было сказано выше, при вытеснении данных блокировки с объектов не снимаются. Таким образом, вероятность возникновения ситуации взаимной блокировки транзакций существенно повышается.
Выброс объектов из транзакций #
Для выброса объектов из транзакции интерфейс IBmPlatformTransaction
предоставляет метод evict(IBmNamespace, long)
. При использовании этого метода необходимо учитывать следующие особенности:
- Данный метод выбрасывает из транзакции все объекты containment-дерева (ресурса в терминах EMF), которому принадлежит указанный объект.
- Если containment-дерево заблокировано на запись, то вызов игнорируется.
- В отличие от автоматического вытеснения, реализованного в легковесных транзакциях, данный метод полностью выбрасывает объект из локального кэша транзакции, а также снимает с объекта блокировку.
- Доступ к объектам, вытесненным из транзакции, запрещен.
В связи с тем, что при выполнении данного метода выбрасываются все объекты containment-дерева, данный метод довольно непредсказуем, поэтому не рекомендуется использовать его без крайней на то необходимости.
Транзакции и потоки #
БМ устанавливает следующие правила доступа к транзакциям:
- Транзакции должны использоваться только в открывшем их потоке.
- Объекты, полученные в транзакции, также должны использоваться только в том потоке, в котором они были получены.
- В каждом потоке может быть одновременно открыто не более одной транзакции. Чтобы получить транзакцию, открытую в текущем потоке, класс
BmPlatform
предоставляет методgetCurrentTransaction
.
Действия с побочными эффектами #
Рассмотрите следующий пример:
|
|
Что здесь может пойти не так? Сообщение о создании задачи может быть доставлено адресату до того, как завершена фиксация транзакции, либо фиксация может вовсе завершиться неудачей (например, из-за проблем с доступом к диску). Таким образом, адресат получит сообщение с идентификатором несуществующей задачи, что в дальнейшем может привести к ошибке обработки данного сообщения.
Чтобы исключить такую ситуацию, рассылку сообщений и любые другие действия с побочными эффектами необходимо осуществлять строго после фиксации транзакции, как показано ниже:
|
|
На случай, если код, в котором необходимо выполнить действие с побочным эффектом, не управляет открытием и закрытием транзакции, интерфейс IBmPlatformTransaction
предоставляет метод addPostCommitHandler(Runnable)
. Ниже приведен пример его использования:
|
|
Доступ к данным вне транзакций #
Для обеспечения совместимости с механизмами, реализованными в ранних версиях 1C:EDT, в БМ была добавлена возможность доступа к данным вне транзакций. В частности, были добавлены следующие возможности:
- Разрешена работа с объектами после фиксации (но не после отката) транзакции, в который они были получены.
- Добавлен метод
IBmEngine.getObjectById(long)
для получения любого объекта по целочисленному идентификатору. - Добавлен метод
IBmEngine.getTopObjectByFqn(String)
для получения объекта верхнего уровня по FQN.
На самом деле, внутри методов IBmEngine.getObjectById(long)
и IBmEngine.getTopObjectByFqn(String)
создается “неявная” транзакция, в которой производится получение объекта. Она начинается и фиксируется “прозрачно” для вас. Информацию об интерфейсе IBmEngine
см. в разделе
Поддержка старого API.
С работой без явного создания транзакций связано несколько ограничений:
- Редактирование объектов, полученных таким образом, запрещено.
- При разрешении ссылок объекта, полученного таким образом, внутри БМ неявно создается транзакция. Соответственно, при обходе больших графов будет создаваться чрезмерное количество транзакций, что негативно скажется на производительности.
- Поскольку транзакции нет, то объект не блокируется, соответственно, параллельно выполняющиеся потоки могут модифицировать его состояние, что может привести к ошибкам в текущем потоке. Рассмотрим следующий пример:
|
|
Очевидно, что если в параллель выполнению цикла другой поток удалит комментарий, то вызов comments.get(i)
на последней итерации цикла приведет к выбросу IndexOutOfBoundsException
. При этом, если переписать данный пример следующим образом, то такой проблемы не возникнет, т. к. задача будет заблокирована на чтение до завершения транзакции, и другие потоки не смогут её модифицировать:
|
|
В связи с описанными выше ограничениями крайне нежелательно работать с БМ без явного создания транзакций, даже если предполагается, что будет осуществляться доступ только на чтение.
Поиск объектов #
БМ поддерживает поиск объектов по различным критериям, например по целочисленному идентификатору или по FQN, уже упоминавшимся в данном документе. Помимо этого БМ поддерживает также поиск объектов по типу, по значению атрибутов; поиск объектов, ссылающихся на заданный; поиск объектов по FQN без учета регистра.
Поиск по типу #
Объекты верхнего уровня #
Для получения объектов верхнего уровня по типу интерфейс IBmPlatformTransaction
предоставляет метод getTopObjectIterator(IBmNamespace, EClass)
. Ниже показан пример использования данного метода:
|
|
Вложенные объекты #
Аналогично, для получения вложенных объектов по типу интерфейс IBmPlatformTransaction
предоставляет метод getContainedObjectIterator(IBmNamespace, EClass)
. При этом поиск поддерживает только те вложенные объекты, которые удовлетворяют заданным правилам индексации (см. настройку containedObjectIndexingRules
в BmPlatformConfiguration
). Необходимо заметить, что пользоваться данной функцией следует с большой осторожностью, потому что, как правило, количество вложенных объектов многократно превышает количество объектов верхнего уровня, соответственно, индекс вложенных объектов может оказаться слишком большим и требовать больших затрат на поддержку.
Поиск по значению атрибута #
Для поиска объектов по значению атрибута интерфейс IBmPlatformTransaction
предоставляет метод getObjectsByAttributeValue(IBmNamespace, EAttribute, Object)
. При этом поиск поддерживает только индексируемые атрибуты (см. настройку indexedAttributes
в BmPlatformConfiguration
). Например, вы можете воспользоваться данной функцией для организации поиска задач по статусу. Для этого добавьте атрибут status
в список индексируемых при создании платформы:
|
|
Теперь все задачи будут индексироваться по значению атрибута status
. Ниже приведен пример использования метода getObjectsByAttributeValue
:
|
|
Необходимо заметить, что БМ позволяет производить поиск и по коллекционным (помеченным как many
) атрибутам. В этом случае, объект может быть получен по любому из элементов коллекции. Пользуясь этим, можно, например, реализовать поиск задач по тегу. Сначала добавьте поле tags
в класс Task
:
|
|
Добавьте его в список индексируемых атрибутов:
|
|
Поиск в этом случае осуществляется аналогично поиску по статусу:
|
|
Поиск объектов, ссылающихся на заданный #
БМ предоставляет возможность поиска кросс-ссылок (т. е. ссылок не помеченных как containment), указывающих на заданный объект. Для этого БМ предоставляет следующие методы:
IBmPlatformTransaction.getReferences(IBmObject, IBmNamespace)
,IBmPlatformTransaction.getReferences(URI, IBmNamespace)
,IBmObject.bmGetReferences()
.
Первый метод возвращает ссылки на указанный объект из объектов, принадлежащих указанному пространству имен; второй метод — на объект с указанным URI. Третий метод возвращает ссылки из объектов, принадлежащих тому же пространству имен, что и объект, на котором производится вызов метода.
Стоит отметить, что данные методы могут возвращать не все имеющиеся ссылки на указанный объект: возвращаются только отслеживаемые ссылки. По умолчанию БМ отслеживает все ссылки кроме «прямых» (см. раздел
Прямые ссылки). Кроме того, в БМ есть возможность фильтрации ссылок: в случае если пользователю БМ заранее известно, что для определенного класса объектов нет необходимости в отслеживании ссылок, пользователь может реализовать интерфейс IBmCrossReferenceFilter
и добавить его в список фильтров (см. настройку crossReferenceFilters
в BmPlatformConfiguration
).
В случае если бы у объекта Task
не было поля blocked
, вы могли бы воспользоваться функцией поиска ссылок для поиска всех заблокированных задач.
|
|
Приведенная выше функция getBlockedTasks
собирает все задачи, у которых переданная в качестве параметра задача находится в коллекции blockers
, т. е. является блокирующей.
Однако, поскольку у объекта Task
есть поле blocked
, то смысла в отслеживании ссылок из коллекции blockers
нет, поэтому имеет смысл отключить его из соображения экономии ресурсов. Для этого вы можете реализовать свой фильтр кросс-ссылок:
|
|
Данный класс фильтрует все ссылки из объектов типа Task
на объекты этого же типа. Таким образом, БМ теперь не будет отслеживать ссылки ни из коллекции blockers
, ни из коллекции blocked
.
Не забудьте указать реализованный фильтр в настройках при создании платформы БМ:
|
|
Поиск по FQN без учета регистра #
Также БМ позволяет производить поиск объектов по FQN без учета регистра. Для этого интерфейс IBmPlatformTransaction
предоставляет метод getTopObjectsByFqnIgnoreCase(IBmNamespace, String)
.
Прямые ссылки #
По умолчанию БМ, как и EMF, в качестве значений кросс-ссылок хранит URI целевого объекта, если целевой объект хранится в БМ, и у него есть URI (о том, как хранятся кросс-ссылки на объекты, не хранящиеся в БМ, см. раздел Ссылки на внешние объекты). Если объект хранится в БМ, но у него нет URI, то в качестве значения используется его целочисленный идентификатор.
Использование URI в качестве внутреннего представления кросс-ссылок позволяет производить установку ссылки на еще не существующий в БМ объект. В этом случае в метод установки значения кросс-ссылки передается прокси-объект, содержащий URI целевого объекта. Например, это удобно, если вы в системе управления задачами работаете над возможностью загрузки данных из какой-либо другой системы. В этом случае вам не требуется ни сортировать задачи на загрузку так, чтобы все объекты загружались строго после тех объектов, на которые они ссылаются, ни делать загрузку в два прохода, когда сначала производится загрузка основных данных, а потом производится связывание объектов.
Однако, использование URI в качестве значения кросс-ссылок имеет свои недостатки:
- URI могут быть достаточно громоздкими, соответственно могут приводить к существенному потреблению оперативной памяти и дискового пространства.
- Получение объекта по URI — достаточно сложный процесс. Сначала необходимо получить объект верхнего уровня по FQN, затем пройти по всей цепочке вложенных объектов до целевого, если целевой объект вложенный.
В связи с этим в БМ предусмотрена возможность принудительного использования целочисленного идентификатора в качестве значения ссылки, как в случае, когда у объекта нет URI. Для этого в БМ предусмотрена аннотация http://www.1c.ru/v8/bm/ForceDirectReference
. Рассмотрите использование данной аннотации на примере:
|
|
Теперь ссылки на блокирующие и блокируемые задачи в качестве значения будут хранить целочисленный идентификатор. Таким образом, хранилище будет компактнее и разрешение ссылок будет происходить быстрее.
В свою очередь использование «прямых» ссылок обладает своими недостатками:
- Нельзя установить ссылку на еще не существующий объект.
- Если ссылка установлена, а затем целевой объект удаляется, то при получении значения не будет возможности вернуть прокси.
- Прямые ссылки не отслеживаются (см. раздел Поиск объектов, ссылающихся на заданный).
Обертки типов #
Работа с массивами #
Предположим, вам не нравится, что хэш-значение пароля хранится в виде строки, и вы хотите, чтобы оно хранилось как байтовый массив. Однако, если вы модифицируете модель, как показано ниже, то не достигнете желаемого результата:
|
|
Вместо геттера и сеттера для поля типа byte[]
EMF создает геттер списка объектов типа java.lang.Byte
. Собственно, это ожидаемо, т. к. это стандартный подход генерации кода для полей помеченных как many
.
|
|
Однако many
-поле вам не требуется, т. к. вам вряд ли понадобится манипулировать отдельными байтами хэш-значения, поэтому в данном случае лучше воспользоваться обертками типов. Измените модель следующим образом:
|
|
Теперь EMF сгенерирует код, который вам требуется:
|
|
Для того чтобы БМ умела сохранять и загружать поля типа ByteArray
, реализуйте соответствующий сериализатор:
|
|
Далее при создании платформы БМ укажите его в настройках:
|
|
Теперь вы можете переписать метод создания пользователя следующим образом:
|
|
Использование неизменяемых объектов #
Важно понимать, что БМ не детектирует изменения во внутренней структуре значений атрибутов. Например, при выполнении следующего метода в хранилище БМ не будет внесено никаких изменений. Конечно, вряд ли бы кто-то захотел обновить пароль подобным образом, но, если бы в качестве эксперимента решил попробовать, то ничего бы не вышло:
|
|
Правильное решение приведено ниже:
|
|
Во избежание подобных ситуаций рекомендуется в качестве значений атрибутов использовать неизменяемые объекты (англ. immutable). Продемонстрируем на примере. Сначала реализуйте класс PasswordHash
, как показано ниже:
|
|
Обновите модель:
|
|
Реализуйте сериализатор:
|
|
Обновите конфигурацию платформы БМ:
|
|
Внесите изменения в метод установки пароля:
|
|
Обертки типов как альтернатива вложенным объектам #
В разделе, посвященном идентификации объектов, в классе User
мы добавили containment-ссылку на объект Address
для хранения адреса пользователя. Хотя такой подход и является правильным с точки зрения EMF, он не является самым эффективным при использовании БМ. Сначала объекту Address
при подключении к БМ будет назначен идентификатор, далее объект будет сохранен в виде отдельной записи на диске, после чего в специальный индекс будет добавлена запись, сопоставляющая идентификатор объекта и его физическое расположение. Кроме того, при запросе поля address
у объекта User
сначала будет получен идентификатор, посредством которого закодирована ссылка на адрес, после чего по идентификатору будет получен объект, для чего сначала будет произведен поиск в индексе информации о физическом расположении записи, и только затем будет произведено чтение объекта из этой записи.
Поскольку в действительности вряд ли кому-то может понадобиться возможность прямого получения объекта Address
по идентификатору, то кажется вполне логичным не тратить ресурсы на хранение отдельной записи и обновление индекса. Вместо этого лучше хранить объект Address
вместе с объектом User
, которому он принадлежит. Этого можно добиться с помощью типов оберток. Для начала реализуйте Address
как обычный Java-класс:
|
|
Добавьте обертку для класса Address
и поле address
в класс User
:
|
|
Реализуйте сериализатор:
|
|
Далее, так же как и в случае с паролем, укажите сериализатор при создании платформы БМ:
|
|
Реализуйте метод для установки адреса:
|
|
Теперь объект Address
будет храниться вместе с объектом User
, которому он принадлежит. Таким образом, вы избежите расходов на хранение отдельного объекта и записи в индексе.
Однако необходимо помнить, что обратной стороной такой экономии является то, что при чтении объекта User
всегда будет считываться и объект Address
, даже при выполнении с объектом User
тех операций, когда его адрес не требуется. Таким образом, этот подход стоит использовать только в тех случаях, когда объект является небольшим или используется очень часто.
Бинарные объекты #
БМ помимо обычных объектов позволяет также хранить неструктурированные (с точки зрения БМ) бинарные объекты, обычно обозначаемые термином BLOB (Binary Large Object). Для работы с бинарными объектами интерфейс IBmPlatformTransaction
предоставляет следующие методы:
IBmBlob createBlob(IBmNamespace namespace, String fqn)
— создает бинарный объект.IBmBlob getBlob(IBmNamespace namespace, String fqn)
— получает бинарный объект.void removeBlob(IBmBlob blob)
— удаляет бинарный объект.void renameBlob(IBmBlob blob, String newFqn)
— переименовывает бинарный объект.
В свою очередь интерфейс IBmBlob
предоставляет следующие методы:
IBmNamespace getNamespace()
— получает пространство имен, которому принадлежит бинарный объект.String getType()
— получает тип содержимого бинарного объекта.InputStream getContent()
— получает содержимое бинарного объекта.long getLength()
— получает длину содержимого бинарного объекта.void setContent(String type, InputStream in)
— устанавливает тип и содержимое бинарного объекта.
Сходства и различия бинарных и обычных объектов:
- Бинарным объектам, как и обычным объектам верхнего уровня, назначается FQN; но, в отличие от обычных объектов, это единственный их идентификатор.
- Как и в случае с обычными объектами, при работе с бинарными объектами на них накладываются блокировки.
- Как и в случае с обычными объектами, попытка наложения блокировки может привести к выбросу
BmDeadlockDetectedException
илиBmLockWaitTimeoutException
. - Как и в случае с обычными объектами, не рекомендуется работать с бинарными объектами вне транзакции.
- При получении бинарного объекта в локальном кэше транзакции размещается только мета-информация, но не содержимое.
- Механизм для выброса бинарных объектов из локального кэша транзакции в БМ не предусмотрен.
- Получение и установка содержимого бинарных объектов работает существенно медленнее, чем получение и установка значений полей обычных объектов.
- Бинарные объекты следует использовать для размещения данных большого размера (например, картинок), не требующих быстрого доступа.
- Обычные объекты, напротив, неэффективно использовать для размещения данных большого размера.
Ссылки на внешние объекты #
В БМ существует возможность установки объектам ссылок на EMF-объекты, не хранящиеся в БМ, а также возможность прозрачного разрешения этих ссылок. Наличие этой возможности обеспечивает практически бесшовное взаимодействие с объектами из пакета Ecore, платформенными объектами, BSL-модулями и тому подобными сущностями.
Основные моменты, которые необходимо понимать при работе со ссылками на внешние объекты, заключаются в следующем:
- Установку и разрешение ссылок на объекты из пакета Ecore БМ осуществляет автоматически.
- При установке ссылки на внешний объект, не принадлежащий пакету Ecore, сначала производится попытка создания внутреннего представления значения ссылки с помощью механизма
IBmReferencePersistenceContributor
(см. настройкуreferencePersistenceContributors
вBmPlatformConfiguration
). При этом производится переборIBmReferencePersistenceContributor
и у каждого осуществляется вызов метода создания внутреннего представления значения ссылкиcreateReferenceValue(EObject, IBmNamespace)
до тех пор, пока не вернется ненулевое значение. Далее вся работа с внутренним представлением значения ссылки (разрешение, сериализация и десериализация) делегируется создавшему ееIBmReferencePersistenceContributor
. - Если все
IBmReferencePersistenceContributor
вернули нулевое значение, то в качестве внутреннего представления значения ссылки используется URI целевого объекта. URI получается путем вызоваEcoreUtil.getURI(EObject)
. Чтобы этот метод отработал корректно, целевой объект должен быть привязан к ресурсу, и должна быть обеспечена возможность построения фрагмента для этого объекта в ресурсе. При разрешении URI внешнего объекта используютсяIBmExternalUriResolver
(см. настройкуexternalUriResolvers
вBmPlatformConfiguration
). Сначала по URI выбирается подходящий экземплярIBmExternalUriResolver
(см. методIBmExternalUriResolver.supports(URI)
). После этого разрешение целевого объекта делегируется методуIBmExternalUriResolver.getObject(URI)
. - Если метод
EcoreUtil.getURI(EObject)
возвратилnull
, то сохранение ссылки на внешний объект невозможно.
Поддержка EMF API #
Важно помнить, что БМ не является полноценной реализацией EMF API. БМ — это высокопроизводительная транзакционная объектная СУБД. На определенном уровне БМ поддерживает совместимость с EMF с целью обеспечения более или менее безболезненной миграции решений, использующих EMF. Поэтому EMF API поддерживается лишь на уровне, минимально необходимом для функционирования решений, реализованных в ранних версиях 1C:EDT.
Далее перечислены основные аспекты, затрагивающие вопрос совместимости БМ с EMF:
Ресурсы #
БМ не оперирует понятиями ресурсов, а оперирует только объектами. При этом, если у объекта, подключенного к БМ, вызвать метод eResource
, то будет возвращен некоторый синтетический экземпляр IBmResource
, у которого в коллекции contents
размещен только лишь соответствующий объект верхнего уровня. В первую очередь метод eResource
был реализован для обеспечения корректной работы утилитарного метода EcoreUtil.getURI
, который внутри производит конкатенацию URI ресурса с фрагментом.
Основные особенности реализации IBmResource
:
getContents
— возвращает немодифицируемый список, содержащий только один объект верхнего уровня.getURI
— возвращает URI со схемойbm
и FQN соответствующего объекта верхнего уровня в иерархической части.getResourceSet
— возвращает синтетический экземплярIBmResourceSet
.getURIFragment
— реализован без каких-либо особенностей.getEObject
— реализован без каких-либо особенностей.- Все остальные методы не поддерживаются и выбрасывают исключение, либо, если было замечено, что они вызываются в коде 1C:EDT или Xtext, то не делают ничего и производят возврат значений
null
,false
,emptyList
и т. п. в зависимости от ситуации.
Особенности реализации IBmResourceSet
.
IBmResourceSet
— создается на каждую транзакцию. Кроме того, есть один глобальный экземпляр, в котором размещаются ресурсы объектов, не привязанные к транзакции.- Ресурс объекта БМ не может быть перепривязан к другому ресурс-сету путем добавления в коллекцию resources.
- Коллекция
resources
— не содержит ресурсы БМ, а содержит только те ресурсы, которые были туда добавлены вами. Это было реализовано для поддержки предыдущих решений по внутреннему экспорту для обеспечения возможности разрешения ссылок на объекты БМ из объектов, не хранящихся в БМ. - Остальные методы либо не реализованы, либо наследуют дефолтные реализации
ResourceSetImpl
, либо оставлены пустыми.
В заключение следует сказать, что при разработке механизмов на базе БМ не следует пользоваться ресурсами и ресурс-сетами, так как:
- Во-первых, как уже говорилось, данная функциональность была реализована на минимальном уровне лишь для обеспечения работоспособности механизмов, реализованных в ранних версиях 1C:EDT, соответственно, ее развитие не планируется.
- Во-вторых, работа через ресурсы и ресурс-сеты существенно менее эффективна с точки зрения производительности, чем работа с API БМ.
Транзиентные features #
БМ игнорирует модификатор transient
, таким образом features, помеченные данным модификатором, все равно записываются в хранилище.
Поддержка старого API #
До появления версии 1С:EDT 2023.1 в БМ отсутствовало понятие пространства имен. Поэтому с целью разделения объектов, принадлежащих разным проектам, на каждый проект создавался отдельный экземпляр БМ, представляемый объектом IBmEngine
. Подобно тому, как в текущей версии любое взаимодействие с платформой начинается с вызова одного из методов объекта BmPlatform
, в предыдущих версиях взаимодействие начиналось с вызова одного из методов объекта IBmEngine
, как правило, с метода открытия транзакции. Для работы с транзакцией в предыдущих версиях использовался объект IBmTransaction
, набор методов которого аналогичен набору методов IBmPlatformTransaction
, с той лишь разницей, что практически каждый метод IBmPlatformTransaction
принимает в качестве одного из параметров объект IBmNamespace
.
Однако старое решение было неудобно тем, что не позволяло в одной транзакции работать с данными нескольких проектов, а также было далеко не оптимально с точки зрения потребления ресурсов (например, каждая инстанция БМ захватывала необходимое ей количество оперативной памяти для хранения индексов, соответственно потребление памяти в 1С:EDT росло кратно количеству проектов, открытых в рабочей области). Для адресации указанных проблем и была реализована текущая платформа БМ с поддержкой пространств имен.
Тем не менее, в текущей версии было принято решение не избавляться от интерфейсов IBmEngine
и IBmTransaction
, чтобы упростить миграцию существующих решений на новый API. В текущем решении эти объекты являются проекциями объектов BmPlatform
и IBmPlatformTransaction
, привязанными к определенному пространству имен. Так, интерфейс IBmNamespace
предоставляет метод IBmEngine asEngine()
, а интерфейс IBmPlatformTransaction
предоставляет метод IBmTransaction getNamespaceBoundTransaction(IBmNamespace)
.
Для начала рассмотрим метод getNamespaceBoundTransaction(IBmNamespace)
интерфейса IBmPlatformTransaction
. Он возвращает реализацию IBmTransaction
, которая представляет не новую транзакцию, а всего лишь обертку над IBmPlatformTransaction
. Эта обертка делегирует выполнение всех действий исходного экземпляра IBmPlatformTransaction
. При этом в те методы IBmPlatformTransaction
, которые принимают пространство имен, передается пространство имен, переданное в качестве параметра в метод getNamespaceBoundTransaction
. Ниже приведена эквивалентная реализация IBmTransaction
:
|
|
Соответственно, метод asEngine()
интерфейса IBmNamespace
возвращает реализацию IBmEngine
, которая представляет собой обертку над BmPlatform
. Все методы этой обертки также работают в контексте пространства имен, к которому обертка привязана. Ниже приведена эквивалентная реализация IBmEngine
.
|
|
Для перехода от старого API к новому интерфейс IBmEngine
предоставляет метод IBmNamespace asNamespace()
, возвращающий пространство имен, к которому привязана текущая инстанция. Аналогично, интерфейс IBmTransaction
для получения связанных с ним транзакции и пространства имен предоставляет методы IBmPlatformTransaction getPlatformTransaction()
и IBmNamespace getNamespace()
.
При работе со старым API необходимо помнить, что IBmTransaction
это всего лишь обертка-адаптер интерфейса IBmPlatformTransaction
, а не некий специфичный вид транзакции. Соответственно, все экземпляры IBmTransaction
, полученные в одном потоке, представляют одну транзакцию, а не разные. Рассмотрите следующий пример. Предположим, у вас есть реализованный на базе старого API метод для переноса задачи из одного проекта в другой:
|
|
Хотя выглядит данный метод практически не читаемо, в старом решении он был бы вполне работоспособен, если не считать, что он не обеспечивал бы атомарность изменений, да еще мог бы привести к неразрешимым средствами БМ ситуациям взаимной блокировки. В новом же решении при попытке создать вторую транзакцию в стр. 9 будет выброшено исключение, т. к. транзакция уже открыта в стр. 3.
Чтобы заставить данный код работать в новом решении, не прибегая к полному переводу на новый API, вы можете изменить его следующим образом:
|
|
Заметьте, что операции фиксации и отката выполняются один раз посредством вызова соответствующих методов на объекте sourceProjectTransaction
, так как, хотя оберток две, транзакция одна. За счет этого приведенный метод будет выполняться атомарно и не будет приводить к неразрешимым ситуациям взаимной блокировки.
Однако, во избежание введения в заблуждение тех, кто будет работать с вашим кодом, все же рекомендуется все компоненты, осуществляющие одновременную работу с несколькими пространствами имен, по возможности переводить на использование нового API. Обратите внимание, что приведенная ниже версия этого же метода, реализованная на базе нового API, выглядит логичнее:
|
|