ADR-SRV-003. Сущности и EF-маппинг
На бэке формируются доменная модель и слой хранения на EF Core. Без единых правил создания сущностей и их маппинга расходятся домен, конфигурация БД и DbContext. Фиксируем обязательный проверяемый порядок: доменная сущность по спеке → явная Fluent-конфигурация → DbSet, с ссылкой на артефакт спеки в коде.
1. Контекст и постановка задачи
Доменные сущности описаны в спеке (50_Domain/<Module>). При переносе в код (Ban.Icd.Domain)
и хранении на EF Core (Ban.Icd.Infrastructure) без единых правил быстро возникают расхождения
между доменной моделью, конфигурацией БД и DbContext: неявные связи, разнобой в именовании,
труднопроверяемые ревью.
Нужен обязательный, воспроизводимый и проверяемый порядок добавления сущности — чтобы реализация была предсказуемой, а расхождение spec ↔ code ловилось на ревью, а не в рантайме. БД/EF ещё не введены; правило вступает в силу с появлением персистентности.
2. Драйверы решения
- Соответствие доменной спеке — сущность повторяет модель и инварианты из
50_Domain. - Предсказуемость — одинаковый порядок файлов и шагов для любой сущности.
- Меньше ошибок в EF-конфигурации — связи и хранение заданы явно.
- Единообразие именования связей и коллекций.
- Проверяемость на ревью — изменение модели данных видно и обосновано.
3. Рассмотренные варианты
- A. Свободный порядок — каждый добавляет сущность и маппинг как удобно. Быстро, но копятся расхождения.
- B (выбран). Обязательный пошаговый контракт создания сущности.
- C. Только рекомендации — без обязательности; на практике не соблюдается.
4. Решение
Выбран Вариант B — минимально достаточный проверяемый контракт добавления сущности:
- Доменная сущность создаётся по доменной модели и её ограничениям из спеки
50_Domain. - Для сущности заводится явная EF-конфигурация (Fluent API,
IEntityTypeConfiguration<T>). - Сущность добавляется в
DbContextчерезDbSet<T>, имя набора — во множественном числе. - Ссылки на другие сущности — как
{Something}Id; навигационные связи и правила (cascade и т. п.) задаются в EF-конфигурации, не в сущности. - При любой несостыковке код ↔ спека ↔ ожидаемое поведение — задать вопрос пользователю, не выбирать трактовку молча.
- В коде сущности — явная ссылка на файл спеки, по которому она создана
(XML-
<remarks>с путём к артефакту50_Domain/...; правило ссылки — вCLAUDE.md).
Разделение ответственности: логика предметной области — в доменном слое; детали хранения (типы колонок, длины, индексы, связи) — в EF-конфигурации. Изменение связи между сущностями — архитектурно значимо и проверяется отдельно.
4.1. Пример
/// <remarks>Спека: sdaid/Ban.Sdaid.Icd/50_Domain/Capture/InputPacket.cs</remarks>
public sealed class InputPacket
{
public required string Number { get; set; }
public IReadOnlyList<ImageFrame> Frames { get; set; }
// FK — как {Something}Id; навигации и cascade — в конфигурации
}
public sealed class InputPacketConfiguration : IEntityTypeConfiguration<InputPacket>
{
public void Configure(EntityTypeBuilder<InputPacket> builder)
{
builder.Property(x => x.Number).HasMaxLength(32);
builder.HasMany(x => x.Frames).WithOne().HasForeignKey(x => x.PacketId); // cascade
}
}
// DbContext
public DbSet<InputPacket> InputPackets { get; set; }
5. Положительные следствия
- Добавление сущности однотипно и быстро проверяется.
- Меньше неявных/ошибочных связей в модели данных.
DbContextконсистентен по составу наборов.- Ссылка на спеку в коде связывает сущность с источником правды.
6. Отрицательные следствия и компромиссы
- Больше явного кода — отдельная конфигурация на каждую сущность.
- На старте добавление простой сущности чуть дольше.
7. Проверка
- Новая сущность проходит чеклист 1–6 из этого ADR.
- В изменении присутствуют: доменная сущность, EF-конфигурация,
DbSetвDbContext. - Имена FK соответствуют шаблону
{Something}Id. - У сущности есть
<remarks>-ссылка на артефакт спеки; при расхождении зафиксирован вопрос пользователю до слияния.
8. Открытые вопросы / отложено
- Шаблон именования таблиц/индексов в EF-конфигурациях — фиксировать ли отдельно.
- Требовать ли seed-данные для каждой справочной сущности (сейчас справочники ввода отдаются in-memory провайдером до появления БД).
- Разделение read/write-контекстов (read-only проекции) — вводится вместе с БД.