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

    От автора


    Прошел год как я сменил профессию сетевого администратора на профессию программиста. За этот год было много необычного и странного. Удалось плотно поработать с 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, пара по потимизации запросов и отложеной загрузке объектов, абзац по свойствам отношений (ага, и такое бывает), но актуально ли это? Прошу критиков высказываться в коментариях. Особо острые и интересные вопросы включу в продолжение.
    Поделиться публикацией

    Комментарии 22

      +2
      Простите, а что в вашей статье имеет отношение к проблематике ORM? На мой взгляд вы еще даже уровень модели сущностей и отношений не покинули, до реализации еще шагать и шагать.
        +5
        Многие не понимают что такое ORM, потому что не могут абстрагироваться от конкретных реализаций универсальных ORM, «почему-то» позволяющих просто отображать лишь очень простые отображения. А ведь ORM это лишь код, позволяющий однозначно соотнести объекты (конкретные!) программы и записи (не менее конкретные!) в RDBMS. Он вовсе не обязан быть универсальным, он вовсе не обязан быть дескриптивно-декларативным, он вовсе не обязан быть отображающим свойство на столбец и наоборот, он вовсе не обязан быть ещё каким-то, кроме как решающим две задачи (и то не обязательно в общем случае): состояние объектов должно однозначно отображаться на БД и наоборот. Реализуем мы скалярное свойство объекта через поле в таблице или через 15 таблиц один-к-одному, а то и многие-ко-многим (но через соглашение типа «на свойство объекта маппится последняя по времени запись, а свойство объекта маппится на последнюю запись с таким же значением или новую, если у последней значение не такое»), а то и вообще через хранимку с хэшем от id объекта конкатенированным с фазой луны, никого кроме разработчика ORM, согласовывающего свои действия с прикладным разработчиком и разработчиком БД волновать не должно.
          –1
          Эх… этот прекрасный новый мир в котором есть разработчик ORM, прикладной разработчик и разработчик БД… очень часто считается что первого заменит офф. документация, второй есть в наличии (ну как же, код пишет) а третий не нужен, т.к. хватит все той-же офф. документации… да и вобще, зачем тогда ORM?
          Поймите, я не свое мнение высказываю, просто сталкиваясь с таким подходом прибываю в шоке. Самый вопиющий подход который используют «прикладные разработчики» я и попытался рассмотреть.
            0
            Так это ещё лучше в каком-то смысле получается: един в трех лицах и ничто не мешает написать ORM заточенную под задачу и подогнать под неё же схему БД.

            Проблема в том что очень часто под специфические задачи пытают использовать универсальные ORM, вместо того чтобы написать свою строго под задачу заточенную.
          +1
          Эмм… я скорее хотел поднять вопрос проблем использования orm программистами не понимающими как в конечном счете данные будут отображены в таблицы. Некоторые считаеют что orm — эта такая магия, используя которую можно просто указать драйвер, описать пару классов и все шоколадно. А проблемы миграции, сериализации, сохранения отображения (структуры) — им чужды.
            0
            Это смотря с какой стороны посмотреть. Одна из целей абстрагирования в разработке ПО как раз и заключается в том, что бы особо не думать о том, что находится на нижних уровнях и как именно работает.
              +2
              Имхо слишком провокационный заголовок. На первый взгляд создаётся впечатление, что вы призываете программистов не учить SQL, хотя без SQL в профессии вообще делать нечего.

              ORM имеют множество достоинств, проявляющихся в длительной перспективе. Это прежде всего безопасность запросов (ORM обычно берёт на себя экранирование); потом представление параметров типами данных основного языка, без утомительной конверсии; автоматизация бэкапа и деплоя в условиях навороченных constraints; прозрачные кэширование данных и отложенные запросы; переносимость (если ORM работает с несколькими СУБД)… Что точно не является достоинством ORM − так это абстрагирование от SQL. Это даже скорее недостаток. В гипотетическом случае, когда достоинства ORM не востребованы, вызов SQL будет проще писаться, лучше читаться (SQL использует концепцию литературного программирования, если что) и работать производительней.

              Кстати, в вашем случае я бы создал одну таблицу для мужчин, женщин и прочих гендеров-шмендеров, а соотношения many-to-many между ними так же реализовал бы в виде отдельной промежуточной таблицы. Нет никаких проблем в том, чтобы связи между строками одной таблицы задать в другой таблице, но об этом часто забывают. Считайте это пропагандой нетрадиционных отношений. :)
                0
                Да, заголовок, как и содержание, получился очень провакационным… У меня есть подозрение что большинство негатива как раз вызвано формой и сумбурностью материала. Нет, я понимаю, что прописные истины на хабре идут плохо, но это повсеместное засилье «реализаций по оф. документации» которорые сводятся к прочтению примера из 2-х моделей — добивает.
              +4
              Красота-то какая… Ну, подумаешь, пара лишних джойнов при селекте…
                0
                Вы открыли EAV? Работаете в медицине, где предметная область, настолько деревянна и многопризначна, что ужос? Вот не уловил сути. Поток сознания надо обработать и покачественней выдать.
                  +1
                  Нет. Человек открывает для себя реляционные базы данных через… ORM! Трагедия! Трагедия!
                  +7


                  Прошел год как я сменил профессию сетевого администратора на профессию программиста.

                  Прошу вас. Вернитесь обратно. Право слово всем будет лучше. Если все же не хотите, то мой совет. Прежде чем браться за ORM прочитайте хотя бы это Основы современных баз данных. Хотя цикл лекций написан давно, но актуальности особо он не потерял.
                  Далее после того когда вы поймете что такое реляционная база данных и научитесь нормально проектировать базы данных, вот только тогда можно взять ORM. ORM является дырявой абстракцией. И если вы хотите чтобы это нормально работало и это можно было использовать, то движение должно быть база данных -> ORM -> объекты. Любые люди которые говорят что мы сейчас объектов наделаем и через ORM их в базу положим и все будет чики-пуки врут. Это работает только для малых объемов данных и малого количество объектов. Любое увеличение данных и или количества объектов, приводит к резкому снижению производительности и большому числу проблем.
                    0
                    Если пропустить Ваш выпад по поводу «вернитесь обратно», то весь текст поста можно заменить вашим коментарием. Серьезно. Я и хотел донести что ORM не панацея! Видимо стоит очень серьезно подумать над формой и содержанием собственных мыслей и их передачей…
                    +9
                    в переводе на язык Пушкина означает «Объектно-реляционное отображение»
                    Нѣтъ, «Объектно-реляціонное отображеніе». Языкъ Пушкина имѣлъ на 4 буквы больше, не говоря ужъ объ иныхъ возможностяхъ орѳографіи.
                      0
                      Программисты забывают о первой буковке абравиатуры и пхнут в одну и ту же табличку все! Начиная от свойств объектов, что логично, и, заканчивая foreign key, что никакого отношения к объекту не имеет!

                      Шта? Или я не совсем понял куда у автора впихивается foreign key или автор имел ввиду чтото другое.
                      Ведь в ORM на начальном уровне мы описываем Model что в реляционных базах есть таблица, с описанием свойств атрибутов, и конечноже один из атрибутов таблицы/модели будет foreign key.
                      Ну а потом уже работа с инстансами/объектами конретной модели/таблицы.

                      Или речь шла не об этом…?
                        0
                        Думаю, не совсем поняли. Автор связи one-to-one впихивает в отдельную таблицу т.е. то что (вероятно) вы обычно делаете для many-to-many связей.
                          0
                          … и конечноже один из атрибутов таблицы/модели будет foreign key.
                          Ну а потом уже работа с инстансами/объектами конретной модели/таблицы.

                          Вот об этом я и говорил… :-)
                          «там все просто! вжик-вжик и побежали» — повсеместная трактовка алгоритма работы с ORM (и как следствие с БД). Многие думают что структура данных не важна, просто пиши код.
                          Знаете, ORM со своим ООП подходом создает илюзию что форма хранения не важна (мало кто разбирался как в памяти хранятся экземпляры обектов), можно просто делать «new» и все будет хорошо. Мое мнение: ЭТО НЕ ТАК!
                            0
                            Так тоже самое бывает и ДБДшников некоторые из которых не знают языков програмирования и ООП.
                            И создают таблицы с foreign key_ями не вынося релейшены в отдельну таблицу.

                            Ну окей. Вы имели ввиду что ОРМ ваще облегчает писание кода без релейшн таблиц не вспоминая о подобной возможности.
                              +1
                              Знаете, ORM со своим ООП подходом создает илюзию что форма хранения не важна (мало кто разбирался как в памяти хранятся экземпляры обектов), можно просто делать «new» и все будет хорошо. Мое мнение: ЭТО НЕ ТАК!

                              Просто вы судите об ORM по тем примерам, с которыми вы знакомы.

                              В реальности, задача ORM в том, чтобы форма хранения действительно не имела значения (это называется «абстракция от хранилища»), вопрос в том, насколько успешно эта задача выполняется (и выполнима).
                                0
                                Это, конечно, не так. Но ООП модель и схема хранения её состояния в БД отношение друг к другу имею весьма посредственное. Задача ORM в том и состоит, чтобы однозначно связать их. И эти связи вовсе не ограничиваются тупым «класс <-> таблица, объект <-> строка, поле <-> колонка». Скажем, один объект может записан в паре таблиц или наоборот — одна запись одной таблицы маппиться на несколько объектов.
                            0
                            Для каждого пола желательно (но не принципиально) иметь свою табличку

                            Как раз это противоречит объектной модели.

                              –1
                              Согласен, но в данном случае я просто не хотел заострять на этом внимание.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое