ADR-SRV-003. Сущности и EF-маппинг

ADR Версия: 1.0 accepted

На бэке формируются доменная модель и слой хранения на EF Core. Без единых правил создания сущностей и их маппинга расходятся домен, конфигурация БД и DbContext. Фиксируем обязательный проверяемый порядок: доменная сущность по спеке → явная Fluent-конфигурация → DbSet, с ссылкой на артефакт спеки в коде.

⟨/⟩ Исходник

1. Контекст и постановка задачи

Доменные сущности описаны в спеке (50_Domain/<Module>). При переносе в код (Ban.Icd.Domain) и хранении на EF Core (Ban.Icd.Infrastructure) без единых правил быстро возникают расхождения между доменной моделью, конфигурацией БД и DbContext: неявные связи, разнобой в именовании, труднопроверяемые ревью.

Нужен обязательный, воспроизводимый и проверяемый порядок добавления сущности — чтобы реализация была предсказуемой, а расхождение spec ↔ code ловилось на ревью, а не в рантайме. БД/EF ещё не введены; правило вступает в силу с появлением персистентности.

2. Драйверы решения

  1. Соответствие доменной спеке — сущность повторяет модель и инварианты из 50_Domain.
  2. Предсказуемость — одинаковый порядок файлов и шагов для любой сущности.
  3. Меньше ошибок в EF-конфигурации — связи и хранение заданы явно.
  4. Единообразие именования связей и коллекций.
  5. Проверяемость на ревью — изменение модели данных видно и обосновано.

3. Рассмотренные варианты

4. Решение

Выбран Вариант B — минимально достаточный проверяемый контракт добавления сущности:

  1. Доменная сущность создаётся по доменной модели и её ограничениям из спеки 50_Domain.
  2. Для сущности заводится явная EF-конфигурация (Fluent API, IEntityTypeConfiguration<T>).
  3. Сущность добавляется в DbContext через DbSet<T>, имя набора — во множественном числе.
  4. Ссылки на другие сущности — как {Something}Id; навигационные связи и правила (cascade и т. п.) задаются в EF-конфигурации, не в сущности.
  5. При любой несостыковке код ↔ спека ↔ ожидаемое поведение — задать вопрос пользователю, не выбирать трактовку молча.
  6. В коде сущности — явная ссылка на файл спеки, по которому она создана (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. Положительные следствия

6. Отрицательные следствия и компромиссы

7. Проверка

8. Открытые вопросы / отложено

Связанные артефакты

Документы