Черновик: Основные подходы к моделированию информации - объектная и реляционные модели информации
Моделирование информации
Введение
Сущности, связи, атрибуты, состояние, данные, объекты, объекты-значения, идентичность и все-все-все
Универсальные понятия
Идентичность хреновины
На базе ИДа (места)
На базе атрибутов
Состояние
Сущность
Значение
Изменяемость/неизменяемость
Абстрактная модель
Сущность
Атрибут
??? значение/тип/домен
Связь
Модель информации на базе записей
ER-модель
Реляционная модель
ХХХ-модель на базе структур (данных)
Поля (атрибуты)
УУУ-модель на базе указателей/объектов
Объект-значение (это симуляция данных в языках, в которых есть только объекты)
Свойства (поля/атрибуты)
Сущности концептуально изменяемы. Но представлять их лучше как последовательность неизменяемых данных
Эпохальная модель времени
переход-подводка
(todo: коммент)
Основной задачей ИС является хранение информации о реальном мире. Реальный мир моделируется посредством сущностей и связей между ними. Сущности идентифицируются ключём - идентификатором.
В программе сущности представляются объектами, которые могут быть изменяемыми, либо неизменяемыми.
Связи между сущностями могут быть представлены либо ссылкой на объект ссылаемой сущности, либо идентификатором ссылаемой сущности. Большинство (все?) связи в реальном мире двунаправленные - по учебной группе можно определить всех преподавателей этой группы, а по преподавателю можно определить все обучаемые им группы. Но в программе их можно моделировать ограничиться однонаправленной связью.
Объектно-ориентированный подход
Сейчас самым распространённым подходом к моделированию реальности является объектно-ориентированный. Этот подход представляет информацию в виде полносвязного (через ссылки), двунаправленного графа изменяемых объектов. Такой подход знаком всем программистам и лёгок в решении локальных задач - имея ссылку на объект, можно пройтись по ссылкам и добраться до любого другого объекта и тут же его изменить. Но эта лёгкость несёт и большое количество проблем.
Проблемы связанные с изменяемыми объектами находятся за рамками темы поста и сейчас я рассмотрю только проблемы вызванные представлением связей как двунаправленных ссылок. Такой подход несёт проблемы в двух аспектах - дизайне и производительности.
Возьму за аксиомы два утверждения: . декомпозиция системы на слабосвязные модули - это хорошо . наличие циклов в связях между модулями - это плохо
Так вот традиционный подход к моделированию информации исключает возможность декомпозиции системы на модули - код из любого модуля, может по ссылкам добраться до объекта из любого другого модуля в обход этого модуля и как минимум считать информацию. А двунаправленные связи создают циклы в связях между модулями.
Результатом этого является архитектура "Большой ком грязи", при которой изменение в любой функции может внести регрессию в любую другую функцию и любой рефакторинг может потребовать изменений по всей кодовой базе.
Из того, что система превращается в большой ком грязи также следуют и проблемы с производительностью. Если данные хранятся в БД, а информация представлена полносвязным двунапрвленным графом, то попытка загрузить любой объект из БД приведёт к загрузке (по ссылкам) практически всей БД в память программы. Естественно, на практике это невозможно и для обхода этой проблемы современные ОРМы используют ленивую загрузку. Но ленивая загрузка приносит с собой проблему нелокальности рассуждений - глядя только на вызов геттера невозможно сказать отработает ли он в памяти или же выполнит запрос к БД.
Кроме того, ленивая загрузка усугубляет проблемы производительности при работе с отношениями один ко многому, когда "много" - это действительно много. В этом случае даже загрузка только идентификаторов может стать проблемой, если их тысячи. А если потом ссылаемые объекты будут загружаться по одному, то это фактически парализует работу приложения. И даже если ссылаемых объектов всего десятки - этого количества достаточно чтобы загрузка объектов по одному превратилась в проблему (у это проблемы даже есть собственное имя - N + 1).
Наконец, отсутствие видимых границ в данных приводит к тому, что в одну транзакцию попадают слабосвязанные между собой изменения, что приводит к увеличению транзакции (количества изменяемых данных и длительности), что приводит либо к дата рейсам, либо к повышению оверхеда на синхронизацию.
Реляционный подход
Другой крайностью к моделированию информации является реляционная модель. Если вытащить реляционную модель на уровень программы, то она будет представлять из себя набор ассоциативных массивов, содержащих объекты.
Связи между объектами реализуются через идентификаторы - некий ключ, который содержит указание на массив, в котором содержится объект и ключ по которому его искать. При этом связи достаточно делать однонаправленными - для того чтобы пройти по обратному направлению (найти объекты ссылающиеся на данный) достаточно выполнить запрос к массиву ссылающихся объектов и отфильтровать их по значению связи.
Наконец, если симуляцию работы с БД доводить до конца, объекты должны быть как минимум копиями - ни кто не должен видеть изменения в объекте, до тех пор пока программа не сохранит его в массиве (аналог UPDATE + COMMIT).
Такой подход решает все проблемы присущие объектно-ориентированному.
Если ассоциативный массив скрыть за интерфейсом модуля (сервиса или репозитория), то модуль будет контролировать все обращения к массиву. Двунаправленные ссылки хоть и формально возможны в реляционной модели, но настоятельно не рекомендуются, т.к. ведут к дублированию информации.
Проблемы с загрузкой полного графа объектов нет по определению. Благодаря этому нет необходимости в ленивой загрузке и как следствие утери локальности рассуждений. Вместе с ленивой загрузкой уходит и проблема N+1. А проблема огромного списка идентификаторов исключается требованием к первой нормальной форме (атомарность атрибутов) и возможность проходить по связям в обратном направлении.
Наконец, в реляционной модели есть жёсткая граница между элементами модели и для того чтобы внести в транзакцию новую сущность, потребуется приложить существенные усилия (как минимум выполнить дополнительный запрос, а может и добавить ссылку на модуль/класс, который позволит этот запрос выполнить) и у разработчика будет шанс задуматься о влиянии изменения на размер транзакции.
Однако у реляционного подхода есть проблема - обеспечение инвариантов включающих несколько сущностей. Если каждую сущность можно изолировано модифицировать, то такая модификация может привести к нарушению инварианта.
Возьмём классический пример с заказами и позициями заказа. А в качестве инварианта возьмём следующий: "Сумма стоимости позиций заказа с постплатой не должна превышать 1000 рублей". В этом случае изменение цены или количества отдельной взятой позиции может привести к нарушению этого инварианта.
Эту проблему можно решить, если нарушить требования первой нормальной формы и поместить список позиций заказа "внутрь" сущности заказа и смоделировать связь посредствам ссылки, а не идентификаторов. Такую вариацию реляционного подхода я называют функциональным подходом к моделированию данных.
Но это скользкий путь - если зайти слишком далеко, то мы снова придём к ОО-модели со всеми её проблемами.
Для того чтобы понять какие связи моделировать ссылками, а какие идентификторами я использую концепцию агрегата из Domain-Driven Design - кластера сущностей, которые должны быть всегда согласованы между собой и удовлетворять бизнес-правилам.
DDD-подход
Таким образом получается, что DDD-подход является комбинацией ООП/ФП и реляционного подхода. Внутри агрегатов используется объектно-ориентированная или функциональная модель - связи моделируются ссылками. А между агрегатами используется реляционная модель - связи моделируются идентификаторами.
DDD-предполагает нарезку модели данных на агрегаты - кластеры сущностей, которые:
- Должны быть всегда и строго согласованы
- Поэтому являются единицей персистанса - всегда загружаются и сохраняются целиком
- Поэтому должны быть минимально возможного размера (при соблюдении п.1)
Такая комбинация позволяет обеспечить соблюдение инвариантов, без проблем с дизайном и производительностью, свойственных ОО-подходу.