ER-модель, кардинальности и уровни C4
ER-модель, кардинальности и уровни абстракции C4 — как показать, что вы умеете думать и о данных, и об архитектуре целиком.
ER-модель описывает, из каких сущностей состоят данные и как они связаны. C4 — способ показать систему на четырёх уровнях приближения, от контекста до кода, чтобы каждой аудитории досталась своя картинка.
Вопрос 1. Что такое ER-модель и как вы читаете кардинальности?
Что проверяют. Умеете ли вы переводить бизнес-правило в структуру данных и обратно. Кардинальность — это не украшение линии, а зафиксированное правило.
Связь читается с двух сторон, и обе стороны нужно проговаривать вслух:
| Запись | Как читать | Как реализуется |
| 1 : 1 | у клиента ровно один профиль настроек | внешний ключ с уникальным индексом |
| 1 : N | у клиента много заказов, у заказа один клиент | внешний ключ на стороне «много» |
| N : M | у заказа много товаров, товар в многих заказах | отдельная таблица-связка |
| 0..1 : N | заказ может быть без промокода | внешний ключ, допускающий NULL |
| 1..* | в заказе минимум одна позиция | проверка на уровне приложения или триггера |
Самое важное на собеседовании — заметить, что связь «многие ко многим» почти никогда не бывает пустой: у неё есть собственные атрибуты. У связки «заказ — товар» это количество и цена на момент покупки. Ниже — исполнимый пример: создадим схему и убедимся, что связка хранит собственные данные.
CREATE TABLE customer (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT
);
CREATE TABLE product (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
price REAL NOT NULL
);
CREATE TABLE "order" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_id INTEGER NOT NULL REFERENCES customer(id),
created_at TEXT NOT NULL,
status TEXT NOT NULL
);
-- таблица-связка N:M со СВОИМИ атрибутами
CREATE TABLE order_item (
order_id INTEGER NOT NULL REFERENCES "order"(id),
product_id INTEGER NOT NULL REFERENCES product(id),
qty INTEGER NOT NULL,
price_at_buy REAL NOT NULL,
PRIMARY KEY (order_id, product_id)
);
INSERT INTO customer (name, email) VALUES ('Анна', 'anna@example.com');
INSERT INTO product (title, price) VALUES ('Кофе', 500.0), ('Чайник', 3200.0);
INSERT INTO "order" (customer_id, created_at, status)
VALUES (1, '2026-03-01', 'paid');
INSERT INTO order_item (order_id, product_id, qty, price_at_buy)
VALUES (1, 1, 2, 450.0), (1, 2, 1, 3200.0);
-- цена в заказе зафиксирована и не зависит от текущей цены товара
SELECT c.name, p.title, oi.qty, oi.price_at_buy, p.price AS price_now
FROM "order" o
JOIN customer c ON c.id = o.customer_id
JOIN order_item oi ON oi.order_id = o.id
JOIN product p ON p.id = oi.product_id;
Запустите блок — в результирующей таблице будут две строки: «Анна, Кофе, 2, 450.0, 500.0» и «Анна, Чайник, 1, 3200.0, 3200.0».
Столбец price_at_buy отличается от price_now — ровно то бизнес-правило, о котором нужно спросить заказчика: «показывать в истории цену покупки или текущую?». В модели это одно поле, в разговоре — целое требование.
Вопрос 2. Расскажите про C4 — зачем нужны уровни?
Что проверяют. Понимаете ли вы, что диаграмма адресована конкретной аудитории. Одна схема «на всё» либо перегружена для бизнеса, либо бесполезна для разработчиков.
- Уровень 1 — контекст (System Context). Наша система как один прямоугольник, вокруг — пользователи и внешние системы. Аудитория: бизнес, заказчик, новый сотрудник в первый день. Отвечает на вопрос «где мы в мире и с кем общаемся».
- Уровень 2 — контейнеры (Container). Разворачиваем систему на разворачиваемые единицы: веб-приложение, мобильное приложение, API, база данных, брокер сообщений. Указываем технологию и протокол связи. Аудитория: команда, архитектор, аналитик. Это самый полезный уровень для аналитика.
- Уровень 3 — компоненты (Component). Внутренности одного контейнера: модули и их ответственность. Аудитория: разработчики этого сервиса. Рисуется по необходимости.
- Уровень 4 — код. Классы и связи. На практике почти всегда генерируется из кода или не рисуется вовсе.
C4, уровень 2 (контейнеры) для интернет-магазина
[Покупатель] ──HTTPS──> (SPA, браузер, Vue)
│ JSON/HTTPS
▼
(API заказов, Java)
│ │ │
SQL/JDBC │ │ │ AMQP
▼ │ ▼
[PostgreSQL] │ [RabbitMQ] ──> (Сервис уведомлений)
│
HTTPS ▼
[Платёжный шлюз — внешняя система]
Аналитику уровень контейнеров нужен как карта: по нему сразу видно, каких интеграций коснётся новая фича и с кем нужно согласовать контракт.
Вопрос 3. Логическая, концептуальная и физическая модель данных — в чём разница?
Короткий и точный ответ, который ценят: концептуальная — только сущности и связи, языком бизнеса, без атрибутов и типов; логическая — атрибуты, ключи, нормализация, но без привязки к СУБД; физическая — конкретные таблицы, типы данных, индексы, партиционирование под выбранную СУБД. Аналитик отвечает за первые две, физическую делает вместе с разработчиком или DBA.
Типичные ошибки кандидатов
- Не проговаривают кардинальность с обеих сторон и теряют бизнес-правила вроде «заказ не может быть пустым».
- Рисуют N:M без таблицы-связки и не замечают, что у связи есть собственные атрибуты.
- Не различают уровни моделей и приносят бизнесу физическую схему с индексами.
- Считают C4 «ещё одной нотацией» вместо способа подобрать уровень детализации под аудиторию.
- Забывают историчность: цена, адрес, тариф меняются, и модель обязана решить, хранить ли значение на момент операции.
Как ответить кратко
ER-модель — это сущности, их атрибуты и связи с кардинальностями, причём кардинальность я всегда читаю с обеих сторон, потому что это записанное бизнес-правило: «в заказе минимум одна позиция» — уже требование. Связь многие-ко-многим разворачиваю в таблицу-связку и проверяю, есть ли у неё собственные атрибуты — количество, цена на момент покупки. Различаю концептуальную, логическую и физическую модели: первые две на мне, физическая — вместе с разработчиком. C4 использую, чтобы дать каждой аудитории свой масштаб: контекст для бизнеса, контейнеры для команды — этот уровень мне нужнее всего, потому что по нему видно, какие интеграции затронет фича.