⟨/⟩ 40_Arch/Tech/CSharp/ADR_SRV_003_EntityAndEfMapping.cs

148 строк · в начало

using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;

namespace Ban.Sdaid.Icd.Arch.Tech.CSharp
{
    /// <summary>
    /// ADR-SRV-003: Правила создания доменных сущностей и их маппинга в EF Core. Обязательный
    /// пошаговый контракт: домен по спеке → явная Fluent-конфигурация → DbSet, со ссылкой на спеку в коде.
    /// </summary>
    public class ADR_SRV_003_EntityAndEfMapping : IAdrDocument, IFolder<ArchCSharpFolder>
    {
        public string Name => "ADR-SRV-003. Сущности и EF-маппинг";

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

        public string Version => "1.0";
        public string Status => "accepted";
        public string[] Comments => new[]
        {
            "2026-08-12. Принято вместе с ADR-SRV-001/002. БД/EF ещё не введены — правило действует с появлением персистентности (первый write-срез).",
        };

        public Type? Supersedes => null;

        public static string S1_Context = """
            ## Контекст и постановка задачи

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

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

        public static string S2_DecisionDrivers = """
            ## Драйверы решения

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

        public static string S3_ConsideredOptions = """
            ## Рассмотренные варианты

            - **A. Свободный порядок** — каждый добавляет сущность и маппинг как удобно. Быстро, но копятся
              расхождения.
            - **B (выбран). Обязательный пошаговый контракт** создания сущности.
            - **C. Только рекомендации** — без обязательности; на практике не соблюдается.
            """;

        public static string S4_DecisionOutcome = """
            ## Решение

            Выбран **Вариант 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-конфигурации. Изменение связи между сущностями —
            архитектурно значимо и проверяется отдельно.

            ### Пример

            ```csharp
            /// <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; }
            ```
            """;

        public static string S5_PositiveConsequences = """
            ## Положительные следствия

            - Добавление сущности однотипно и быстро проверяется.
            - Меньше неявных/ошибочных связей в модели данных.
            - `DbContext` консистентен по составу наборов.
            - Ссылка на спеку в коде связывает сущность с источником правды.
            """;

        public static string S6_NegativeConsequences = """
            ## Отрицательные следствия и компромиссы

            - Больше явного кода — отдельная конфигурация на каждую сущность.
            - На старте добавление простой сущности чуть дольше.
            """;

        public static string S7_Validation = """
            ## Проверка

            - Новая сущность проходит чеклист 1–6 из этого ADR.
            - В изменении присутствуют: доменная сущность, EF-конфигурация, `DbSet` в `DbContext`.
            - Имена FK соответствуют шаблону `{Something}Id`.
            - У сущности есть `<remarks>`-ссылка на артефакт спеки; при расхождении зафиксирован вопрос
              пользователю до слияния.
            """;

        public static string S8_OpenQuestions = """
            ## Открытые вопросы / отложено

            - Шаблон именования таблиц/индексов в EF-конфигурациях — фиксировать ли отдельно.
            - Требовать ли seed-данные для каждой справочной сущности (сейчас справочники ввода отдаются
              in-memory провайдером до появления БД).
            - Разделение read/write-контекстов (read-only проекции) — вводится вместе с БД.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_001_Cqrs)} — CQRS: команды работают с сущностями через единицу работы.
            - {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_002_GridQueryContract)} — списки строятся на внутренних моделях/проекциях сущностей.
            """;
    }
}