Ответы на вопросы по Эргономичной нотации ER-модели
November 24, 2023
В этом посте я отвечу на вопросы, которые задали в комментариях к посту об Эргономичной нотации ER-модели.
Зачем нужна эта нотация? какую задачу решает?
Глобально - для тех же целей, что и оригинальная нотация Чена или нотация вороньей лапки - описание предметной области.
Для того чтобы концептуальная модель бесшовно легла на реализацию в виде деревьев неизменяемых структур данных важно, чтобы нотация её описания включала понятие слабой сущности, которого не хватает в нотации вороньей лапки.
В оригинальной нотации это понятие есть, но она сама по себе тяжеловесная - каждый атрибут и каждое отношение обозначается отдельной полноценной фигурой.
Поэтому я скрестил ужа с ежом, чтобы получить лаконичную нотацию со слабыми сущностями.
Касательно контекстов и импортированных сущностей - это просто "синтаксический сахар", который позволяет разбить одну большую диаграмму на несколько маленьких, тем самым упростив чтение каждой из них.
Теоретически, нотацию диаграммы классов UML-я так же можно приспособить для этих целей и я пробовал, но почему-то отказался. Почему - сейчас уже не могу вспомнить.
Зачем так много сущностей и что они означают?
Есть например описания что с "слабыми сущностями" нужно что-то там сделать, превратить в или из, а что это вообще такое? То же про стержневые, импортированные сущности, владеющие, невладеющие отношения
Большинство сущностей - стержневые сущности, слабые сущности, владеющие (идентифицирующие) и невладеющие отношения - это всё из оригинальной нотации Чена.
И все эти штуки, при работе в DDD-подобном стиле вообще и на базе неизменяемой модели в особенности, - присутствуют в коде.
Стержневые сущности (ака корни агрегата) - это сущности с идом, которые можно создавать независимо от других сущностей и на которые можно ссылаться.
Слабые сущности (ака не корневые сущности и объекты значения) - это сущности без ида, которые могут существовать только в контексте своей стержневой сущности, и соотвественно, ссылаться снаружи на которые можно только через их стержневые сущности.
Владеющие отношения в коде реализуются через ссылку языка программирования:
data class User(val photo: Photo)
Невладеющие отношения в коде реализуются через идентификатор сущности:
data class User(val photoId: Long)
Зачем нужны связи 1-1 если их всегда можно заменить на одну таблицу?
Сущности концептуальной модели лучше рассматривать как классы, а не таблицы. Зачем выделять класс Photo, если его можно заинлайнить в класс User?
Для этого есть много причин, сходу: такое решение нарушает SRP, принцип одного уровня абстракции и снижает связанность (cohesion) класса User.
Проиллюстрировать это можно тем, что если фото заинлайнить, то у пользователя появится свойство mime-type - что противоречит здравому смыслу. Или тем, что в этом случае у пользователя либо появится пять сеттеров (копирующих, в случае неизменяемой модели), которые могут нарушить целостность данных (поменять тип, но не содержание), либо один сеттер с пятью параметрами.
При том в БД, в принципе, отношения 1-1 со слабыми сущностями можно заинлайнить, для того, чтобы убрать джоин.
Но, если слабая сущность большая (изображение) или разные кластеры атрибутов (пользовательские и фото) часто меняются в разных конкурирующих транзакциях, то их лучше всё-таки растащить по разным таблицам.
Зачем в исходной схеме в Пост есть поле "комментарии" и связь с Комментарий
Опять же, сущности из модели надо рассматривать как классы/структуры данных и это то, как представляется владеющая связь.
Что произошло в примере кроме приведения к нормальной форме?
Поменялся тип связей Пользователь-Фото и Пост-Комментарий.
Что значит "модели данных пригодные для реализации в виде неизменяемых структур данных"
Не всякая концептуальная модель представима с помощью неизменяемых структур данных. А некоторые модели представимы, но работа с ними будет неудобной или не эффективной.
Неизменяемая модель данных - это модель данных которая состоит из классов без сеттеров. И в такой модели, например, невозможно представить двунаправленную связь. Потому что это породит бесконечный цикл - условно, чтобы создать объект пользователя, вам надо ему в конструктор передать список его постов, а чтобы создать этот список, вам надо создать объект поста, которому надо в конструктор передать объект пользователя-автора, которому в конструктор надо передать…
Если попытаться одну из ссылок сделать нуллабельной, то это решит только часть проблемы - второй объект будет иметь обратную ссылку на объект, в котором нет ссылки на него:
var user = User(id = 1, post = null)
var post = Post(id = 2, author = user)
user = User(id = 1, post = null) // здесь user.post.author == null
Можно пойти ещё дальше, но это всё равно породит "странность":
post = Post(id = 2, author == user) // если сделать так, то получим post.author.post != post
Другое ограничение, заключается в том, что с большими деревьями объектов, которые надо создавать в памяти и/или загружать из БД работать и неудобно и неэффективно.
И для того, чтобы строить концептуальные модели, которые будучи переведены в неизменяемые классы, дадут набор классов, с которым удобно и эффективно работать - я и использую Эргономичную нотацию и алгоритм её допила.
Зачем все эти сложности с неизменяемой моделью? Затем, чтобы можно было использовать ФП-стиль, который по моему мнению позволяет мне снизить стоимость разработки.
Может показаться ненужным начинать проектирование со схемы 1, потому что она "денормализована" и там есть "ненужные" 1-1 связи
Первая (концептуальная или логическая) модель строится совместно с экспертом предметной области и должна отражать его взгляд на мир.
Тут Павел правильно передал мою идею: ER-диаграмма(как на первой схеме) должна отражать сущности из бизнеса и связи между ними без погружения в технические детали. С точки зрения бизнес-аналитика именно пост содержит комментарии, а не комментарии ссылаются на пост. Начинать проектирование с такой схемы может быть полезно, чтобы, с одной стороны, говорить с бизнесом на одном языке, а с другой, раньше времени не думать о табличках в базе.
Вторая модель - строится системным аналитиком/лидом и её уже можно считать тех заданием для разработки - из этой модели классы выводятся автоматически.
Думаю, что всем будет интересно как эти схемы связанны с условными джава-классами, схемой БД, бизнес-аналитикой и прочими аспектами "реального" мира
С реальным миром связана как раз вторая модель. Перевод второй модели в классы я продемонстрирую на примере QYoga в ближайшее время.
Но уже сейчас могу описать правила перевода:
- Контекст превращается в пакет;
- Стержневая сущность превращается в класс данных (корень агрегата) и репозиторий объектов этого класса;
- Слабая сущность, превращается в класс данных, на который прямо или транзитивно ссылается корень агрегата;
- Владеющая связь превращается в поле с типом класса данных слабой сущности;
- Невладеющая связь превращается в поле с типом идентификатора ссылаемой сущности или какой-то обёртки вокруг него (AggregateReference в Spring Data JDBC).
Непонятно откуда взялось деление на контексты в первичной схеме
В оригинальном посте я писал: предположим, что путём вдумчивого анализа, мы пришли к выводу, что в этой системе есть два контекста - пользователи и посты.
Отсюда и взялось:) Это исключительно ради демонстративных целей.
В реальной жизни контексты можно искать либо с помощью техник DDD, либо с помощью декомпозиции на базе эффектов.
Я опишу, как я понимаю эти стрелочки, может быть будет полезно
- ER-диаграмма(как на первой схеме) должна отражать сущности из бизнеса и связи между ними без погружения в технические детали. С точки зрения бизнес-аналитика именно пост содежит комментарии, а не комментарии ссылаются на пост. Начинать проектирование с такой схемы может быть полезно, чтобы, с одной стороны, говорить с бизнесом на одном языке, а с другой, раньше времени не думать о табличках в базе. Однако, на самом деле непонятно откуда взялось деление на контексты в первичной схеме. Кажется, что оно должно появиться позже и каким-то более алгоритмическим путём, чем "я так чувствую".
- Финальная схема, как я понимаю, практически 1 в 1 отражает структуры классов ядра системы и связи между ними (БД пока не трогаем). Например наличие сплошной(владеющее отношение) между A и Б означение, что в коде класс A будет иметь поле типа Б. Наличие пунктирной связи(невладеющее отношение) означает отсутствие прямой связи между классами в коде. Связь в коде задается путем добавления в класс A поля b_id типа B (ниже будут примеры кода)
- Алгоритм, описанный в статье не покрывает разработку схемы БД, но сильно облегчает ее впоследствии.
Ни добавить, ни убавить - всё так.