При проектировании веб-систем более-менее неигрушечного размера (форум, баг-тракинг, wiki) обычно принято хранить данные в какой-нибудь серверной БД.
При разработке TaCo я решил отойти от этого решения. И вот почему...
Impedance mismatch
Или точнее "Object-relational impedance mismatch" - это понятие, которое описывает тот факт, что объектно-ориентированное моделирование и реляционное моделирование построены на разных принципах, и когда их приходится совмещать, это не всегда происходит гладко. Я как-то не смог найти в сети внятного и точного технического описания проблемы, но самая вменяемая, на мой взгляд, статья есть на Wikipedia.
Тем не менее, я, как человек привыкший работать именно с объектно-ориентированным подходом, и являющийся большим его поклонником, частенько на это самое "несовпадение" наталкиваюсь. Среди прочего, меня всем этом вопросе беспокоят следующие аспекты.
Наследование
Теория, что объектная, что баз данных, говорит о том, что логические объекты предметной области следует делать объектами или таблицами соответственно (это я упрощенно). Однако дальше начинаются неприятные различия: объекты могут наследоваться, и им свойственнен полиморфизм. Таблицы же - это плоские образования. Сплошь и рядом в объектной программе можно иметь контейнеры разных наследников одного базового объекта, но хранить в одной таблице записи с разным количеством полей нельзя. Все это можно обойти в БД, но эти способы обычно довольно кривы, и с ними сложно работать.
Инкапсуляция данных
Объект должен иметь все данные, с которыми работает, что называется "при себе". Причем, независимо от их структуры. Наример, если в программе как-то представляется ветка форума, то все ее сообщения будут храниться в ней. В базах данных же данные группируются не по логическому отношению, а по типам: все сообщения одной ветки форума могут быть как угодно размазаны в табличке с сообщениями из всех веток.
На практике это выливается в то, что данные, необходимые объектной программе в каждый момент работы, часто запрашиваются очень обширными запросами, затрагивающими много таблиц. И не всегда оптимизатор БД может обеспечить достаточно быструю выборку этих данных.
Избыточное обеспечение целостности
Теория баз данных довольно жестко делает упор на то, что ссылочная целостность данных должна обеспечиваться в самой БД. Однако простой ссылочной целостности часто бывает недостаточно: есть еще различные бизнес-правила, которые накладывают свои ограничения (например, "цена товара при начислении любых скидок не должна быть меньше такой-то"). Для этого в базах данных есть триггеры, где такие правила можно проверить.
Но дальше появляется интересный момент: хорошая объектная программа заботится о корректном состоянии своих объектов сама, а не полагается на ошибки БД (через минуту к тому еще вернусь).
Другими словами, поддержку корректности данных в таких программах часто приходится делать два раза: на уровне БД и на уровне приложения.
Разные способы поддержки корректности
Итак, почему бы объектной программе не полагаться на БД для проверки корректности данных.
Первая причина очень простая: обращение за каждой пустяковой проверкой корректности данных в базу - это неимоверный удар по производительности.
Вторая причина состоит в том, что способы обеспечения корректности данных у БД и в объектах просто работают по-разному. БД дает полностью открытый доступ к данным, и только на этапе фиксации данных проверяет их корректность Объект же внутренние данные скрывает и предоставляет четко ограниченный интерфейс к данным, который гарантирует, что с ними нельзя сделать ничего некорректного.
На практике эти два подхода стыкуются плохо. Приведу для иллюстрации очень частный пример из недавнего прошлого.
Представим, что нам нужно отслеживать возврат клиентами товаров в интернет-магазин. Нужно знать, когда какие товары принесли и на каких складах они лежат. Для этого есть такие объекты:
-
"Возврат товара" - он означает некий факт того, что в магазин обратился клиент с желанием вернуть товар. Этот Возврат может быть в разных состояниях: "Оформлен", "Принят у клиента", "Передан на экспертизу"...
-
"Товар" - у Возврата есть список связанных с ним Товаров, так как клиент может за один раз принести их несколько.
В БД эти объекты лежат в виде двух табличек, и табличка Товаров ссылается на табличку Возвратов через внешний ключ - стандартное решение.
Есть разные правила сосуществования всех этих объектов и одно из них для примера: чтобы Возврат можно было перевести в состояние "Принят у клиента", у каждого Товара должно быть проставлено, на какой склад он принят.
Программа дает свободно редактировать и состояния Возврата и Товары пользователю, и дает кнопку, по которой сохраняет изменения в базу, просто вызывая сохранения двух табличек:
Procedure TReturn.Save;
Begin
GoodsTable.Save;
ReturnTable.Save;
End;
Эти две строчки по сути просто вызывают соответствующие SQLные UPDATE'ы для таблички Товаров и таблички Возвратов. Заметьте порядок сохранения: сначала сохраняются зависимая табличка, чтобы при сохранении таблички Возврата БД могла проверить, что у всех Товаров действительно проставлена ссылка на склад.
Все, вроде, просто? А теперь представим, что наш Возврат только что создан в программе, и в базе его еще нет. При попытке сохранить табличку зависимых Товаров БД тут же ругнется, что она не может вставлять зависимые записи Товаров, поскольку не существует главной для них записи Возврата - нарушается ссылочная целостность. И вот приходится писать совершенно дурацкую вилку, что "If запись новая Then сохранять в другом порядке". И следить уже за думя кусками кода, если понадобится добавить сохранение еще какой-нибудь таблички.
Теперь главное, что я хотел показать этим примером. Объектная программа может позволить себе временную некорректность данных по ходу исполнения кода метода, потому что она знает, что в этом методе будет в итоге сделано все, что надо. С точки же зрения БД вызовы запросов никак не связаны между собой. Она не знает, что за одним запросом обязательно придет другой, и "все будет хорошо", поэтому единственный для БД выход - обеспечивать целостность при каждом запросе. Что сильно снижает гибкость работы с данными.
Да, все это, опять же, можно обойти. Например создавать на такие комплексные действия отдельные хранимые процедуры. Но это во-первых, еще большее дублирование кода приложения в базе, а во-вторых, во всех языках, что я видел, еще не научились работать с процедурами БД так же прозрачно, как с собственными процедурами языка, и все эти вызовы объектов БД сильно мусорят код.
Пути решения
Мне известно, по большому счету, два способа разрешать это "несовпадение импеданса".
Первый - это ORM - Object-Relational Mapping. К программе подключается некая библиотека или даже платформа, которая прозрачно для объектного программиста переводит его объекты в модель данных в базе. Проверяет типы, целостность и т.д. Таким образом она решает проблему дублирования: модель данных и доступ к ней определяются один раз в программе, а БД строится по ней автоматически и внимания не требует.
Но платой за универсальность обычно является эффективность: библиотека понятия не имеет, что лучше для логики именно вашего приложения, она действует именно универсальными методами.
Второй подход - это вообще исключить БД, как отдельный уровень системы, и устранить эту сложность на корню. Вместо этого каждый объект должен уметь себя сохранять и восстанавливать. Каким именно способом - программист решает сам.
TaCo
В TaCo я выбрал как раз второй подход. Поскольку система простая и компактная, особенно много думать над сохранением объектов не приходится: они сохраняются в XML-файлы, с которыми в Python'е работать - одно удовольствие.
Немного о том, в какие плюсы и минусы это выливается по ходу разработки:
Плюсы:
- Я могу строить всякие странные хитрые индексы типа списка задач, упорядоченного по времени их последнего изменения, при этом у них нет даже никакого отдельного поля для этого.
- У меня никогда не будет болеть голова про SQL injection.
Минусы:
- Придется писать собственную обработку одновременного доступа к файлам из разных процессов, БД бы сделала это сама.
В общем и целом, я совсем не уверен, что этот подход обязательно себя оправдает. Это именно эксперимент. Но вместе с тем, сама идея мне нравится. Она хорошо вписывается в концепцию новых методов проектирования, когда какая-то структура или функциональность считается "ненужной, пока не доказана ее неоходимость", а не "необходимой, пока не доказана ее ненужность".
Комментарии: 22
В порядке эксперимента это конечно интересно, но вообще в БД есть такое понятие как транзакции ;)
Правда я как раз недавно жалел, что в MySQL 3.23 это не поддерживается.
А как транзакции отменяют все, что написано в статье? :-)
Хотя я понимаю, откуда может быть путаница. Это, видимо, о той части, где базу нужно временно вывести из корректного состояния... Но это не то. Транзакция обеспечивает целостность логической операции. А вот ссылочная целостность поддерживается базой всегда, не дожидаясь commit'а текущей транзакции, после любого оператора.
Транзакции я, на самом деле, намеренно не трогал, чтобы еще больше не усложнять :-)
База — это не столько механизм хранения, сколько механизм построения выборок. Делать же более-менее нетривиальные выборки из файлового хранилища сложно. Особенно если на этапе создания программист не озаботился реализацией (вручную!) нужных индексов, а через год эксплуатации они вдруг понадобились.
Угу, я как раз о том и пишу.
Вот как раз в том и смысл, что выборок делать не нужно. Данные на диске инкапсулированы точно так же, как они инкапсулированы в объекте, когда он в памяти. Фактически между данными на диске не существует никаких специально оформленных связей, они возникают только когда объект нпосредственно инстанциируется (черт, как это по-русски? ) из своего сериализованного представления, которое сам же и записал.
Все не так сложно, как звучит. Если код сам по себе не является кашей, то добавить индекс - это очень просто. Это просто отсортированный массив по какому-то признаку. И его также легко сдампить на диск.
Изюминка же еще и в том, что вы не ограничены индексированием, как единственным способом оптимизации. Можно использовать особенности работы программы. Вот пример прямо из головы. Особенность багтракинга в том, что большую часть времени пользователь работает с активными тасками, количество которых особенно не растет. Закрытые же таски постоянно складываются в архив. Вот в этом случае можно сильно все ускорить, если выделить активные таски в отдельное место (логическое или физическое - как удобней).
... и в конечном итоге получить базу данных, написанную с нуля :)
Хочу еще один раз разъяснить ключевое непонимание, которое, судя по всему, есть у многих :-).
Смысл в том, что не писать базу данных. Ни писать, ни имитировать, ни страдать от ее отсутствия.
Смысл в том, чтобы устранить impedance mismatch за счет устранения базы данных. Объектная модель обладает достаточными средствами защиты данных сама по себе, что делает в данном конкретном случае базу данных не нужной.
Идея-то, кстати, не новая. Если интересно поискать источники - Google is your friend.
может быть стоило посмотреть в сторону нереляционных БД? что-то вроде BDB-XML или eXist
http://www.rpbourret.com/xml/XMLDatabaseProds.htm#native
Вопрос скорее представляет теоритический интерес, чем практический :) Ты сам себе противоречешь и скатываешся на изобретение велосипеда. В реалии же проще и быстрее устранить impedance mismatch и следовательно быстрее получить конечный результат, при этом вполне качественный. Хотя не сомневаюсь что из спортивного интереса ты все сделаешь хорошо и правильно :) И не понятно чем тебя так Phyton зацепил, взял бы яву с ее уже встроенной сериализацией. Все имхо..
Тоже проводил такие эксперименты. в т.ч. на Python/XML - результат так себе. Причем чем более долгосрочный проект, тем больше проблем вылазит. Хотя, тут все конечно от задачи зависит.
Ну а извечный спор RDBMS vs. OODBMS по-моему не имеет решения. :)
Интересный подход, понравится тем, кто не может/не хочет работать с базой данных, например: удаленная разработка + дешевый хостинг без базы данных/с еле шевелящейся базой данных. С год назад на valuehost'е я мучалась с базой, которая не отвечала постоянно, не знаю как у них сейчас дела обстоят.
В плане переносимости TaCo тоже проще получается.
Я знаю об удачном примере отказа от базы данных по причине того что было сложно организовывать неплоские структуры данных. На последней КРИ человек из Нивала рассказывал об их ресурсной системе (запись и слайды, если кому интересно). Но они и от XML отказались и в итоге пришли к своему формату.
Кстати, насчет извечных споров... Где-то я читал (потерял ссылку) довольно большую статью про то, что священные войны таки имеют решение: история расставляет все по своим местам. Так случилось с войной "call или goto", так случилось с войной "процедуры или объекты" и т.д.
Так что, поживем увидим :-).
Oracle позволяет определить правила целостности таким образом, чтобы они проверялись только после commit.
Хороший пост по поводу XML abuse ;)
Мне кажется проблема что называется "притянута за уши". Я сам разрабатываю под веб почти исключительно на ООП и никогда не сталкивался с проблемой реализации взаимодействия между БД и обьектной моделью.
Мне кажется что автор не понимает разницу между обьектами и данными (а БД это просто хранилище данных + описание структуры данных). У данных и не должно быть ни какого наследования, ближе всего в терминах ооп для данных и таблиц БД подходит static или final, от которых нельзя наследовать они просто есть.
Сообщения могут быть также представлены отдельными обьектами и ветка форума может о них ничего не знать. Так что инкапсуляция тут не к месту даже если ветка ето класс - контейнер для сообщений (которые тоже обьекты) так как "in capsula" подразумевает всего лиш что свойства и методы конкретного обьекта должны хранится в нем.
Ну да если писать как в ПХП нюке где сотня запросов для генерации простой странички. Нормальные веб приложения по минимуму дергают БД так как "пустяковые" проверки можно сделать и в самом коде.
Обьект данные не скрывает а инкапсулирует путать эти понятия нельзя!
Программа то может а только вот нормальный програмист не позволит себе такое написать. В нормально написаной программе с самого начала проверяеться корректность данных.
Срочно читаем про транзакции и не позоримся.
Если правильно программировать она и так не будет болеть по этому поводу, а при неумении бороться с хаками их применят при любом подходе к реализации программы.
Нашёл условно-полезный документ "НадРеляционный Манифест", как мне кажеться в тему
Мда, как видно у всех одно и то же на уме :) Зашли бы Вы, что ли, на sql.ru в форм "Сравнение БД".... Там есть несколько тем, в которых эти вопросы активно обсуждаются и рассмтариваются. Их только надо поискать:
"Сравнение объектно-ориентированных, реляционных и объектно-реляционных СУБД" - это классика :) .
"СУБД CACHE'" - что еще есть.
"Объектные СУБД от Versant Corporation (FastObjects .NET, FastObjects t7, Developer Suite)" - объекты, это не только полезно...
"Cerebrum : Сетевая объектно-ориентированная система управления базой знаний (1,2,3,4,5,6,7,8,все)" - спешка нужна только при ловле блох.
"НРМ" - все совсем не так, как оно есть на самом деле...
"РМД пора на пенсию?" - да ну?.
Что касается поднятой темы, то ИМХО типичный для програмиста подход. В данном случаае, когда речь идет о "...проектировании веб-систем более-менее неигрушечного размера..." он ИМХО может быть и оправдан, просто не стоит забывать, что в целом термины "БД" и "СУБД" гораздо более наполнены, чем просто хранение данных для какой-либо программы, и, тем более, для какой-либо конкретной программы.
С одной стороны:
1. Не все хранится в базе. (профиль пользователя - куки, ini, реестр, внешние xml)
2. Разные СУБД имеют свои возможности и недостатки, сконвертировать при необходимости будет не так просто. У меня есть пара проектов, которые неплохо было бы распространить и на веб (на Yaffil'е и Sybase'е), реинжиниринг и прочие xDBC не предлагать.
3. Дэдлоки сакс.
С другой стороны:
1. Транзакции
2. Распределенность
3. Хранимые процедуры, не перегружающие сервер приложений (хотя для веба я так понимаю не сильно актуально)
4. мощь select.
5. все уже написано. парсер написать проще, чем ээфективную модель хранения данных. в итоге можно упереться в производительность, ибо прогнозу новое решение сложно поддается. Как такой вид хранения будет ворочить 20-ю гигами инфы? Другой вопрос будут ли они эти 20 гигов...
Мое мнение, если есть желание и/или возможность реализовывать такой вид хранения, то надо делать. Ждем результатов.
Может, пишешь слишком сложный код? Будь проще :). Мне даже обьекты не всегда нужны, хотя не сказал бы, что очень простые вещи пишу. Вообще, обьектная модель имеет нехилые минусы. Она очень удобна при разработке GUI (в delphi|VC++). А в текстовом мире веба иногда черезмерно всё усложняет.
Перекрёстные запросы в критичных местах - это просто ошибки при разработке структуры базы. Полдня над размышлениями над структурой надо провести хотя бы. (до написания первой строчки кода) А можно и день.
применительно к PHP - magic_quotes_gpc on + всё и всегда заключать в кавычки (даже если поля - числа). Можно добавить $temp=(int)$temp для целночисленных значений.
И можно про SQL-иньекции забыть в принципе.
Иван Сагалаев>