Ответы на вопросы по Эргономичной нотации 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 в ближайшее время.

Но уже сейчас могу описать правила перевода:

  1. Контекст превращается в пакет;
  2. Стержневая сущность превращается в класс данных (корень агрегата) и репозиторий объектов этого класса;
  3. Слабая сущность, превращается в класс данных, на который прямо или транзитивно ссылается корень агрегата;
  4. Владеющая связь превращается в поле с типом класса данных слабой сущности;
  5. Невладеющая связь превращается в поле с типом идентификатора ссылаемой сущности или какой-то обёртки вокруг него (AggregateReference в Spring Data JDBC).

Непонятно откуда взялось деление на контексты в первичной схеме

В оригинальном посте я писал: предположим, что путём вдумчивого анализа, мы пришли к выводу, что в этой системе есть два контекста - пользователи и посты.

Отсюда и взялось:) Это исключительно ради демонстративных целей.

В реальной жизни контексты можно искать либо с помощью техник DDD, либо с помощью декомпозиции на базе эффектов.

Я опишу, как я понимаю эти стрелочки, может быть будет полезно

  1. ER-диаграмма(как на первой схеме) должна отражать сущности из бизнеса и связи между ними без погружения в технические детали. С точки зрения бизнес-аналитика именно пост содежит комментарии, а не комментарии ссылаются на пост. Начинать проектирование с такой схемы может быть полезно, чтобы, с одной стороны, говорить с бизнесом на одном языке, а с другой, раньше времени не думать о табличках в базе. Однако, на самом деле непонятно откуда взялось деление на контексты в первичной схеме. Кажется, что оно должно появиться позже и каким-то более алгоритмическим путём, чем "я так чувствую".
  2. Финальная схема, как я понимаю, практически 1 в 1 отражает структуры классов ядра системы и связи между ними (БД пока не трогаем). Например наличие сплошной(владеющее отношение) между A и Б означение, что в коде класс A будет иметь поле типа Б. Наличие пунктирной связи(невладеющее отношение) означает отсутствие прямой связи между классами в коде. Связь в коде задается путем добавления в класс A поля b_id типа B (ниже будут примеры кода)
  3. Алгоритм, описанный в статье не покрывает разработку схемы БД, но сильно облегчает ее впоследствии.

Ни добавить, ни убавить - всё так.