Данный текст рассматривает вкратце особенности объектно-реляционного отображения (Object-Relational Mapping — ORM) и вводит новое понятие реляционного отображения коллекций (Collection-Relational Mapping — CoRM), предлагая обсудить перспективы и возможности технической реализации новой концепции долговременного хранения состояния объектов
Объектно-реляционное отображение (ORM)
Сегодня почти общим местом при разработке больших и сложных систем стало использование объектно-реляционных отображений (Object-Relational Mapping, ORM), которые используют общность некоторых теоретических основ объектно-ориентированного и реляционного программирования для объединения этих двух, существенно разных, способов моделирования реальности.
Объектно-ориентированное программирование опирается на концепции, плохо ложащиеся в парадигму реляционных СУБД, в основном представленных в виде систем, исполняющих запросы SQL. Такими концепциями являются иерархические структуры данных, полиморфизм, инкапсуляция и конечно же, наследование. И если наследование еще как-то получило развитие например в PostgreSQL, то например инкапсуляция, прямо противоречащая принципиальной открытости и произвольности комбинирования значений в кортежах SQL, создает на самом деле существенные (возможно, исходящие из теоретических противоречий) препятствия эффективному использованию реляционной СУБД, как долговременного хранилища состояний объектов класса.
Тем не менее, распространенность реляционных СУБД, их относительная эффективность при исполнении запросов к большим и очень большим массивам данных, широкие возможности объединения гетерогенной вычислительной среды через реляционную СУБД и другие преимущества, связанные с использованием таких СУБД, заставляют искать компромисс между удобством проектирования системы в объектно-ориентированном стиле и ограничениями, накладываемыми на проектируемую систему использованием этих СУБД.
Основной опорой для объединения объектной и реляционной парадигм в рамках ORM является тезис о том, что так же как реляционная таблица содержит множество элементов (строк) одной структуры, так и класс объединяет множество объектов, имеющих единую структуру и поведение. Поэтому одним из наиболее распространенных шаблонов, повсеместно используемых в реализациях ORM, является отображение класса в таблицу, при этом полями таблицы являются атрибуты (все или часть, однозначно определяющая состояние) объектов класса.
Рисунок 1 Наиболее распространенная форма объектно-реляционного отображения. Классы отображаются на таблицы, атрибуты отображаются на поля таблиц
Любая техническая реализация имеет свои ограничения, не стала исключением и технология реляционных СУБД, основанных на языке SQL, достаточно далеком от своего теоретического предшественника — алгебры Кодда. При этом, все технические ограничения SQL, связанные с самим наличием таблицы, как физического носителя кортежей значений, связанных между собой отношением один-к-одному, естественным образом переходят на класс, отображенный на такую таблицу с помощью ORM.
Рисунок 2 Одна из проблем при прямом отображении классов в таблицы: отображение атрибутов, имеющих множественный характер, требует отдельных таблиц.
Реляционное отображение коллекций
Концепция реляционного отображения коллекций (Collection-Relational Mapping, CoRM) опирается на тот факт, что таблица, как долговременное хранилище состояний объекта, имеет вполне адекватный образ в любой развитой объектно-ориентированной среде, а именно — коллекцию (Collection).
Хранение состояния коллекции объектов в виде таблицы SQL — далеко не новое изобретение. Так, половина пакетов, посвященных хранению данных в стандартной библиотеке python, посвящена именно этой технологии. Новизна подхода CoRM состоит в том, что разрозненные коллекции объектов объединяются хорошо определенными (well-defined) реляционными взаимоотношениями между этими коллекциями.
CoRM не накладывает никаких ограничений на способность разных объектов одного класса хранить свое состояние в разных коллекциях, так же как и на способность коллекции одновременно содержать объекты, принадлежащие к разным классам. Отличие CoRM от традиционного подхода ORM состоит в том, что коллекция не привязана напрямую к классу и теоретически может содержать любой объект, при выполнении минимальных требований к этому объекту (а именно, способности к сериализации некоторым специальным образом). При этом, данные требования не включают в себя никаких ограничений на структуру объекта, использование специальных типов данных и так далее.
Последнее обстоятельство отличает подход CoRM в том числе и от широко известной библиотеки SQLAlchemy, так же манипулирующей коллекциями и отдельно привязанными к коллекциям классами.
Реляционные взаимоотношения между элементами коллекций определяются наличием и содержимым (возвращаемым значением) выделенных свойств (properties), атрибутов или детерминированных (deterministic) методов у входящих в коллекцию объектов. Такие выделенные свойства будут называться индексами (Index) коллекции. Таким образом, коллекция и ее индексы являются связующим звеном между парадигмой объектно-ориентированного программирования, поставляющей значения индексов, и реляционной парадигмой хранилища SQL, которое использует полученные значения индексов для установления реляционных взаимоотношений между хранимыми объектами.
Рисунок 3 Коллекция и ее индексы являются связующим звеном между объектной и реляционной моделями данных
Углубляясь в реляционную парадигму, можно определить понятие ключей (Key) коллекции, каждый из которых будет ссылаться на некоторое сочетание индексов этой коллекции, определяя для этого сочетания дополнительные оптимизационные и ограничительные свойства, например уникальность (составного в общем случае) значения ключа в рамках коллекции.
Отдельные ключи коллекции могут служить внешними ключами для обращения к объектам, хранящимся в другой коллекции, в точности так же, как внешний ключ в таблице служит для установления реляционных отношений с другой таблицей. В точности таким же способом, как внешний ключ в реляционной СУБД, внешний ключ в реляционном отображении коллекций может быть использован для поддержки целостности (integrity) данных, запрещая хранение такого значения внешнего ключа, которое не соответствует ни одному из значений ключа в целевой для внешнего ключа коллекции.
Возможность практического использования реляционных отношений между коллекциями определяется размещением связанных с ними таблиц в одной базе данных. Отражением этого факта для программиста является наличие специального объекта хранилища (Storage), который определяет специфику подключения к базе данных и общие структурные и функциональные характеристики СУБД, а также содержит точки доступа к коллекциям, таблицы для которых содержатся в этой базе данных.
Обращаясь к коллекциям для выборки данных, в выражениях фильтрации, группировки, упорядочения — программист манипулирует не свойствами объектов, которые хранятся в коллекции, а значениями индексов, получая в результате список объектов, либо список кортежей, составленных из объектов и дополнительно подсчитанных выражений (определенных на значениях индексов коллекций). Сами объекты остаются полностью инкапсулированными внутри коллекции вплоть до тех пор, пока не будут десериализованы при получении результатов запроса.
Инкапсуляция объекта внутри коллекции и изоляция хранимого состояния объекта от индексов, доступных (в отличие от состояния объекта) со стороны реляционного хранилища, делают бессмысленными попытки отображения выражений (фильтрации, группировки, вычислений дополнительных возвращаемых значений и упорядочения) на какие-то подобия выражений используемого объектного языка (python в нашем случае), как это происходит например в SQLAlchemy. Вместо этого, запрос выборки из коллекции может быть сформулирован на языке, максимально близком по структуре к SQL, как это например делается при использовании Google App Engine Datastore. Язык GQL, используемый при обращении к коллекциям Google Datastore, является почти оптимальным образцом (разумеется за исключением странных ограничений, накладываемых на этот язык его реализацией) языка, который мог бы быть использован для получения данных из коллекций при использовании CoRM. Имена, входящие в выражения языка, очевидно должны ссылаться на индексы и ключи, определенные на коллекциях, а не на атрибуты хранимого объекта, скрытые от реляционного хранилища.
Выведение синтаксиса запроса к коллекции за пределы синтаксиса объектного языка позволяет проводить предварительную компиляцию и кеширование компилированных запросов целевого хранилища, оставляя возможность десериализации объекта из полученных данных, что может привести к существенной оптимизации скорости доступа к объектам, чье состояние сохранено в реляционном хранилище (здесь необходимо отметить, что вопрос оптимизации доступа к объектам в коллекции, вообще говоря, является достаточно болезненным для существующих ORM).
Интересным дополнением является то, что инкапсуляция и выделение индексов коллекции, как отдельной сущности, помимо прочего, позволяют легко реализовать динамические индексы, значения которых не содержатся напрямую в атрибутах хранимого состояния объекта, но однозначно вычисляются из них.
Вместо вывода
Реляционное отображение коллекций, будучи реализованным, может стать отличной альтернативой существующим реализациям ORM, позволяя вывести из под ограничений реляционного хранилища объектную сущность приложения.
Внимание, вопрос:
Автор: nnseva