Реализация сервера объектного представления средствами реляционной СУБД

на примере 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.

Литература

  1. Корпоративные системы на основе CORBA. : Пер. с англ. - М. Издательский дом "Вильямс", 2000
  2. Бойко В.В., Савинков В.М. Проектирование информационных систем. – М.: Финансы и статистика. 1988
  3. М.Р.Когаловский. Абстракции и модели в системах баз данных. - Журнал СУБД #04-05/98 (http://www.osp.ru/dbms/1998/04-05/07.htm)
  4. Обсуждение вертикальной модели хранения атрибутов в fido.su.dbms
  5. Проблемы оптимизации в ООБД (дискуссия в fido.su.oop)
  6. Проблемы persistent layers (обсуждение в fido.su.dbms)

Сергей Тарасов, октябрь 2003
С поправками, февраль 2005

Прикрепленный файлРазмер
Package icon A2KernelDemo.zip58.84 KB

Комментарии

Понравилось. Особенно "малая м

Понравилось. Особенно "малая механизация" на ERwin.
А почему ты исключил из метаданных описание событий, которые может принять объект, и их обработчиков?

Ограничение прав на чтение данных. Решается введением дополнительной проверки в условии where или введением соединения с таблицей (матрицей) прав только на уровне view V_Object (класс объекта известен на этом уровне).

В большинстве прикладных случаев ведение марицы прав слишком накладно. Рассмотрим пример. Пусть у клиента есть артибут Филиал, и у пользователя есть атрибут Филиал. Пользователи могут иметь доступ только к клиентам зарегестрированным в их филиале. Если реализовывать это правило через матрицу, то придеться создать таблицу отношений Пользователь-Клиент и поддреживать ее в актуальном состоянии (триггера). В альтернативном варианте это правило реализуется в V_Клиент, однако в этом случае не будет работать автоматическиая генерация view. Обычная дилема - эфективность vs абстракция.

Изображение пользователя Serguei_Tarassov.

А почему ты исключ

А почему ты исключил из метаданных описание событий, которые может принять объект, и их обработчиков?

Событий было 4: create, update, delete, undelete (это права на неописанную в статье вьюшку VD_ИмяКласса, содержащую удаленные объекты). Управление правами осуществлялось стандартными средствами grant.

В большинстве прикладных случаев ведение марицы прав слишком накладно. Рассмотрим пример. Пусть у клиента есть артибут Филиал, и у пользователя есть атрибут Филиал. Пользователи могут иметь доступ только к клиентам зарегестрированным в их филиале. Если реализовывать это правило через матрицу, то придеться создать таблицу отношений Пользователь-Клиент и поддреживать ее в актуальном состоянии (триггера). В альтернативном варианте это правило реализуется в V_Клиент, однако в этом случае не будет работать автоматическиая генерация view. Обычная дилема - эфективность vs абстракция.

Матрицу можно трактовать как "есть доступ" или "нет доступа". Зависит от разреженности :)

"Serge

"Sergey_Bykov":
А почему ты исключил из метаданных описание событий, которые может принять объект, и их обработчиков?

Событий было 4: create, update, delete, undelete (это права на неописанную в статье вьюшку VD_ИмяКласса, содержащую удаленные объекты). Управление правами осуществлялось стандартными средствами grant.

Это события хранилища, они не явным образом связаны с некоторыми событиями класса "создание объекта", "изменение объекта" и т.д. Любое событие прикаладного уровня на уровне хранилища будет преобразовано в одно из этих событий. Мне представлется полезным описание прикаладных событий, которые может вопринимать класс, с указанием где реализован обработчик - сервер БД, App сервер или клиент.

Изображение пользователя Serguei_Tarassov.

Это события хранил

Это события хранилища, они не явным образом связаны с некоторыми событиями класса "создание объекта", "изменение объекта" и т.д. Любое событие прикаладного уровня на уровне хранилища будет преобразовано в одно из этих событий. Мне представлется полезным описание прикаладных событий, которые может вопринимать класс, с указанием где реализован обработчик - сервер БД, App сервер или клиент.

У меня было сделано без выделения класса прикладных событий. Каждое прикладное событие в итоге меняет состояние объекта.
Значит, срабатывает одно из событий Create, Update, Delete
Программист пишет обработчик этого события.

Пример: перевод компании в категорию "VIP-клиент".
Это значит, что у объекта "Компания" изменился атрибут "Категория клиента".
Пишем обработчик
create procedure Компания_Update_Категория
...
в котором обрабатывается это правило.
Права на изменения атрибутов тоже можно задавать встроенными средствами (грантами на поля вьюшек), но мне это не потребовалось.

Пример: перевод ко

Пример: перевод компании в категорию "VIP-клиент".
Это значит, что у объекта "Компания" изменился атрибут "Категория клиента".
Пишем обработчик
create procedure Компания_Update_Категория
...
в котором обрабатывается это правило.
Права на изменения атрибутов тоже можно задавать встроенными средствами (грантами на поля вьюшек), но мне это не потребовалось.

Лучше задавать права на уровне грантов на обработчики.

Все таки есть однобокость в том, что предметная область описывает только декларативно (структуры данных) и не описывается функционально (события и их реализация).

А вот еще не охваченная область, которая может быть решена средствами РСУБД: диаграмма переходов объекта из одного состояния в другое. Состояние описывается набором значений атрибутов. Нужно создать механизм описания возможных переходов из одного состояния в другие.
Например, компания может быть переведна в категорию "VIP-клиент" только из категорий "надежный клиент" и "друг босса". Конечно эти правила можно задавать в обработчике, но большинство из них носит декларативный характер: "если то, то можно это".
Насколько возможно использование средств СУБД для реализации подобных правил типа check и trigger?
Мне представляется, что использование триггеров в явном виде затруднительно - правил может быть много, они могут быть плохо согласованы.
А вот генерация триггера по некотором метаданным - выглядит привлекательно. Пока я не могу найти инструмента наподобе ERwin, для описания таких правил.

Изображение пользователя Serguei_Tarassov.

Лучше задавать пра

Лучше задавать права на уровне грантов на обработчики.

Нет, и это принципиальный момент. Права могут быть даны на сообщения, которые можно посылать объекту. В моем случае роль события играет операция с вьюшкой. Т.е. предполагается, что нет событий без параметров, роль параметра здесь играет поле вьюшки - атрибут (свойство) класса. Поэтому на этом уровне и даются права.
Если нужно вводить посылку сообщений без параметров, то опять приходим к NEXUS-подобному EvProcess()

Все таки есть однобокость в том, что предметная область описывает только декларативно (структуры данных) и не описывается функционально (события и их реализация).
А вот еще не охваченная область, которая может быть решена средствами РСУБД: диаграмма переходов объекта из одного состояния в другое. Состояние описывается набором значений атрибутов. Нужно создать механизм описания возможных переходов из одного состояния в другие.
Например, компания может быть переведна в категорию "VIP-клиент" только из категорий "надежный клиент" и "друг босса". Конечно эти правила можно задавать в обработчике, но большинство из них носит декларативный характер: "если то, то можно это".
Насколько возможно использование средств СУБД для реализации подобных правил типа check и trigger?
Мне представляется, что использование триггеров в явном виде затруднительно - правил может быть много, они могут быть плохо согласованы.
А вот генерация триггера по некотором метаданным - выглядит привлекательно. Пока я не могу найти инструмента наподобе ERwin, для описания таких правил.

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

Изображение пользователя ipanshin.

Посмотреть бы как это все рабо

Посмотреть бы как это все работает. Есть ли возможность увидеть работающую теорию? Особенно интересна мысль сращивания Erwin c клиентом (imho). Далее тоже интересно использование триггеров (насколько я понял они должны работать как обработчики классов?) . Есть ли практический выхлоп в сторону клиента нексус или ядра?

Но скажу по секрету я вообще перестал думать о стилях и ограничениях хранения в таблицах. Самая лучшая таблица вообще без ограничений типа
xxx int null - это самое лучшее
Единственное, что надо это UDN и нумерация строк при записи в таблицу. НУМЕРАЦИЯ !!!

Изображение пользователя Serguei_Tarassov.

Посмотреть бы как

Посмотреть бы как это все работает. Есть ли возможность увидеть работающую теорию?

Ты хочешь увидеть систему в действии или поставить себе демку ?

Прочитал. СТ. Да я хочу ув

Прочитал.

СТ. Да я хочу увидеть работающую твою концепцию, но прошу попроще, сам понимаешь существует предел.

ivn - я так понимаю Володя. Му немного знакомы. Если говорить о преобразовании объектов, то предлагаю метаморфизм классов объектов. Классная вещь. Сам гонял телефоны из сломанных - в ремонтируемые - в выданные, минуя состояние объекта из класса. Позволяет групповые операции.

К метаданным отношусь подозрительно, так как вижу за ними подножку. Если кто скажет как можно синхронизировать метаданные с БД, над которой поработали по живому, то пойду к нему в ученики.

Detailed, конечно, геморрой, но если он один, то неплохо. Совместно с дополнительными интерфейсными таблицами дает неплохие результаты.

В настоящее время пишу без ограничений на таблицы вообще.
create table tbl(col type null) для всех таблиц и их колонок.
Можете ругаться, но свобода дает удовлетворение. Зато пользуюсь alter table в любом контексте, не боясь, что чтолибо вылетит. Хранимые процедуры, конечно, усложняются, но как ни странно ненамного.

СБ назвал меня символистом.

От иерархий ДЦ просто пищу так как на его подходе можно их создавать бесплатно великое множество. И более опуская или поднимая объект по иерархии ДЦ появляется механизм забывания в системе.

Я не отказываюсь от кодирования С++ , но и не ставлю его во главу. Основной упор при внедрении лежит на правильном понимании логики предметной области, конечно. К этому времени пройден путь программирования клиента (это только код типа С++, никаких там мусорщиков) и авто генерация класса на стороне сервера (пока transactSQL).

Результаты работы здесь www.panshin.spb.ru

Все это ООП хорошо, только зад

Все это ООП хорошо, только зададим себе вопросы:
1) Какие чудовищные тормоза дает протокол обмена типа Detailed между объектами, особенно при групповом обновлении объектов. На практике приходится уйти обычный update. Так может это и есть правильный подход, который надо додумать?
2) Потери времени девелоперов на отладку. Сколько крови было попито.

На практие я решил это изменить так.
Создан конструктор документов, который сразу генерил формы, таблицы и view для работы с объектом. Detailed был убит, вместо него появились view типа
create view dbo.D2779g_EX as select * from D2779g_EXT where Usr=system_user
Где, D2779g_EXT это буферная таблица в замен Detailed, но в нормальном реляционном виде.

App Server это возможно не плохо. Но давайте не забывать, что наша цель делать проблемные решения, а не AppSrv. С точки зрения даже масштабных решений охватывающих сразу все сервера Лукойла в масштабах проихводственного комплекса Лукойл-Коми или Лукойл-Пермь потенциал самого обычно MS SQL-подхода далеко не исчерпан и позволяет решать реальные задачи.
Тем не менее, надо давать себе отчет, что есть нереляционные задачи в приципе, например матричные экономические вычисления, их и нужно делать в виде серверных модулей.

Изображение пользователя Serguei_Tarassov.

Володя, согласен, я именно это

Володя, согласен, я именно это и продвигаю как тезис: работа через вьюшки с максимальным использованием встроенных средств самого MSSQL :) С этой же точки зрения мне не вполне нравятся OPF и Persistence Layers, поскольку жестокий оверхед и неоптимальность работы с БД могут свести на нет все прелести знакомой прикладному разработчику объектной абстракции.

P.S. На форуме NEXUS есть немеряный флейм на тему вьюшек, который мы с Димой Цурановым затеяли на практически пустом месте: реализации Application role для клиента :)

Все это ООП хорошо

Все это ООП хорошо, только зададим себе вопросы:
1) Какие чудовищные тормоза дает протокол обмена типа Detailed между объектами, особенно при групповом обновлении объектов. На практике приходится уйти обычный update. Так может это и есть правильный подход, который надо додумать?
2) Потери времени девелоперов на отладку. Сколько крови было попито.

Согласен групповые операции - серьезная задача. Для нее надо делать отдельные обработчики, что бы не было тормозов.

Как в любом деле недостатки обмена через Detailed в одних случаях, в других ситуациях становятся преимуществами.

Затраченные усилия надо сказать приносят не плохие плоды имеено в задачах, решающих прикладные проблемы.
Не много есть систем, в которых функциональность можно изменить путем добавления или изменения данных. Это эффективнее как в процессе разработки, так и в процессе тестирования - сам исполняемый модуль не меняется. Нет необходимости запускать полную процедуру тестирования, как в случае нового biuld'а.

Согласен групповые

Согласен групповые операции - серьезная задача. Для нее надо делать отдельные обработчики, что бы не было тормозов.

А зачем умножать сущности без необходимости? Работа через хорошо сконструированные view проще и эффективней. Постобработчики (после обновлений через специальные view) мы используем в 10% случаев, в конце концов тут бизнес-логика не уровня SAP.

Как в любом деле недостатки обмена через Detailed в одних случаях, в других ситуациях становятся преимуществами.

Сплошной недостаток. Центральная проектировочная бага это попытка засандалить в одну структуру все и вся. В результате Detailed нереляционная структура, хоть и таблица. Гемор по работе с ней несусветный. Буферные таблицы это рулез, НО нужно было просто создать буферные таблицы по одной на объект (у нас EXT-таблицы). Разделение доступа в буферных таблицах через systemuser. Самое смешное, что при такой концепции клиентом может быть не чудовище написанное кейсами, а любая апликуха хоть на Аксесс. Не нарушена стандартная реляционная модель, поэтому мы работаем обычными компонентами Delphi.

Затраченные усилия надо сказать приносят не плохие плоды имеено в задачах, решающих прикладные проблемы.

Какие плоды?

Не много есть систем, в которых функциональность можно изменить путем добавления или изменения данных.

Если взять 1С, она и то круче на порядок. Манипулируя метаданными можно изменять все.

Это эффективнее как в процессе разработки, так и в процессе тестирования - сам исполняемый модуль не меняется.

Это действительно плюс, мы тоже держим один EXE, но это не значит, что Detailed условие этого.

Нет необходимости запускать полную процедуру тестирования, как в случае нового biuld'а.

Тяжело сказать. Чудес не бывает. Для ЛК вы везде встраивает селф-тесты (иногда очень тяжелые) и автоматически гоняем их после каждого изменения.

Затрач

Затраченные усилия надо сказать приносят не плохие плоды имеено в задачах, решающих прикладные проблемы.

Какие плоды?

Скорость и удобство модификации приложений. Если таже такой "недоделанный" макет дает возможность за 1-2 часа добавить новый артибут и создать по нему отчет, то "причесанная" система видимо может дать возможность совсем отказаться от услуг кодеров на прикладном уровне, осанутся только аналитики, которые смогут непосредственно заносить модель в систему.

Если взять 1С, она и то круче на порядок. Манипулируя метаданными можно изменять все.

Вся бизнес-логика в 7.7 зашита в коде (про 8 не знаю), метаданные позволяют менят только структуру документов.
Про то,что 1С круче спорить не буду - она постоянно развивается ...

Это эффективнее как в процессе разработки, так и в процессе тестирования - сам исполняемый модуль не меняется.

Это действительно плюс, мы тоже держим один EXE, но это не значит, что Detailed условие этого.

Да я и не защищаю конкретную реализацию. Она имеет множетство недостатков.
Однако, для того что бы иметь возможность минупилировать данными в реляционном хранилище используя объектные методы, нужен некоторый шлюз, который должен быть одновременно и реляционным и объектным. Вот такая диалектика - единство и борьба противополженостей. Если заменить одну таблицу на набор таблиц, то каждая из них сможет удовлетворить реляционной модели, но отношения между ними не всегда будут реляционными.

По поводу объектов вообще

Я извиняюсь, что не могу конструктивно побеседовать по теме статьи непосредственно.
Мне кажется, что все решения, подобные этому, будут частными, с разной, и обычно невысокой, степенью применимости на практике, до тех пор, пока не будут решены более общие вопросы функционирования объектных информационных систем. Причём проблема сохранения состояния объектов в той или иной памяти здесь далеко не на первом месте. Вот, например, что сейчас вспомню навскидку:
- объект в рамках информационной системы и его отношение к предметам реального мира (в той части, которая этой системой автоматизируется);

- несколько экземпляров одного и того же объекта (в различных видах памяти, или в одном - неважно), их "философский" смысл, взаимодействие друг с другом и отношение к предметам реального мира;

- среда реализации поведения объекта и необходимое для этого окружение;
необъектные представления состояния объекта (они всегда бывают, например в том же SQL или в буфере какого-нибудь Grid-а, показывающего список) - как они вписываются в общую концепцию и какое влияние оказывают.

Я пытался эти идеи двигать в saveobjects, пока он ещё жив был, но, видимо, недостаточно убедителен был, а самому духу не хватило более-менее целостно подобные вопросы изложить.
Решений, описанные в статье, ведь довольно много, их, конечно, можно использовать, но неплохо бы понять, какие ограничения они накладывают, что умеют, а что не умеют принципиально, и почему.
Спасибо.

Изображение пользователя Serguei_Tarassov.

Re: По поводу объектов вообще

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

Совершенно верно, решение частно, да еще и с привязкой к СУБД.
Я помню, что ты написал небольшой документ, где эти общие проблемы популярно описывались. Он сохранился ?
Было бы интересно его опубликовать и попробовать обсудить в контексте.

Re: По поводу объектов вообще

Я помню, что ты написал небольшой документ, где эти общие проблемы популярно описывались. Он сохранился ?

Он, конечно, сохранился, но уровень проработки вопросов там поверхностный. Я-то его писал с целью наглядного обоснования терминов (на пальцах) когда мы окончательно запутались, кто что под каким словом понимает.

http://border.dts.donin.com/~demid/oop_i...
Он небольшой по объёму.

Re: По поводу объектов вообще

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

Любая работающая система не зависимо от уровня заложенной в нее абстракции, является частным решением, т.к. по определению обладает ограничениями.
MS SQL и Oracle DBMS - это частные реализации реляционного хранилища данных.
Проблема в том, здесь я полностью согласен с Demid'ом, что теоритическая база ООП слаба. Вопросы эффективного управления хранением и извличением элземпляров вообще практически ни кем не рассматривались (если не прав прошу поправить, а лучше дать ссылочку). Нет в ООП своего Кодда :-(.
А кто-нибудь рассматривал вопросы создания хранилица для объектов в иерархической СУБД, например, в DBVista? У таких СУБД структура ближе к ООП, например, можно задавать отношения между кортежами данных, т.е. описывать средствами самой СУБД иерархию классов.

Изображение пользователя Serguei_Tarassov.

Re: По поводу объектов вообще

Проблема в том, здесь я полностью согласен с Demid'ом, что теоритическая база ООП слаба. Вопросы эффективного управления хранением и извличением элземпляров вообще практически ни кем не рассматривались (если не прав прошу поправить, а лучше дать ссылочку). Нет в ООП своего Кодда :-(.
А кто-нибудь рассматривал вопросы создания хранилица для объектов в иерархической СУБД, например, в DBVista? У таких СУБД структура ближе к ООП, например, можно задавать отношения между кортежами данных, т.е. описывать средствами самой СУБД иерархию классов.

Своего Кодда, действительно, нет. Буч, все-таки, больше коммерсант и уровень у его теорий недостаточный. UML - продаваемый товар, более-менее понятные всем картинки, это все важно и нужно, но не более.
DBVista была сетевой, а не иерархической, если я не запамятовал. Этот движок и сейчас живее всех живых. Реклама на их сайте: MSSQL слишком тяжел, Access слишком легок, ваше решение: Vista DB. :)
http://www.vistadb.net

Мне кажется у нас отечественна

Мне кажется у нас отечественная школа IT-архитекторов и IT-менеджеров сильно пострадала от того, что был из "объектистов" известен только Буч. Его ООА воспринимался как единственный "супер".

Сейчас я больше сторонник Йордана и его моделирования методом use case, что более соотвествует созданию бизнес-моделей и не такой абстрационизм как моделирование Буча.

К слову ранее была только нотация swimlanes, с OMG-UML v1.3 появился Action-Object Flow Diagram.
Эта диаграмка очень хороша для моделирования документооброта и не такая абстрация как IDEF.
В Action-Object Flow Diagram видны объекты с состояниями, актеры, действия.
Мы ее использовали для создания бизнес-модели работы корп. DDB.
Рекомендую.

Изображение пользователя Serguei_Tarassov.

Мне кажется у нас

Мне кажется у нас отечественная школа IT-архитекторов и IT-менеджеров сильно пострадала от того, что был из "объектистов" известен только Буч. Его ООА воспринимался как единственный "супер".

Я бы еще добавил "тлетворное влияние" Д.Цуранова, который очень любит механизм наследования :))
У Буча, действительно, много несуразностей. Из которых он выпутывается при помощи множественного наследования, например.

У Йордана, помнится, была книжка о структурных моделях, где он пытался совместить оба подхода.
Насчет Action-Object Flow пока ничего не могу сказать, надо оценить.

Я бы еще добавил "

Я бы еще добавил "тлетворное влияние" Д.Цуранова, который очень любит механизм наследования :))

А чем так плохо наследование?
IMHO, оно повышает "повторное использование" кода и снижает издержки при исправлении ошибок.

К можественному наследованию я отношусь скорее отрицательно: иерархическая структура более управляема, чем произвольный граф.
Обойти ограничение на отсутствие множественного наследования можно созданием классов-контейнеров.

Изображение пользователя Serguei_Tarassov.

А чем так плохо на

А чем так плохо наследование?

Само по себе - ничем, плохо его необоснованное использование, приводящее к диким иерархиям, из которых кроме как через множественное наседование, не выбраться :))

"Serge

"Sergey_Bykov":
А чем так плохо наследование?

Само по себе - ничем, плохо его необоснованное использование, приводящее к диким иерархиям, из которых кроме как через множественное наседование, не выбраться :))

Кроме проектировочной ошибки Сотрудник -> Пользователь, других "диких" иерархий Цуранов не плодил, IMHO. За чем из-за одного неудачного решения вешать клеймо? (Ераре хумане ест, Не суди да не судим будешь и т.д.)
Дима - ИТ импрессионист (цитирую сам себя :) ). Не важно, что картины выглядят иногда не точными, незаверешенными или небрежными. Они красивы, в них есть изюминка, душевный порыв.

Изображение пользователя Serguei_Tarassov.

Дима - ИТ импресси

Дима - ИТ импрессионист (цитирую сам себя :) ). Не важно, что картины выглядят иногда не точными, незаверешенными или небрежными. Они красивы, в них есть изюминка, душевный порыв.

Ну, значит, это "тлетворное влияние" импрессионизма :))

Re: По поводу объектов вообще

DBVista была сетевой, а не иерархической, если я не запамятовал. Этот движок и сейчас живее всех живых. Реклама на их сайте: MSSQL слишком тяжел, Access слишком легок, ваше решение: Vista DB. :)
http://www.vistadb.net

VistaDB - это не DBVista, та была иерархическая (я это точно помню), а эта реляционная.

VistaDB is 100% self-contained RDBMS.

Взято тут

Изображение пользователя Serguei_Tarassov.

Нашел!!!RAIMA пр

Нашел!!!
RAIMA производителя DBVista купила Birdstep Tecnology.
Теперь это называется Birdstep RDM (Raima Database Manager).
Она действительно сетевая СУБД - Тарасов оказался прав, а меня память подвела :-(
http://www.raima.com/index.php3

Ааа, точно !
DBVista в клипперные времена называлась еще и Raima Data Manager.