на примере MS SQL Server 2000 и CASE ERwin
Зачем это нужно?
Объектный подход, используемый для разработки программных систем доказал на практике свои преимущества. К сожалению, кроме приятных возможностей он привнёс и новые проблемы. В классической парадигме "программа = алгоритмы + данные" месту объекта не находится, поскольку объект есть и данные и процедуры, как говорится, "в одном флаконе". Это привело в частности к тому, что до сих пор не существует общих решений для самого широкого класса ПО - приложений баз данных. Данная ситуация и некоторые способы её решения хорошо описана, например, в [1].
По роду своей деятельности "автоматизаторам" преимущественно приходится сталкиваться с объектами, которые по своей природе являются долговременными, то есть должны хранить своё состояние в долговременной памяти. Сама по себе задача хранения состояний и даже управления хранением ещё не ведёт напрямую к использованию базы данных, можно ведь и в файлах хранить сериализованные объекты. Но из последующей задачи поиска объектов по произвольно заданным критериям и анализа хранящейся информации о состояниях объектов это следует прямо.
Исходя из старого, но верного правила "чем дальше источник данных от мест их обработки, тем обработка медленнее" [2], я придерживаюсь точки зрения, что методика "пассивный объект в БД - активный объект на клиенте БД" является узким местом в информационной системе. Очевидно, что использование объектов в адресном пространстве СУБД более эффективно с точки зрения производительности, чем в адресном пространстве программы - клиента СУБД. Клиентом может быть и сервер приложений, суть не меняется: затраты на сериализацию/десериализацию и маршаллинг всегда будут присутствовать в любой подобной схеме. Примеров таких решений масса: ECO (Bold), Hibernate (NHibernate), XPO, ObjectSpaces, InstantObjects и многие другие OPF (Objects Persistence Framework).
ORM - это, по большому счету, просто заменитель традиционной технологии работы через DataSet, а не система управления объектами (СУОб, по аналогии с СУБД). Вместо работы со строками и полями DataSet, выборками на стандартном SQL, используется работа с объектами и их свойствами, выборками коллекций, в общем случае, на нестандартном языке. Отсюда вытекают все плюсы и минусы. Итак, известные недостатки существующих решений проекций объектов на реляционную структуру:
- Высокие накладные расходы (overhead) для манипуляций с объектами вне адресного пространстве сервера (отсутствует in-process)
- Невозможность эффективно разделять логику между приложениями, работающими с одной БД (объекты "живут" в адресном пространстве клиентов)
- Проблемы оптимизации и тонкой настройки взаимодействия с БД (ярким примером является требования разработчиков о необходимости поддержки отображения на хранимые процедуры, которое вводится, например в нынешней 3-й версии Hibernate)
Даже для частичного решения этих проблем необходимо создавать собственный сервер приложений, что является совсем не тривиальной технической и совсем не очевидной экономической задачей (необходимость в системных разработчиках, повышение TCO).
Приведу одну простую иллюстрацию. Наверняка, вы знаете, что реляционная СУБД хранит свои данные отнюдь не в виде таблиц или тем более реляционных отношений - множеств кортежей, которыми оперирует программист. СУБД использует внутренние эффективные оптимизированные структуры, в том числе древовидные, размещаемые в оперативной и долговременной памяти компьютера. Это подсистема хранения. Подсистема выполнения запросов к данным на языке SQL интерпретирует запрос и транслирует его в последовательность низкоуровневых операций поиска и чтения по этим внутренним нереляционным структурам. Получается, что сторонники разделения сред хранения состояний и использования объектов практически предлагают разделить среды хранения данных и выполнения SQL-запросов в СУБД. Более того, разрешается прямой (т.е. хакерский) доступ к таким структурам, минуя интерфейс языка запросов. Подобная ситуация наблюдалась с настольными СУБД типа FoxPro/dBase/Paradox, использовавшимися 10-15 лет назад в многопользовательском варианте на основе файл-сервера.
Являясь сторонником развития технологии баз данных в сторону объектных СУБД, я надеюсь в обозримом будущем сделать проект автоматизации с использованием промышленной версии таковой. А пока же у нас есть промышленные реляционные СУБД, есть математический аппарат в основе используемой модели данных, международные стандарты и развитая инфраструктура для разработки приложений и эксплуатации [3].
Как нам обустроить РСУБД?
Итак, наша цель - наделить РСУБД возможностями работы не с таблицами, строками и полями, а с коллекциями, объектами и атрибутами. Здесь существует несколько подходов, например, реализация слоя доступа через хранимые процедуры. Подобный подход был нами успешно реализован и кратко описан в статье. На мой взгляд, этот подход имеет недостатки, прежде всего, в необходимости введения некоего макроязыка для манипуляции объектами и невозможности использования стандартных средств доступа к данным, таких как, например, средства импорта данных из СУБД в Excel, зачастую необходимых рядовому пользователю.
Другой подход состоит в максимальном использовании стандартных средств самой СУБД, в частности механизма представлений (view) и языка запросов SQL. Рассмотрим его подробнее.
Отображение
Для начала определимся со схемой отображения объектов на таблицы РСУБД. Обзор трех принципиальных решений исчерпывающе описан в статье Александра Усова "Объектное представление о реляционной модели". В принципе, для нашего примера отображение может быть любым, но, не касаясь вопросов эффективности, воспользуемся следующим вариантом:
- общий абстрактный предок для всех классов Object. Каждый объект имеет уникальный в пределах информационной системы генерируемый ключ OID
- подклассу соответствует отдельная таблица; имя таблицы совпадает с именем класса; связь с таблицей суперкласса "один к одному" по ключу OID
- строки таблиц соответствуют объектам
- атрибуты классов соответствуют столбцам таблицы; атрибуты суперкласса, за исключением OID, не дублируются в таблице подклассов
- каждому классу в системе соответствует одна проекция (view), с именем V_ИмяКласса, содержащая все атрибуты как самого класса, так и суперклассов
Метаданные
Важной частью информационной системы являются метаданные. В приложении к объектам корректнее использовать термин "метаинформация", но в рамках статьи будем использовать традиционный. Не рассматривая вопросы необходимости метаданных для пользователя, определимся, зачем они будут нужны нам. Итак:
- метаданные содержат декларацию класса: характеристики самого класса, его атрибутов и связей с другими классами
- декларация класса позволяет автоматически генерировать структуры для хранения объектов и интерфейсы для доступа к ним
Структура для хранения метаданных может иметь следующий вид:
Итак, таблица Object будет содержать атрибуты корневого суперкласса. Мы ввели в нее атрибут Deleted, который служит для моделирования "мусорной корзины": логического удаления объектов с возможностью восстановления.
Таблица Classes содержит характеристики класса: имя (является ключом), пользовательское название, ссылка на суперкласс (пустая для корневого), признак иерархической организации объектов (влияет на генерацию таблиц, например в создаваемой таблице может быть создано поле для ссылки на родителя), признак "собственная проекция" (не генерируется автоматически, создается программистом).
Таблица Attributes содержит характеристики атрибутов класса: имя, пользовательское название, тип (например, одно из значений: целое, строка, дата и время, вещественное, деньги, ссылка на объект), класс объектов (для типа атрибутов "объектная ссылка"), является ли альтернативным ключом для поиска и однозначной идентификации объекта данного класса (таковых атрибутов может быть несколько, в этом случае они образуют составной уникальный ключ).
Таблица Links содержит характеристики связей между классами: главный класс и атрибут, починенный класс и атрибут, пользовательское название связи, признак "обязательная связь" (подчиненный не может быть создан без главного).
Для декларации классов, атрибутов и связей создадим простой интерфейс в виде хранимых процедур. Например, если мы захотим объявить классы Компания (Company) и Контактное лицо (Person), связанные между собой отношением "один ко многим", то декларация может иметь вид:
exec Class_Declare @Class = 'Company', @Superclass = 'Object', @Caption = 'Компания' exec Attribute_Declare @Class = 'Company', @Name = 'Name', @Caption = 'Name', @Type = 'TString', @IsAltKey = 1
exec Class_Declare @Class = 'Person', @Superclass = 'Object', @Caption = 'Контактное лицо' exec Attribute_Declare @Class = 'Person', @Name = 'FirstName', @Caption = 'Имя', @Type = 'TPersonName', @IsAltKey = 1 exec Attribute_Declare @Class = 'Person', @Name = 'LastName', @Caption = 'Фамилия', @Type = 'TPersonName', @IsAltKey = 1 exec Attribute_Declare @Class = 'Person', @Name = 'CompanyID', @Caption = 'Компания', @Type = 'TOID', @ObjClass = 'Company'
exec Link_Declare @Class = 'Company', @AttrName = 'OID', @LinkedClass = 'Person', @LinkedAttrName = 'CompanyID', @Caption = 'является сотрудником компании', @Mandatory = 1
Автогенерация структур
Согласно принятым положениям о проекции классов на таблицы, мы должны создать для объявленных нами классов Company и Person, производных от Object, таблицы.
CREATE TABLE Company ( OID TOID, Name TString128 NOT NULL, CONSTRAINT PK_Company PRIMARY KEY (OID), CONSTRAINT FK_Company_Object FOREIGN KEY (OID) REFERENCES Object, CONSTRAINT AK_Company UNIQUE (Name) ) CREATE TABLE Person ( OID TOID, FirstName TPersonName NOT NULL, LastName TPersonName NOT NULL, CompanyID TOID, CONSTRAINT PK_Person PRIMARY KEY NONCLUSTERED (OID), CONSTRAINT FK_Person_Object FOREIGN KEY (OID) REFERENCES Object, CONSTRAINT FK_Person_Company FOREIGN KEY (CompanyID) REFERENCES Company, CONSTRAINT AK_Person UNIQUE (CompanyID, FirstName, LastName) )
Для того, чтобы работать с объектами средствами SQL нам понадобятся представления V_ИмяКласса. Например, для объявленных нами классов Company и Person они будут такими:
create view V_Object as SELECT OID, Class, Deleted FROM dbo.Object O WHERE (Deleted = 0)
create view dbo.V_Company with VIEW_METADATA as select SO.*, O.Name as [Name] from Company as O join dbo.V_Object as SO on O.OID = SO.OID
create view dbo.V_Person with VIEW_METADATA as select SO.*, O.FirstName as [FirstName], O.MiddleName as [MiddleName], O.LastName as [LastName], /* отображаем ключевой атрибут связанного объекта */ O.CompanyID as [CompanyID], L1.Name as [Company] from Person as O join dbo.V_Object as SO on O.OID = SO.OID inner join Company as L1 on O.CompanyID = L1.OID
Очевидно, что процедура создания таблиц (или скрипта для их создания) и генерация таких представлений на основе метаданных может быть поручена программе. Для этих целей были написаны две несложные процедуры Metadata_GenerateTable и Metadata_GenerateView, принимающие параметр: имя класса. Теперь сценарий создания в среде нового класса выглядит так:
- Декларация класса, атрибутов и связей (с использованием процедур Class_Declare, Attribute_Declare и Link_Declare)
- Генерация таблиц или скрипта для их создания (Metadata_GenerateTable)
- Генерация проекции (Metadata_GenerateView)
После этого прикладной программист получает в свое распоряжение проекцию V_ИмяКласса, с которой он может работать точно так же, как и с простой таблицей: делать выборки, используя стандартный SQL. Кроме этого, конечно, нужно иметь возможность создавать, модифицировать и удалять объекты, чем мы сейчас и займемся.
Автогенерация кода методов
Средства MSSQL предоставляют возможность создавать для представлений триггеры, обрабатывающие события вставки, модификации и удаления данных. Очевидно, что метаданных нам хватит для генерации таких триггеров при помощи созданной для этой цели процедуры Metadata_GenerateProcs с параметром "имя класса". Например, для класса Company триггер на вставку будет выглядеть следующим образом:
create trigger V_Company_Create on V_Company instead of insert as begin set nocount on declare @Ret int declare @OID TString, @Name TString select top 1 @OID = convert(varchar(16),I.OID), @Name=O.Name from V_Company O, inserted I where O.OID <> I.OID and O.[Name]=I.[Name] if @@rowcount > 0 begin rollback tran raiserror('Компания уже существует: OID: %s Name: %s', 11, 1, 'Company', @OID, @Name) return end insert into Object( [OID], [Class], [Deleted]) select [OID], 'Company', 0 from inserted if @@error <> 0 begin rollback transaction return end insert into Company( [OID], [Name]) select [OID], [Name] if @@error <> 0 begin rollback transaction return end end
Аналогичным образом генерируются триггеры для модификации и удаления данных. Триггер на delete, в соответствии с логикой "корзины", не удаляет записи, а просто меняет атрибут Object.Deleted = 1. С этого момента запись перестает быть видимой в представлении.
К сценарию создания класса в среде добавляется один пункт: генерация кода триггеров с использованием процедуры Metadata_GenerateProcs. Теперь прикладной программист может работать с атрибутами класса, как с полями таблицы не задумываясь о том, как и где они хранятся. Например, создание новой компании выглядит так:
insert into V_Company (OID, Name) values (12345, 'ТОО Рога и копыта')
Наращиваем возможности среды
То, что у нас получилось на прошлом этапе - всего лишь реализация отображения (mapping), хотя и с дополнительными важными возможностями в виде метаданных и "корзины", что само по себе еще недостаточно для полноценной работы с ООСУБД. Необходимы, как минимум, сервис безопасности (разделение прав доступа, аудит) и хотя бы простейшая реализация обработчиков событий.
Ограничения доступа
Пользователи лишены прав на работу с таблицами напрямую. На уровне же представлений мы можем достаточно легко смоделировать:
- Ограничения прав на создание объектов данного класса. Решается встроенными средствами СУБД: предоставление прав на insert для view V_ИмяКласса
- Ограничение прав на модификацию и удаление данных. Решается проверкой соответствующих прав в триггере на update и delete.
- Ограничение прав на чтение данных. Решается введением дополнительной проверки в условии where или введением соединения с таблицей (матрицей) прав только на уровне view V_Object (класс объекта известен на этом уровне).
При этом программисту не нужно ничего изменять в программе: она продолжает работать с представлениями. Более того, права буду соблюдаться даже при работе пользователя из любой другой программы, например, при импорте данных из БД в Excel.
Подробнее о моделировании прав доступа см. статью "Реализация ядра безопасности в информационной системе". Вам потребуется только добавить код в триггеры для реализации проверок.
Аудит
Часто встречающая задача: хранить историю операций пользователей с данным объектом. Решается не просто, а очень просто.
Добавляем два класса: "системный пользователь" (SysUser), который однозначно идентифицируется по имени регистрации пользователя (Login) SQL Server и "системное событие" (для простейшего случая нам потребуются 3 события: Create, Update, Delete). В таблицу SysLog будем заносить все операции, которые производит пользователь над объектами. Для этого в автоматически генерируемые триггеры нужно добавить всего несколько строчек кода. Например, для триггера insert вот такие:
insert into SysLog (UserID, ObjectID, EventID, [Date], Message) select (select OID from SysUser where Login = SYSTEM_USER), I.OID, (select OID from V_SysEvent where Name = 'Create'), getdate(), 'Комментарий' from inserted I
В прикладной программе снова ничего не изменилось. Но теперь каждое действие пользователя протоколируется независимо от того, из какой программы он работает с базой.
Проверки ссылочной целостности
Пример проверки на уникальность уже приведен в коде триггера. Кроме существующих деклараций внешних ключей на уровне таблиц БД, прикладному программисту хотелось бы получать от СУБД "осмысленные" сообщения, а не малопонятные для пользователя системные ошибки о нарушении. Эта задача также решается просто. На уровне метаданных мы имеем описания связей. Этого достаточно, чтобы в код автоматически генерируемых триггеров включить проверки и выдавать сообщения в стиле: "Не могу удалить объект "Компания". Объект связан с одним или несколькими объектами "Контактное лицо" или "Контактное лицо "Остап Бендер" является сотрудником компании "ТОО Рога и Копыта".
Обработчики событий
Если разработчику захочется реализовать логику не в приложении а непосредственно на уровне СУБД, то в его распоряжении будут два основных механизма: триггеры и хранимые процедуры. Если условиться об именовании хранимых процедур в стиле:
ИмяКласса_ИмяСобытия_ИмяОбработчика[_]
где ИмяСобытия - Create, Update, Delete; ИмяОбработчика - идентификатор, который назначает прикладной программист; подчеркивание в конце или его отсутствие - признак пре- или пост-обработчика
В этом случае возможно вызывать эти процедуры автоматически во время соответствующих операций с представлениями. Усложняется лишь код автоматически генерируемых триггеров.
Интеграция со средой моделирования
Если вернуться к коду декларации классов, атрибутов и связей, нетрудно заметить, что и его в свою очередь, можно генерировать из информационной модели или диаграммы классов. В качестве примера приведу собственный положительный опыт интеграции с ERwin, который имеет внутренний скриптовый язык, позволяющий запрограммировать генерацию декларирующего кода непосредственно из физической (в терминах ERwin) модели. В приводимом ниже примере используется именно такая связка.
Пример использования
Имеется следующая информационная модель в ERwin:
Используя измененный для нашего случая механизм генерации скриптов по модели, получаем сценарий для декларации объектов
/*** Classes declaration ***/ exec spDeclareClass @Class = 'PrKind', @Superclass = 'Object', @Caption = 'Product kind' exec spDeclareAttribute @Class = 'PrKind', @Name = 'OID', @Caption = 'OID', @Type = 'TOID' exec spDeclareAttribute @Class = 'PrKind', @Name = 'Code', @Caption = 'Code', @Type = 'TString16' exec spDeclareAttribute @Class = 'PrKind', @Name = 'Name', @Caption = 'Name', @Type = 'TString128' go exec spDeclareClass @Class = 'PrList', @Superclass = 'Object', @Caption = 'Price list' exec spDeclareAttribute @Class = 'PrList', @Name = 'OID', @Caption = 'OID', @Type = 'TOID' exec spDeclareAttribute @Class = 'PrList', @Name = 'PriceDir', @Caption = 'Price direction', @Type = 'char(1)' exec spDeclareAttribute @Class = 'PrList', @Name = 'CurrencyID', @Caption = 'Currency', @Type = 'TOID' , @ObjClass = 'Currency' exec spDeclareAttribute @Class = 'PrList', @Name = 'QuantityB', @Caption = 'Quantity bottom', @Type = 'int' exec spDeclareAttribute @Class = 'PrList', @Name = 'Price', @Caption = 'Price', @Type = 'money' exec spDeclareAttribute @Class = 'PrList', @Name = 'VDate', @Caption = 'Value date', @Type = 'datetime' exec spDeclareAttribute @Class = 'PrList', @Name = 'ProductID', @Caption = 'Product or service', @Type = 'TOID' , @ObjClass = 'Product' exec spDeclareAttribute @Class = 'PrList', @Name = 'HCompanyID', @Caption = 'Holding company', @Type = 'TOID' , @ObjClass = 'HCompany' exec spDeclareAttribute @Class = 'PrList', @Name = 'PLClGroupID', @Caption = 'Client group', @Type = 'TOID' , @ObjClass = 'PLClGroup' go exec spDeclareClass @Class = 'Product', @Superclass = 'Object', @Caption = 'Product' exec spDeclareAttribute @Class = 'Product', @Name = 'OID', @Caption = 'OID', @Type = 'TOID' exec spDeclareAttribute @Class = 'Product', @Name = 'Code', @Caption = 'Code', @Type = 'TProductCode' exec spDeclareAttribute @Class = 'Product', @Name = 'Name', @Caption = 'Product name', @Type = 'TCaption' exec spDeclareAttribute @Class = 'Product', @Name = 'PrTypeID', @Caption = 'Product type', @Type = 'TOID' , @ObjClass = 'PrType' exec spDeclareAttribute @Class = 'Product', @Name = 'PrStatusID', @Caption = 'Product status', @Type = 'TOID' , @ObjClass = 'PrStatus' exec spDeclareAttribute @Class = 'Product', @Name = 'PrKindID', @Caption = 'Product kind', @Type = 'TOID' , @ObjClass = 'PrKind' exec spDeclareAttribute @Class = 'Product', @Name = 'PrModelID', @Caption = 'Product model', @Type = 'TOID' , @ObjClass = 'PrModel' exec spDeclareAttribute @Class = 'Product', @Name = 'DocProcTypeID', @Caption = 'Document processing type', @Type = 'TOID' , @ObjClass = 'DocProcType' go exec spDeclareClass @Class = 'PrStatus', @Superclass = 'Object', @Caption = 'Product status' exec spDeclareAttribute @Class = 'PrStatus', @Name = 'OID', @Caption = 'OID', @Type = 'TOID' exec spDeclareAttribute @Class = 'PrStatus', @Name = 'Code', @Caption = 'Code', @Type = 'TProductStatusCode' exec spDeclareAttribute @Class = 'PrStatus', @Name = 'Caption', @Caption = 'Caption', @Type = 'TCaption' go exec spDeclareClass @Class = 'PrType', @Superclass = 'Object', @Caption = 'Product type' exec spDeclareAttribute @Class = 'PrType', @Name = 'OID', @Caption = 'OID', @Type = 'TOID' exec spDeclareAttribute @Class = 'PrType', @Name = 'Code', @Caption = 'Code', @Type = 'varchar(2)' exec spDeclareAttribute @Class = 'PrType', @Name = 'Caption', @Caption = 'Caption', @Type = 'TCaption' go exec spDeclareClass @Class = 'Tax', @Superclass = 'Object', @Caption = 'Tax' exec spDeclareAttribute @Class = 'Tax', @Name = 'OID', @Caption = 'OID', @Type = 'TOID' exec spDeclareAttribute @Class = 'Tax', @Name = 'Code', @Caption = 'Code', @Type = 'TString16' exec spDeclareAttribute @Class = 'Tax', @Name = 'Caption', @Caption = 'Caption', @Type = 'TCaption' go exec spDeclareClass @Class = 'TaxGroup', @Superclass = 'Object', @Caption = 'Product tax group' exec spDeclareAttribute @Class = 'TaxGroup', @Name = 'OID', @Caption = 'OID', @Type = 'TOID' exec spDeclareAttribute @Class = 'TaxGroup', @Name = 'Code', @Caption = 'Code', @Type = 'TString16' exec spDeclareAttribute @Class = 'TaxGroup', @Name = 'Caption', @Caption = 'Tax group name', @Type = 'TCaption' exec spDeclareAttribute @Class = 'TaxGroup', @Name = 'TaxID', @Caption = 'Tax', @Type = 'TOID' , @ObjClass = 'Tax' go /*** Declare links ***/ exec spDeclareLink @Class = 'PLClGroup', @AttrName = 'OID', @LinkedClass = 'PrList', @LinkedAttrName = 'PLClGroupID', @Caption = 'qualifies', @Mandatory = 1 exec spDeclareLink @Class = 'HCompany', @AttrName = 'OID', @LinkedClass = 'PrList', @LinkedAttrName = 'HCompanyID', @Caption = 'has', @Mandatory = 1 exec spDeclareLink @Class = 'Currency', @AttrName = 'OID', @LinkedClass = 'PrList', @LinkedAttrName = 'CurrencyID', @Caption = 'used in the', @Mandatory = 1 exec spDeclareLink @Class = 'Product', @AttrName = 'OID', @LinkedClass = 'PrList', @LinkedAttrName = 'ProductID', @Caption = 'has', @Mandatory = 1 exec spDeclareLink @Class = 'DocProcType', @AttrName = 'OID', @LinkedClass = 'Product', @LinkedAttrName = 'DocProcTypeID', @Caption = 'is attribute of', @Mandatory = 1 exec spDeclareLink @Class = 'PrModel', @AttrName = 'OID', @LinkedClass = 'Product', @LinkedAttrName = 'PrModelID', @Caption = 'is a base of', @Mandatory = 0 exec spDeclareLink @Class = 'PrKind', @AttrName = 'OID', @LinkedClass = 'Product', @LinkedAttrName = 'PrKindID', @Caption = 'qualifies', @Mandatory = 1 exec spDeclareLink @Class = 'PrStatus', @AttrName = 'OID', @LinkedClass = 'Product', @LinkedAttrName = 'PrStatusID', @Caption = 'determines', @Mandatory = 1 exec spDeclareLink @Class = 'PrType', @AttrName = 'OID', @LinkedClass = 'Product', @LinkedAttrName = 'PrTypeID', @Caption = 'qualifies', @Mandatory = 1 exec spDeclareLink @Class = 'Tax', @AttrName = 'OID', @LinkedClass = 'TaxGroup', @LinkedAttrName = 'TaxID', @Caption = 'groups in the', @Mandatory = 1 go
После прогона скрипта декларации создаем таблицы, проекции и триггеры:
exec Metadata_GenerateTable @Class = 'PrKind' exec Metadata_GenerateTable @Class = 'PrList' exec Metadata_GenerateTable @Class = 'Product' exec Metadata_GenerateTable @Class = 'PrStatus' exec Metadata_GenerateTable @Class = 'PrType' exec Metadata_GenerateTable @Class = 'Tax' exec Metadata_GenerateTable @Class = 'TaxGroup' exec Metadata_GenerateView /* перегенерация всех представлений */ exec Metadata_GenerateProcs /* перегенерация всех триггеров */
Таблицы и представления для "Product taxes" и "Tax values" создаются в примере вручную, но это решение было принято из соображений оптимизации; ничто не мешает в общем случае представить связь "многие ко многим" связующим классом.
Заключение
Описанный выше подход имеет свои преимущества и недостатки, свою область применения. Например, существует тесная привязка к СУБД: в одних случаях это является преимуществом, так как позволяет максимально использовать её возможности, в других - недостатком. Процедурный язык СУБД может показаться прикладному разработчику слишком примитивным даже в сравнении с Visual Basic, но если вспомнить, что на нем будет реализовываться исключительно логика обработки данных, то эта простота обернется мощностью и эффективностью специализированного языка. Решение с логикой, реализованной на уровне СУБД масштабируется хуже, чем с выделенным сервером приложений. Однако, выделенный сервер приложений - это увеличение стоимости развертывания и содержания, в то время как недостатки масштабирования СУБД могут быть решены использованием кластера. В целом, все зависит от конкретной ситуации, от требований к системе.
В качестве примеров реализации я могу привести заказную CRM-систему с распределенной БД (репликация). Область использования такой архитектуры - корпоративные приложения, особенно в ситуации, когда с одной БД предполагается работа более одного приложения: решение позволяет прозрачно для прикладного программиста повторно использовать логику, реализованную на сервере БД. Вторая область - тиражируемые решения, когда заказчику нужен настраиваемый функционал, а тип СУБД его интересует в гораздо меньшей степени (зачастую, не интересует и вовсе). Примеры: КИС "БОСС-компания" и, с некоторыми натяжками, "1С" и наш проект NEXUS.
В примере используется MS SQL Server, тем не менее, нет никаких препятствий реализации подобной архитектуры средствами Sybase, Oracle, Interbase.
Литература
- Корпоративные системы на основе CORBA. : Пер. с англ. - М. Издательский дом "Вильямс", 2000
- Бойко В.В., Савинков В.М. Проектирование информационных систем. – М.: Финансы и статистика. 1988
- М.Р.Когаловский. Абстракции и модели в системах баз данных. - Журнал СУБД #04-05/98 (http://www.osp.ru/dbms/1998/04-05/07.htm)
- Обсуждение вертикальной модели хранения атрибутов в fido.su.dbms
- Проблемы оптимизации в ООБД (дискуссия в fido.su.oop)
- Проблемы persistent layers (обсуждение в fido.su.dbms)
Сергей Тарасов, октябрь 2003
С поправками, февраль 2005
Attachment | Size |
---|---|
![]() | 58.84 KB |
- CASE |
- MDA/MDD |
- SQL Server