Pull to refresh

ORM или как забыть о проектировании БД

Reading time 5 min
Views 167K

От автора


Прошел год как я сменил профессию сетевого администратора на профессию программиста. За этот год было много необычного и странного. Удалось плотно поработать с sqlalchemy, взглянуть на ponyorm, поиграться с hibernate и nhibernate, освежить models из django… и знаете что? Весь код который я видел ассоциировался у меня с последствиями деятельности обезьяны имеющей запас гранат… оказалось что пускать программистов к бд — не самая лучшая затея. Под катом много боли и ненависти, но вдруг кому-то пригодится.

Прежде чем начать, хочу ввести пару допущений:
  1. Весь текст представляет собой IMHO
  2. Все имена и кейсы — синтетичны. Любое совпадение с реальными проектами и персоналями — случайность.
  3. У автора большие проблемы с Великим и Могучим (боремся через личные сообщения)


Что такое ORM?


Прежде чем учить кого-то уму-разуму стоит понять что представляет из себя термин ORM. Согласно аналогу БСЭ, аббревиатура ORM скрывает буржуйское «Object-relational mapping», что в переводе на язык Пушкина означает «Объектно-реляционное отображение» и означает «технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования»… т.е. ORM — прослойка между базой данных и кодом который пишет программист, которая позволяет созданые в программе объекты складывать/получать в/из бд.
Все просто! Создаем объект и кладем в бд. Нужен этот же объект? Возьми из бд! Гениально! НО! Программисты забывают о первой буковке абравиатуры и пхнут в одну и ту же табличку все! Начиная от свойств объектов, что логично, и, заканчивая foreign key, что никакого отношения к объекту не имеет! И, что самое страшное, многие тонны howto и example пропагандируют такой подход… мне кажется что первопричина кроется в постоянной балансировке между «я программист» и «я архитектор бд», а т.к. ORM плодятся и множатся — голос программиста давлеет над архитекторским. Все, дальше боли нет, только imho.

«Кто Вы, Мистер Брукс?» или «Что такое объект?»


Как определить «что такое объект» в ORM? Сколько человек смогут с первого раза донести весь смысл? Давайте я попробую.
Предположим что все мы живем на территории Белоруссии/Украины/России, прям как я. Согласно законодательству, в стране запрещены однополые браки и многоженство. При этом есть понятие «воинской обязанности» и прочих «специфичных» для каждого пола свойств. Что из этого следует?
  1. Для каждого пола желательно (но не принципиально) иметь свою табличку
  2. Где-то надо хранить информацию о принадлежности мужа жене и обратно

«Дык это ж OneToOne! Классика! Чего тут сложного? Определим fkey у строки для мужского или женского пола и побежали дальше» — ага, я умею читать мысли. НО! Наше ПО через год стало интересно арабским шейхам и они недоумевают, как так, не более 1 жены?!?! Да и кому fkey добавлять? Мужскому полу? Женскому? А если все в одной таблице — кто напишет и будет сопровождать логику проверяющую что у дамы приклонного возраста не появится супруга?
Непонятно? Ок. Давайте псевдокодом:
man_1 = new Man()
man_1.name = "Иван"
woman_1 = new Woman()
woman_1.name = "Таня"
???
А что дальше?
man_1.wife = woman_1 или woman_1.husband = man_1
А как сохранить ЭТО в бд? А никак! В примере заведомо не верный подход к структуре данных!
Свойство name объектов woman_1 и man_1 — является свойством объекта ORM, а вот cвойство wife объекта man_1 и husband объекта woman_1 — это уже отношения объектов ORM! ЭТО РАЗНЫЕ СВОЙСТВА!
Все еще не понятно? Ок. Давайте еще более гуманитарным языком подойдем к вопросу.
Есть Иван и есть Татьяна. Если они забракуются то? Правильно! Они не изменятся! А если оторвать руки (или иной важный орган) Ивану и сбрить волосы Татьяне — они изменятся! Так вот, брак — отношение объетов, а руки и волосы — свойства объектов. Все, я не знаю как еще объяснить.

Тяжкое наследие ООП


Худо-бедно мы пришли к пониманию объекта. Стало вполне понятно кто есть кто. Но как ЭТО сохранить в бд? Некоторые товарищи с умными лицами и длинными бородами пропагандируют утверждение «все есть объект». Давайте попробуем придерживаться того-же, раз уж мы рассматриваем ORM. Допустим для объектов Man и Woman мы используем разные таблицы. Где будем хранить «объект» их отношения? Правильно! В ОТДЕЛЬНОЙ ТАБЛИЦЕ! Назовем ее woman_and_man. В табличке пока будет всего 2 колонки представляющих собой fkey: man_id и woman_id.
«Постойте, любезный, это уже ManyToMany какой-то!» — ага, я все еще читаю ваши мысли! Впринципе, я с вами соглашусь, с одной маааленькой поправкой. Если для таблицы woman_and_man определить правильные constraint — она легким движением руки превращается в OneToOne. Не верите? Пробуем!
Для случая OneToOne нам необходимо соблюсти следующие правила:
  1. Одна женщина не может иметь более одного мужа мужского пола
  2. Один мужчина не может иметь более одной жены женского пола

Введем соответствующие constraint:
  1. значение man_id впределах таблицы woman_and_man должно уникально и не null
  2. значение woman_id впределах таблицы woman_and_man должно уникально и не null

Все! У нас OneToOne!
Отправляем ПО на экспорт в ОАЭ — меняем constraint для man_id и получаем ManyToOne/OneToMany! Обратите внимание, структура таблиц не изменится, изменятся только constraint!
Отправляем ПО на экспорт в страну со взаимным многоженством/многомужеством (а есть такие?!) — меняем constraint для woman_id и получаем ManyToMany! Обратите внимание, структура таблиц не изменится, изменятся только constraint!
Проверьте ваши ORM — вы найдете пример использования OneToOne/ManyToOne/OneToMany через доп. таблицу.

Критикам посвящается


После высказывания своих мыслей руководителю я получил вполне ожидаемую реакцию: «Зачем так усложнять? KISS!»
Пришлось «набраться опыта»:

Были случаи с циклическими связями между объектами содержащими среди свойств fkey и задачей «бекапа/сериализации» этого безобразия в xml/json. Нет, бекапы то делаются, вот восстанавливать потом это безобразие чертовски сложно… необходимо жестко отслеживать какие свойства создаются при восстановлении/десериализации, а потом повторно проходить по объектам и восстанавливать связи между ними. Придерживаясь правила выше — надо сначала восстановить объекты, а уж потом связи между ними. Т.к. хранится эта информация в разных таблицах/сущностях — логика была линейной и простой.

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

P.S. Это далеко не весь «поток сознания» связаный с ORM. Есть еще пара абзацев по null, пара по потимизации запросов и отложеной загрузке объектов, абзац по свойствам отношений (ага, и такое бывает), но актуально ли это? Прошу критиков высказываться в коментариях. Особо острые и интересные вопросы включу в продолжение.
Tags:
Hubs:
-11
Comments 22
Comments Comments 22

Articles