Черновик: Основные подходы к моделированию информации - объектная и реляционные модели информации

Моделирование информации

Введение

Сущности, связи, атрибуты, состояние, данные, объекты, объекты-значения, идентичность и все-все-все

Универсальные понятия

Идентичность хреновины
На базе ИДа (места)
На базе атрибутов
Состояние
Сущность
Значение
Изменяемость/неизменяемость

Абстрактная модель

Сущность
Атрибут
??? значение/тип/домен
Связь

Модель информации на базе записей

ER-модель

Реляционная модель

ХХХ-модель на базе структур (данных)

Поля (атрибуты)

УУУ-модель на базе указателей/объектов

Объект-значение (это симуляция данных в языках, в которых есть только объекты)
Свойства (поля/атрибуты)

Сущности концептуально изменяемы. Но представлять их лучше как последовательность неизменяемых данных

Эпохальная модель времени

переход-подводка

(todo: коммент)

Основной задачей ИС является хранение информации о реальном мире. Реальный мир моделируется посредством сущностей и связей между ними. Сущности идентифицируются ключём - идентификатором.

В программе сущности представляются объектами, которые могут быть изменяемыми, либо неизменяемыми.

Связи между сущностями могут быть представлены либо ссылкой на объект ссылаемой сущности, либо идентификатором ссылаемой сущности. Большинство (все?) связи в реальном мире двунаправленные - по учебной группе можно определить всех преподавателей этой группы, а по преподавателю можно определить все обучаемые им группы. Но в программе их можно моделировать ограничиться однонаправленной связью.

Объектно-ориентированный подход

Сейчас самым распространённым подходом к моделированию реальности является объектно-ориентированный. Этот подход представляет информацию в виде полносвязного (через ссылки), двунаправленного графа изменяемых объектов. Такой подход знаком всем программистам и лёгок в решении локальных задач - имея ссылку на объект, можно пройтись по ссылкам и добраться до любого другого объекта и тут же его изменить. Но эта лёгкость несёт и большое количество проблем.

Проблемы связанные с изменяемыми объектами находятся за рамками темы поста и сейчас я рассмотрю только проблемы вызванные представлением связей как двунаправленных ссылок. Такой подход несёт проблемы в двух аспектах - дизайне и производительности.

Возьму за аксиомы два утверждения: . декомпозиция системы на слабосвязные модули - это хорошо . наличие циклов в связях между модулями - это плохо

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

Результатом этого является архитектура "Большой ком грязи", при которой изменение в любой функции может внести регрессию в любую другую функцию и любой рефакторинг может потребовать изменений по всей кодовой базе.

Из того, что система превращается в большой ком грязи также следуют и проблемы с производительностью. Если данные хранятся в БД, а информация представлена полносвязным двунапрвленным графом, то попытка загрузить любой объект из БД приведёт к загрузке (по ссылкам) практически всей БД в память программы. Естественно, на практике это невозможно и для обхода этой проблемы современные ОРМы используют ленивую загрузку. Но ленивая загрузка приносит с собой проблему нелокальности рассуждений - глядя только на вызов геттера невозможно сказать отработает ли он в памяти или же выполнит запрос к БД.

Кроме того, ленивая загрузка усугубляет проблемы производительности при работе с отношениями один ко многому, когда "много" - это действительно много. В этом случае даже загрузка только идентификаторов может стать проблемой, если их тысячи. А если потом ссылаемые объекты будут загружаться по одному, то это фактически парализует работу приложения. И даже если ссылаемых объектов всего десятки - этого количества достаточно чтобы загрузка объектов по одному превратилась в проблему (у это проблемы даже есть собственное имя - N + 1).

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

Реляционный подход

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

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

Наконец, если симуляцию работы с БД доводить до конца, объекты должны быть как минимум копиями - ни кто не должен видеть изменения в объекте, до тех пор пока программа не сохранит его в массиве (аналог UPDATE + COMMIT).

Такой подход решает все проблемы присущие объектно-ориентированному.

Если ассоциативный массив скрыть за интерфейсом модуля (сервиса или репозитория), то модуль будет контролировать все обращения к массиву. Двунаправленные ссылки хоть и формально возможны в реляционной модели, но настоятельно не рекомендуются, т.к. ведут к дублированию информации.

Проблемы с загрузкой полного графа объектов нет по определению. Благодаря этому нет необходимости в ленивой загрузке и как следствие утери локальности рассуждений. Вместе с ленивой загрузкой уходит и проблема N+1. А проблема огромного списка идентификаторов исключается требованием к первой нормальной форме (атомарность атрибутов) и возможность проходить по связям в обратном направлении.

Наконец, в реляционной модели есть жёсткая граница между элементами модели и для того чтобы внести в транзакцию новую сущность, потребуется приложить существенные усилия (как минимум выполнить дополнительный запрос, а может и добавить ссылку на модуль/класс, который позволит этот запрос выполнить) и у разработчика будет шанс задуматься о влиянии изменения на размер транзакции.

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

Возьмём классический пример с заказами и позициями заказа. А в качестве инварианта возьмём следующий: "Сумма стоимости позиций заказа с постплатой не должна превышать 1000 рублей". В этом случае изменение цены или количества отдельной взятой позиции может привести к нарушению этого инварианта.

Эту проблему можно решить, если нарушить требования первой нормальной формы и поместить список позиций заказа "внутрь" сущности заказа и смоделировать связь посредствам ссылки, а не идентификаторов. Такую вариацию реляционного подхода я называют функциональным подходом к моделированию данных.

Но это скользкий путь - если зайти слишком далеко, то мы снова придём к ОО-модели со всеми её проблемами.

Для того чтобы понять какие связи моделировать ссылками, а какие идентификторами я использую концепцию агрегата из Domain-Driven Design - кластера сущностей, которые должны быть всегда согласованы между собой и удовлетворять бизнес-правилам.

DDD-подход

Таким образом получается, что DDD-подход является комбинацией ООП/ФП и реляционного подхода. Внутри агрегатов используется объектно-ориентированная или функциональная модель - связи моделируются ссылками. А между агрегатами используется реляционная модель - связи моделируются идентификаторами.

DDD-предполагает нарезку модели данных на агрегаты - кластеры сущностей, которые:

  1. Должны быть всегда и строго согласованы
  2. Поэтому являются единицей персистанса - всегда загружаются и сохраняются целиком
  3. Поэтому должны быть минимально возможного размера (при соблюдении п.1)

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