using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;
namespace Ban.Sdaid.Icd.Arch.Tech.CSharp
{
/// <summary>
/// ADR-SRV-001: CQRS-архитектура бэкенда и единая обёртка ответа. Фиксирует, как
/// организованы операции над доменом (команды и запросы), как встраивается сквозная
/// логика (pipeline) и какой контракт результата возвращают все ручки.
/// </summary>
public class ADR_SRV_001_Cqrs : IAdrDocument, IFolder<ArchCSharpFolder>
{
public string Name => "ADR-SRV-001. CQRS и обёртка ответа";
public string Description =>
@"Бэкенд СОК реализует операции над доменом (ввод, карты, документы, обработка). Операции делятся
на команды (меняют состояние) и запросы (читают). Фиксируем единый способ их организации: свой лёгкий
CQRS без внешних зависимостей, тонкий контроллер → процессор → обработчик, авто-регистрация обработчиков,
место для сквозной логики (pipeline) и **единую обёртку ответа** для всех ручек.";
public string Version => "1.0";
public string Status => "accepted";
public string[] Comments => new[]
{
"2026-08-12. Принято перед первым реальным эндпоинтом (справочники ввода). Read-ветка уже реализована каркасом; command-ветка, pipeline и БД — целевые, вводятся под первый write-срез.",
};
public Type? Supersedes => null;
public static string S1_Context = """
## Контекст и постановка задачи
Каждая операция бэкенда — это либо **команда** (создать/изменить/удалить, поставить пакет в
очередь), либо **запрос** (справочник, список, карточка). Нужен **один способ** их организации,
чтобы:
- код был однороден: операция = предсказуемый набор файлов в предсказуемом месте;
- сквозную логику (логирование, трассировку, будущую валидацию) можно было встроить, не трогая
обработчики;
- чтение и запись опирались на разные контексты данных (read-only без трекинга vs write);
- контроллер оставался тонким: принял запрос → отдал обработчику → вернул результат;
- **форма ответа была единой** у всех ручек — успех/ошибки/сообщение в одном контракте, который
одинаково разворачивает фронт и его сгенерированный клиент.
Текущее состояние — каркас: реализована только read-ветка (`Ban.Icd.Cqrs`: процессор запросов и
авто-регистрация обработчиков), без pipeline-шагов; command-ветка, БД/EF и разделение
read/write-контекстов ещё не введены.
""";
public static string S2_DecisionDrivers = """
## Драйверы решения
1. **Однородность** — каждая операция живёт в отдельной папке, структура повторяется.
2. **Сквозная логика без правки обработчиков** — логирование/трассировка/валидация как шаги
pipeline вокруг обработчика.
3. **Разделение чтения и записи** — запросы на read-only контексте (no tracking), команды — на
write через единицу работы.
4. **Тонкий контроллер** — не содержит бизнес-логики, делегирует процессору.
5. **Минимум зависимостей** — без внешнего медиатора; контроль над процессором и pipeline у нас.
6. **Единый результат** — все ручки возвращают один базовый контракт ответа.
""";
public static string S3_ConsideredOptions = """
## Рассмотренные варианты
- **A. Свой лёгкий CQRS** — интерфейсы запроса/команды + процессоры + pipeline в отдельном
проекте `Ban.Icd.Cqrs`. Полный контроль, нет внешних зависимостей.
- **B. MediatR** — готовый медиатор с `IPipelineBehavior`. Экосистема есть, но лишняя
зависимость и чужие абстракции ради того, что делается парой интерфейсов.
- **C. Без абстракций** — контроллер вызывает сервисы напрямую. Быстро на старте, но теряется
однородность и негде встроить сквозную логику.
""";
public static string S4_DecisionOutcome = """
## Решение
Выбран **Вариант A** — свой лёгкий CQRS. Нет внешних зависимостей, процессор и pipeline полностью
под нашим контролем, контракт единообразен.
### Раскладка проектов
```
src-api/
Ban.Icd.Cqrs/ ← контракты CQRS и процессоры (без ссылок на Api/Domain)
Queries/Abstracts/ ← IIcdQuery, IIcdQueryHandler, IIcdQueryResult, IIcdQueryProcessor
Queries/ ← IcdQueryProcessor; (позже) IIcdQueryPipelineStep + шаги
Commands/… ← (целевое) IIcdCommand, IIcdCommandHandler, IIcdCommandResult,
IIcdCommandProcessor, IIcdCommandPipelineStep
Ban.Icd.Api/
Application/
CommonModels/ ← IcdBaseRequestResult, ErrorDetails, (grid) PagingModel/SortingModel
Abstracts/ ← (grid) IcdBaseGridQuery, IcdBaseGridQueryResult
Basement/ ← IcdBaseQueryHandler / IcdBaseCommandHandler, ApiException
Extensions/, Helpers/ ← QueryableExtensions, SortingApplier
<Area>/Queries/<Name>/ ← Query + (UserFilter) + Result + Handler
<Area>/Commands/<Name>/ ← Command + Result + Handler
Ban.Icd.Domain/ ← доменные сущности (спека 50_Domain)
Ban.Icd.Infrastructure/ ← инфраструктура; (позже) EF, контексты, единица работы
```
### Контракты read-ветки (реализованы)
```
IIcdQuery<TResult> маркер запроса
IIcdQueryResult маркер результата (bool IsSuccess)
IIcdQueryHandler<TQuery,TResult> .HandleAsync(query, ct)
IIcdQueryProcessor .ProcessQueryAsync<TQuery,TResult>(query, ct)
```
Процессор резолвит обработчик под пару (запрос, результат) из DI и делегирует ему; контроллеры
зависят от **процессора**, а не от конкретных обработчиков (через него же пройдёт pipeline).
### Контракты command-ветки (целевое, вводятся под первый write-срез)
```
IIcdCommand<TResult> / IIcdCommandResult / IIcdCommandHandler / IIcdCommandProcessor
IcdBaseCommandHandler ← общий базовый обработчик: авто-SaveChanges при IsSuccess
```
### Единая обёртка ответа
Все результаты (и запросов, и команд) наследуют один базовый контракт:
```csharp
public record IcdBaseRequestResult : IIcdQueryResult, IIcdCommandResult
{
public Guid? RequestId { get; init; } // для трассировки (пока резерв)
public bool IsSuccess => Errors.Length == 0; // успех = нет ошибок
public int StatusCode { get; init; } = 200; // HTTP-код операции
public string? Message { get; init; } // сопроводительное сообщение
public ErrorDetails[] Errors { get; init; } = []; // детализация ошибок
}
public record ErrorDetails
{
public string? ErrorData { get; init; } // поле/контекст ошибки
public ErrorCode ErrorCode { get; init; } // код ошибки (enum)
public string? ErrorMessage { get; init; } // текст (по-русски, от бэка)
}
```
Полезная нагрузка кладётся в поле `Payload` конкретного результата — фронт единообразно
разворачивает `payload` и показывает `errors` (тексты ошибок формирует бэк, UI их не переводит).
### Тонкий контроллер
```csharp
[ApiController]
[Route("api/[controller]")]
public sealed class InputPacketController(IIcdQueryProcessor processor) : ControllerBase
{
[HttpGet]
public Task<GetInputQueueQueryResult> Get(CancellationToken ct)
=> processor.ProcessQueryAsync<GetInputQueueQuery, GetInputQueueQueryResult>(
new GetInputQueueQuery(), ct);
}
```
Запросы без вложенных параметров — `GET`; grid-запросы с вложенными `Paging`/`Sorting`/`UserFilter`
принимают тело через `POST` (см. ADR-SRV-002), т. к. вложенные объекты не ложатся в query string.
### Регистрация в DI (авто-обнаружение)
```csharp
builder.Services
.RegisterQueryProcessor()
.RegisterQueryHandlers(executingAssembly); // все IIcdQueryHandler<,> из сборки
// (целевое) то же для команд: RegisterCommandProcessor/Pipeline/Handlers
```
Новый обработчик подхватывается сканированием сборки — `Program.cs` править не нужно.
### Что не делаем
- Не тащим внешний медиатор.
- Не кладём бизнес-логику в контроллер.
- Не возвращаем «голые» DTO мимо обёртки — контракт ответа один на все ручки.
""";
public static string S5_PositiveConsequences = """
## Положительные следствия
- Новый обработчик = один файл, регистрируется автоматически.
- Сквозная логика (логирование, трассировка, валидация) добавляется pipeline-шагом, не трогая
обработчики.
- Разделение чтения и записи: запросы — read-only без трекинга, команды — write через единицу
работы с авто-`SaveChanges` при успехе.
- Единая обёртка — предсказуемая обработка успеха/ошибок на фронте и в генерируемом клиенте.
""";
public static string S6_NegativeConsequences = """
## Отрицательные следствия и компромиссы
- Свои процессоры и pipeline — нет готовой экосистемы (в отличие от медиатора).
- Обобщённые pipeline-шаги (open generics) усложняют DI-регистрацию.
- Обёртка добавляет уровень вложенности (`payload`) даже там, где хватило бы «голого» ответа —
принимаем ради единообразия контракта.
""";
public static string S7_Validation = """
## Проверка
- Первый контроллер с обработчиком компилируется и отвечает без ручной регистрации в `Program.cs`.
- Все результаты наследуют `IcdBaseRequestResult`; ответ содержит `payload`/`isSuccess`/`errors`.
- Добавление обработчика не требует правок `Program.cs`.
""";
public static string S8_OpenQuestions = """
## Открытые вопросы / отложено
- **Единица работы (IUnitOfWork)** и **write/read-контексты** — вводятся с БД под первый write-срез.
- **Pipeline-шаги** (логирование, затем валидация) — добавляются, когда появится сквозной концерн.
- **RequestId/трассировка** — поле зарезервировано; заполняется pipeline-шагом при появлении
сквозного логирования/OpenTelemetry.
- **Коды ошибок (ErrorCode)** — состав enum фиксируется по мере появления ошибочных сценариев.
""";
public static string S99_Related = $"""
## Связанные артефакты
- {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_002_GridQueryContract)} — контракт grid-запросов поверх этой обёртки.
- {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_003_EntityAndEfMapping)} — правила сущностей и EF для write-ветки.
""";
}
}