Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Система VisTrails

Глава 23 из 1 тома книги "Архитектура приложений с открытым исходным кодом".

Оригинал: "VisTrails", глава из книги "The Architecture of Open Source Applications"
Авторы: Juliana Freire, David Koop, Emanuele Santos, Carlos Scheidegger, Claudio Silva, and Huy T. Vo
Дата публикации: 2012 г.
Перевод: Н.Ромоданов
Дата перевода: март 2013 г.

Creative Commons. Перевод был сделан в соответствие с лицензией Creative Commons. С русским вариантом лицензии можно ознакомиться здесь.


23.3. Внутри системы VisTrails

23.3.3. Сериализация и хранение данных

Одним из ключевых компонентов любой системы, поддерживающей использование информации о происхождении, является сериализация и хранение данных. В системе VisTrails данные изначально сохраняются в формате XML с использованием простых методов fromXML и toXML, встроенных во внутренние объекты системы (например, в дерево версий, в каждый модуль). Чтобы поддерживать эволюцию схемы подобных объектов, в этих функциях также закодированы все переходы между версиями схемы. По мере того, как проект разрабатывался, база наших пользователей росла, и мы решили поддержавать различные версии сериализации, в том числе и реляционные хранилища. В добавок по мере того, как добавлялись объекты схемы, нам для управления обычными данными потребовалось поддерживать более развитую инфраструктуру, позволяющую управлять версиями схем, выполнять преобразование между версиями и работать с сущностными отношениями. Чтобы все это сделать, мы добавили новый слой базы данных (db).

Слой (db) состоит из трех основных компонентов: доменных объектов, сервис-логики и методов сохранения данных. Доменная компонента и компонента сохранения данных позволяют использовать версии, так что для каждой версии схемы есть свой собственный набор классов. Таким образом, у нас поддерживается код, позволяющий читать каждую версию схемы. Также есть классы, в которых определены правила перевода объектов из одной версии схемы в другие. В классах сервис-логики предоставляются методы взаимосвязи с данными и методы, осуществляющие обнаружение и преобразование версий схемы.

Поскольку написание большей части этого кода утомительно и повторяющееся, мы используем шаблоны и мета-схемы, с помощью которых определяется как компоновка объекта (и все индексы доступа к памяти), так и код сериализации. Мета-схема написана на языке XML и является расширяемой в том смысле, что можно добавлять варианты сериализаций, отличные используемых по умолчанию XML и реляционных отображений, определенных в системе VisTrails. Этот подход похож на объектно-реляционные отображения и фреймверки, например, Hibernate [2] и SQLObject [3], но лишь добавлено несколько специальных процедур, которые автоматизируют такие задачи, как повторное отображение идентификаторов и преобразование объектов из одной версии схемы в следующую. Кроме того, мы также можем использовать те же самые мета-схемы при генерации кода сериализации для многих языков. Сначала была написана мета-схемаmeta-Python, в которой код домена и код сохранения данных были сгенерированы с помощью выполнения кода на языке Python с переменными, полученными из мета-схемы, а совсем недавно мы перешли к шаблонам Mako [4].

Автоматическое преобразование является ключевым для пользователей, которым нужно перенести свои данные на новые версии системы. В нашем проекте были добавлены специальные возможности, которые сделали для разработчиков такое преобразование чуть менее болезненным. Поскольку мы поддерживаем копии кода для каждой версии, перевод кода нужен просто для отображения одной версии в другую. На корневом уровне, мы определяем отображение, в котором указывается, каким образом любая версия может быть преобразована в любую другую версию. Для версии, которые сильно разнесены друг от друга, обычно указывается цепочка преобразований через несколько промежуточных версий. Сначала использовалось отображение только в прямом направлении, а это означает, что новые версии не могут преобразовываться в старые, но в совсем недавних отображениях схем было добавлено отображение в обратном направлении.

В каждом объекте есть метод update_version, который получает произвольную версию объекта и возвращает текущую версию. По умолчанию, он выполняет рекурсивное преобразование, при котором каждый объект обновляется при помощи отображения полей старого объекта в те, которые есть в новой версии. По умолчанию это отображение представляет собой копирование каждого поля с тем же самым именем, но с помощью метода, указываемого как "override" ("переопределяющий"), можно для любого поля можно переопределить поведение, определенное по умолчанию. Переопределяющий метод является методом, который берет старый объект и возвращает новую версию. Поскольку большинство изменений в схеме затрагивает только небольшое количество полей, в большинстве случаев достаточно отображения, выполняемого по умолчанию, но переопределения предоставляют гибкий механизм внесения локальных изменений.

23.3.4. Расширяемость с помощью пакетов и языка Python

В первом прототипе системы VisTrails был фиксированный набор модулей. Это была идеальная среда для разработки базовых идей, связанных с деревом версий VisTrails и кэширование многочисленных запусков рабочих процессов, но с точки зрения долгосрочной перспективы прототип был сильно ограничен.

Мы рассматриваем систему VisTrails как инфраструктуру для вычислений, а это, в буквальном смысле, значит, что в системе должны быть вспомогательные подмостки для других инструментальных средств и процессов, которые должны быть разработаны. Главное требование этого сценария является расширяемость. Типичный способ для достижения этой цели включает в себя определение целевого языка и написание соответствующего интерпретатора. Это выглядит привлекательным благодаря очень индивидуальному контролю, который предоставляется за исполнением. Эта привлекательность усиливается в свете наших требований кэширования. Тем не менее, внедрение полноценного языка программирования требует больших усилий, что никогда не было нашей главной целью. Что еще более важно, это заставит пользователей, которые просто пытаются использовать систему VisTrails, изучать совершенно новый язык, который им не нужен.

Нам нужна была система, которая позволяла пользователям легко добавлять свои собственные функции. В то же время, нам нужно, чтобы система была достаточно мощной, с тем чтобы самостоятельно реализовывать довольно сложные части программного обеспечения. В качестве примера, в системе VisTrails поддерживается библиотека визуализации VTK [5]. В VTK содержится около 1000 классов, которые изменяются в зависимости от вариантов компиляции, конфигурирования и операционной системы. Поскольку нам показалось, что будет контрпродуктивным и в конечном счете безнадежной писать разные пути кода для всех этих случаев, мы решили, что набор модулей VisTrails, который предоставляется в любом заданном пакете, нужно определять динамически и, естественно, библиотека VTK, стала наш целевой моделью для сложных пакетов.

Вычислительные науки были одной из областей, для использования в которых мы изначально нацеливали систему, и когда мы разрабатывали ее, среди этих ученых язык Python становился в качестве "кода для склейки". Поскольку при спецификации поведения модулей VisTrails, определяемых пользователями, используется сам язык Python, мы могли бы справиться со всем, но преодолели один большой барьер. Как оказалось, в языке Python предлагается замечательная инфраструктура для динамически определяемых классов и рефлексии. Почти каждое определение в языке Python имеет эквивалентную форму в виде выражения первого порядка. Для системы наших пакетов особенно важными возможностями рефлексии языка Python являются следующие две:

  • Классы языка Python могут определяться с помощью вызова динамического квалификатора type. Возвращаемым значением является представление класса, который можно использовать точно так же, как и класс языка Python, определяемый обычным образом.
  • Модули языка Python можно импортировать с помощью вызовов функции __import__, и результирующее значение ведет себя точно так же, как идентификатор в стандартной инструкции import. Путь, используемый для получения этих модулей, можно указывать время выполнения.

Конечно, использование языка Python в качестве целевого, имеет ряд недостатков. Прежде всего, такая динамическая природа языка Python означает, что пока мы не захотим реализовать некоторые вещи, такие как безопасность типов пакетов VisTrails, они вообще будут невозможны. Более того, некоторые из требований к модулям VisTrails, в частности такие, как прозрачность при ссылках на модули (подробнее об этом позже), не могут поддерживаться в Python. Тем не менее, мы считаем, что с помощью искусственных механизмов целесообразно ограничить конструкции, допустимые в языке Python, и с этой оговоркой, язык Python является чрезвычайно привлекательным языком, позволяющим расширять программное обеспечения.

23.3.5. Пакеты и сборки системы VisTrails

Пакет системы VisTrails инкапсулирует в себе набор модулей. Его самое обычное представление на диске точно такое, как представление пакета Python (к несчастью, допускающего коллизию имен). Пакет Python состоит из набора файлов Python, в которых определяются функции и классы языка Python. Пакет системы VisTrails является пакетом языка Python, в котором предпочтение отдается определенному интерфейсу. В нем есть файлы, в которых определены конкретные функции и переменные. В своей простейшей форме, пакет VisTrails должен быть каталогом, содержащим два файла: __init__.py и init.py.

Первый файл __init__.py добавлен в соответствие с требованиям к пакетам Python, в нем должны быть только несколько определений, являющиеся константами. Хотя нет никакого способа это проконтролировать, пакеты VisTrails, в которых не соблюдается это требование, рассматриваются как ошибочные. Среди значений, определенных в файле, есть уникальный глобальный идентификатор пакета, который используется для того, чтобы различать модули, когда происходит их сериализация или создаются их версии (версии пакета важны при обработке рабочего процесса и обновлении пакетов; смотрите раздел 23.4). В этом файле также могут быть функции package_dependencies и package_requirements. Поскольку в модулях VisTrails допускаются подклассы других модулей VisTrails, не входящих в корневой класс Module, вполне возможно, что в некотором пакете VisTrails есть расширение поведения другого пакета, и, поэтому, первый пакет должен быть проинициализирован перед вторым пакетом. Такие взаимозависимости пакетов определяются с помощью package_dependencies. С другой стороны, с помощью функции package_requirements определяются требования к библиотекам системного уровня, которые в системе VisTrails могут, в некоторых случаях, выполняться автоматически за счет использования абстракции сборок.

Сборка (bundle) является пакетом системного уровня, управление которым в системе VisTrails происходит при помощи специальных системных инструментальных средств, таких как RPM в RedHat или APT в Ubuntu. Когда все эти особенности соблюдены, система VisTrails может определить свойства пакета с помощью непосредственного импорта модуля Python и доступа к соответствующим переменным.

Во втором файле, init.py, находятся точки входа для всех актуальных определений модулей VisTrails. Наиболее важной особенностью этого файла является определение двух функций initialize и finalize. Функция initialize вызывается тогда, когда пакет становится доступным после то, как стали доступными все пакеты, от которых он зависит. С ее помощью выполняются задачи настройки для всех модулей в пакете. С другой стороны, функция finalize обычно используется, чтобы освободить ресурсы времени выполнения (например, могут быть стерты временные файлы, созданные в пакете).

Каждый модуль системы VisTrails представлен в пакете в виде одного класса Python. Чтобы зарегистрировать этот класс в системе VisTrails, разработчик пакетов один раз для каждого модуля VisTrails обращается к функции add_module. Такие модули VisTrails могут быть любыми классами языка Python, но в них должны соблюдаться некоторые требования. Первое из них то, что каждый из этих классов должен быть подклассом базового класса Python, определенным в системе VisTrails, хотя это может показаться скучным, обращением к Module. В модулях VisTrails может использоваться множественное наследование, но только один из классов должен быть модулем VisTrails — решетчатые иерархии в дереве модулей VisTrails не допускаются. Множественное наследование становится полезным, в частности, при определении смешанных классов: простое поведение, закодированное в родительских классах, может быть объединено вместе с тем, чтобы создать более сложное поведение.

Набор имеющихся портов определяет интерфейс модуля VisTrails, так что это влияет не только на отображение этих модули, но и взаимодействие этих модулей с другими. Итак, эти порты должны быть явно описаны в инфраструктуре системы VisTrails. Это может быть сделано либо путем соответствующих обращений к функциям add_input_port и add_output_port при обращении к функции initialize, либо указав списки add_input_port и add_output_port в каждом модуле VisTrails для каждого класса.

В каждом модуле вычисления, которые должны быть выполнены, определяются при помощи переопределения метода compute. Данные передаются между модулями через порты и доступ к ним осуществляется через методы get_input_from_port и set_result. В традиционной среде потоков данных, порядок выполнения определяется по требованию при помощи запросов данных. В нашем случае, порядок выполнения определяется при помощи топологической сортировки модулей рабочего процесса. Поскольку для алгоритма кэширования требуется ациклический граф, мы составляем план выполнения в обратном топологическом порядке сортировки, с тем чтобы обращения к этим функциям не требовали переключения на исполнение модулей, находящихся выше. Мы приняли это решение сознательно: оно позволяет проще рассматривать поведение каждого модуля вне зависимости от всех остальных модулей, что делает нашу стратегию кэширования более простой и надежной.

В качестве общей рекомендации, в модулях системы VisTrails нужно при переопределении модуля compute воздерживаться от использования функций с побочными эффектами. Как отмечалось в разделе 23.3, благодаря этому требованию становится возможным кэширование конкретного рабочего процесс: если модуль отвечает этому требованию, то его поведение является функцией, зависящей от выходных данных модулей, находящихся выше. Тогда для каждого ациклического подграфа вычисления можно делать только один раз, и результатами можно пользоваться повторно.

23.3.6. Передача данных в виде модулей

Одной особенностью модулей VisTrails и их взаимодействия является то, что данные, которые передаются между модулями VisTrails, сами являются модулями системы VisTrails. В системе VisTrails есть единственная иерархии классов модулей и данных. Например, модуль может представлять собой результат вычислений (и, по сути, в каждом модуле есть определяемый по умолчанию выходной порт "self"). Основным недостатком является исчезновение концептуального различия между вычислениями и данными, которые иногда учитываются в системах с архитектурой на базе потоков данных. Но, в этом есть два больших преимущества. Во-первых, точно имитирует система типов объектов языков Java и C++, и этот выбор был не случайным: для нас важно поддерживать автоматическое подключение больших библиотек классов, таких как VTK. В этих библиотеках допускаются объекты, которые в качестве результата вычислений создают другие объекты, что усложняет такую поддержку в случаях, когда есть различия между вычислениями и данными.

Вторым преимуществом этого решения является то, что становится проще определять значения констант и параметров, устанавливаемых пользователями, и их интеграция с остальной частью системы становится более единообразной. Рассмотрим, например, рабочий процесс, который загружает в сеть локально расположенный файл, заданный как константа. В настоящее время для этого используется графический пользовательский интерфейс, в котором URL может быть указан в качестве параметра (смотрите область редактирования параметров Parameter Edits на рис 23.1). Естественное изменение этого рабочего процесса состоит в том, чтобы можно было определить URL, который уже был вычислен выше. Нам бы хотелось, чтобы остальную часть рабочего процесса требовалось менять как можно меньше. Если предположить, что модули могут выдавать в качестве результата самих себя, то мы бы могли просто подключить строку с правильным значением к порту соответствующего параметра. Поскольку при выдаче константы, результатом ее вычисления является сама эта константа, то поведение было бы точно такое, как если бы значение было фактически определено как константа.

Рис.23.5: Прототипирование новой функциональности с помощью модуля PythonSource

Есть и другие соображения, касающиеся использования констант. Для каждого константного типа есть свой собственный идеальный графический интерфейс для задания значения. Например, в системе VisTrails в модуле констант — файлов предлагается диалог, позволяющий выбрать файл; значение типа Boolean определяется с помощью отметки, делаемой в чекбоксе; значение цвета выбирается с помощью диалога средства выбора цвета, своего собственного для каждой операционной системы. Чтобы добиться такого обобщения, разработчик должен для конкретной константы создать подкласс базового класса Constant и переопределить методы, в которых определяется виджет графического интерфейса и строковое представление (с тем, чтобы любые константы можно было сериализовать на диске).

Отметим, что для простых задач прототипирования, в системе VisTrails имеется встроенный модуль PythonSource. Модуль PythonSource можно использовать для непосредственной вставки скриптов в рабочий процесс. В конфигурационном окне модуля PythonSource (смотрите рис.23.5) предоставляется возможность указать несколько входных и выходных портов, а также указать код на языке Python, который должен быть выполнен.


Далее: 23.4. Компоненты и возможности