Данная публикация является переводом статьи Jeff-a Atwood-а почти 20-ти летней давности. Jeff Atwood, один из фаундеров StackOverFlow, написал эту статью как некоторое резюме того, как человечество боролось с проблемой O/R Impedance Mismatch.

Прежде чем перейти к самой теме, хочу пояснить, почему я вообще взялся переводить эту старую (но по-прежнему актуальную) статью. В рамках предстоящего бесплатного события, которое мы вместе с Haulmont проведём по Spring Data JDBC, мы неизбежно будем сталкиваться с концептуальными проблемами, корни которых уходят далеко в прошлое — к самой природе O/R Impedance Mismatch.

Чтобы обсуждение на ивенте было более предметным, полезно иметь общий контекст и понимание того, как эта проблема исторически развивалась и какие подходы к её решению предпринимались. Именно поэтому я решил подготовить этот перевод: если вы познакомитесь с материалом заранее, вам будет значительно проще понять меня на эфире, откуда растут ноги у многих решений Spring Data JDBC, какие компромиссы в неё заложены и почему.


В этом году мне посчастливилось встретиться с Тедом Ньюардом на конференции TechEd. В конце 2004 года Тед, среди прочего, впервые произнёс знаменитую фразу: «ORM — это Вьетнам нашей индустрии».

Эта аналогия пугающая, но точная. Я не раз видел, как разработчики годами боролись с огромным разрывом между реляционными моделями баз данных и классическими объектными моделями. И все предлагаемые ими решения, похоже, лишь усугубляли проблему. Я полностью согласен с Тедом: у проблемы маппинга объектов в реляционные структуры (ORM) нет хорошего решения. Решения, конечно, существуют, но все они требуют серьёзных и болезненных компромиссов. И хуже всего то, что последствия этих компромиссов обычно проявляются лишь на поздних стадиях разработки.

Недавно Тед опубликовал давно ожидаемую запись в своём блоге, в которой детально и всесторонне проанализировал проблему ORM. Это очень длинный пост. Но если вы не являетесь «ветераном с боевыми шрамами» от бесконечных ORM-войн, настоятельно рекомендую хотя бы пробежаться по нему глазами, чтобы осознавать множество подводных камней, ожидающих вас при попытке внедрить ORM-решение. Вокруг полно «серебряных пуль», и не меньше наивных разработчиков, верящих в них.

Пост Теда отличный и авторитетный, но несколько многословный; читая его, я почувствовал, будто сам побывал в том самом «Вьетнаме». Поэтому давайте сразу перейдём к резюме в конце статьи, где Тед перечисляет современные (и будущие) пути решения проблемы ORM:

1. Полный отказ от объектов. Разработчики полностью отказываются от объектной модели и возвращаются к подходу программирования, который вообще не создаёт проблемы несоответствия между объектами и реляционными таблицами. Хотя такой подход может показаться неприятным, в некоторых сценариях объектно-ориентированный подход порождает больше накладных расходов, чем приносит пользы, и возврат инвестиций (ROI - Return On Investment) просто не оправдывает затрат на создание богатой предметной модели. (Фаулер подробно обсуждает эту идею.) Проблема здесь решается элегантно: если объектов нет, то и несоответствия тоже не существует.

Речь о том, чтобы просто отказаться от идеи возвращать "сущности" / "объекты" из слоя работы с БД. Возвращать просто некоторые проекции данных, по сути просто Tuples из базы данных. И далее, соответственно, уже работать с этой проекцией.

2. Полное принятие альтернативных хранилищ. Разработчики отказываются от реляционного хранения вообще и используют модели хранения, которые естественным образом соответствуют парадигме их языка программирования. Системы хранения объектов, например проект db4o, элегантно решают проблему, записывая объекты непосредственно на диск и тем самым устраняя многие (хотя и не все) вышеупомянутые трудности.

Для расширения кругозора читателей скажу, что решения по типу db4o, ObjectDB и т.д. являются так называемыми OODBMS - Object Oriented DBMS. Раньше, в 2000-2010, приобретала популярность интересная идея - если проблема Impedance Mismatch настолько велика, то может быть для ООП языков и для ORM вообще не использовать традиционные реляционные БД?

Может быть, получится сделать БД, которая будет не хранить строки и работать по правилам реляционной алгебры, а будет прямо полноценно хранить объекты? Тогда не придётся решать проблему Impedance Mismatch, т.к. само по себе хранилище более не реляционное.

Надо сказать, что большая часть таких OODBMS долго не прожила, например коммерческая разработка db4o уже давно не ведется. Причин "неудачи" OODBMS решений довольно много, обсуждать их в сноске я не буду. Задача просто показать, как исторически человечество пробовало решить эту проблему.

Например, здесь не возникает «second schema problem», поскольку единственной схемой является сама структура объектов. Многие администраторы баз данных (DBA) могут прийти в обморок от такой мысли, но в мире, всё больше ориентированном на сервисы — где прямой доступ к данным отвергается, а весь доступ должен осуществляться исключительно через сервисные шлюзы, инкапсулирующие механизм хранения, — становится вполне реально представлять разработчиков, хранящих данные в удобной для них форме, а не в той, которую предпочитают DBA.

3. Ручное отображение. Разработчики признают, что задача вовсе не так сложна, как кажется, и пишут прямой код для реляционного доступа, получающий данные из базы, извлекающий кортежи и при необходимости заполняющий объекты. Во многих случаях такой код может даже генерироваться автоматически с помощью инструментов, анализирующих метаданные базы данных, что устраняет основное возражение против этого подхода («слишком много кода для написания и поддержки»).

Очень хорошим примером может послужить библиотека Jooq в Java экосистеме. Какой-то совершенно базовый маппинг Jooq сделать способен, но в случаях, когда результирующий датасет из базы данных не является плоским, разработчикам приходится писать мапинг самим. Маппинг, конечно, может быть довольно непростым, и потом начинаются подобного рода дискуссии. Этот путь известный.

4. Принятие ограничений ORM. Разработчики принимают как данность, что невозможно эффективно и просто полностью устранить несоответствие между объектами и реляционными таблицами. Они используют ORM для решения 80% (или 50%, или 95% — в зависимости от задач) проблемы, а для оставшихся случаев прибегают к прямому SQL и реляционному доступу (например, «сырому» JDBC или ADO.NET). Однако такой подход несёт в себе собственные риски: разработчики должны учитывать любые механизмы кэширования, реализованные внутри ORM, поскольку прямой реляционный доступ, очевидно, не сможет использовать этот кэш.

Кстати, именно такой подход и предлагает сам Hibernate.

На мой взгляд, основываясь на моем опыте, для большей части проектов это именно тот самый sweet spot. Моя практика говорит, что ORM в, условно, 80% случаев стоит себя, но в условных 20% - нет. И никто не мешает для этих 20% использовать другие решения

5. Интеграция реляционных концепций в язык программирования. Разработчики соглашаются, что эту проблему следует решать на уровне языка, а не с помощью библиотек или фреймворков. На протяжении последнего десятилетия основное внимание уделялось попыткам «подтащить» объекты ближе к базе данных, чтобы программисты могли сосредоточиться исключительно на объектной парадигме. Однако в последние годы интерес к «скриптовым» языкам с мощной поддержкой множеств и списков (например, Ruby) пробудил идею другого решения: встроить реляционные концепции (по сути — основанные на множествах) прямо в основные языки программирования, облегчив тем самым преодоление разрыва между «множествами» и «объектами». На сегодняшний день такие усилия ограничиваются в основном исследовательскими проектами и «нишевыми» языками, но уже появляются интересные инициативы, набирающие популярность в сообществе: гибридные функционально-объектные языки вроде Scala или F#, а также прямая интеграция в традиционные ОО-языки — например, проект LINQ от Microsoft для C# и Visual Basic. К сожалению, одна из подобных попыток потерпела неудачу — стратегия SQL/J; даже в ней подход был ограниченным: она не стремилась интегрировать множества в Java, а просто предлагала встраивать SQL-вызовы, которые предварительно обрабатывались и транслировались в JDBC-код.

6. Интеграция реляционных концепций во фреймворки. Разработчики признают, что проблема разрешима, но требует смены взглядов. Вместо того чтобы ждать решения от создателей языков или библиотек, они переосмысливают саму природу «объектов», делая её более реляционной, и строят предметные фреймворки, напрямую основанные на реляционных конструкциях. Например, вместо создания класса Person, хранящего данные экземпляра непосредственно в собственных полях, разработчики создают класс Person, данные которого хранятся в RowSet (Java) или DataSet (C#). Такие структуры легко собираются вместе в единый блок данных для последующего обновления в базе или же распаковываются из базы данных в отдельные объекты.

Позже Тед опубликовал ещё одну запись, в которой ответил на наиболее распространённые критические замечания по поводу своего первого поста. Если вы уже занесли палец над кнопкой «комментировать», сначала прочитайте это продолжение.

Лично я считаю, что единственный рабочий способ решения проблемы ORM — это выбрать что-то одно: либо полностью отказаться от реляционных баз данных, либо полностью отказаться от объектов. Уберите из уравнения O или R — и проблема отображения исчезнет сама собой.

Может показаться безумием отказаться от привычного объекта Customer — или от классической таблицы Customer, — но выбор одного из двух подходов — вполне разумная альтернатива той сложной трясине из классов, объектов, генерируемого кода, SQL и хранимых процедур, к которой обычно приводят попытки «решить» ORM.

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

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Комментарии (5)


  1. vat78
    03.12.2025 11:12

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


  1. amironov
    03.12.2025 11:12

    Если честно, то статья на сегодняшний день малоактуальна. За 20 лет много чего изменилось: появились быстрые мапперы, кодогенераторы, linq в том же c# давно обыденность. "Тяжелые" ORM сейчас, если и имеет смысл использовать, то только для небольших CRUD приложений.


  1. inkelyad
    03.12.2025 11:12

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

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


    1. noavarice
      03.12.2025 11:12

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

      Оригинал ссылается на другой пост (в веб-архиве), в котором сначала идёт приличной протяженности описание событий Вьетнама и их последствий, затем связь Вьетнама с проблемами ORM, и затем идёт длинное и достаточно подробное описание конкретных проблем ORM (identity vs equivalence, inheritance и т.д.). Если я правильно понял, что имеется в виду под "не умеют объяснять, в чем проблема".


      1. inkelyad
        03.12.2025 11:12

        Есть статья (ссылка вытащено из википедии)

         затем идёт длинное и достаточно подробное описание конкретных проблем ORM

        Которые ничего практически ничего не объясняют по поводу "А чего неудобно-то и где грабли?".
        А должно было бы быть что то вроде:

        Скрытый текст

        Берем классическое "Заказ <-> позиции заказа" (на псевдо-sql)

        TABLE Orders (
            order_id INT PRIMARY KEY,    
        );
        TABLE OrderItems (
            order_item_id INT PRIMARY KEY,
            order_id INT NOT NULL,
        );

        Вот что тут написано? Что OrderItem без Order не существует.
        Теперь смотрим, как это в языке делается(условный ЯВУ):

        order = new Order()
        order_item = new OrederItem(данные поля); // Ой, OrderItem есть, но какого заказа?
        order.add(order_item)

        Если соответствовать схеме БД, одно должно делаться где-то как

        order = new Order()
        order_item = order.add();
        order_item.fill(данные поля)

        Все, несоответствие пропало. (Причем такое возможно - в Java при помощи тех же Inner Class. Но так делается далеко не всегда)
        Причем первый способ - тоже можно без указанной накладки сделать.

        TABLE Orders (
            order_id INT PRIMARY KEY,    
        );
        TABLE OrderItems (
            order_item_id INT PRIMARY KEY,
        );
        TABLE OrdeToItems(
            order_id INT NOT NULL,
            order_item_id INT NOT NULL
        )

        Тогда заказы, позиции и какая в каком - становятся отдельно, как и в ЯВУ, могут существовать отдельно и вкладывать одно в другое по мере необходимости.

        Ну и так далее, по каждому пункту объяснений несоответствия должны быть примеры.

        Ни одной статьи не видел.