На этой странице11
24 августа 2026 г. · 5 мин чтения
Компилятор, а не генератор: почему это не игра слов
DomainCraft — это не генератор шаблонного кода, а компилятор доменной модели: настоящий конвейер из лексера, парсера, валидатора, IR и рендерера. Рассказываем, чем это отличается от «печати файлов» и что даёт на практике.
«Сгенерировать код» может любой шаблонизатор. «Скомпилировать контракт» — только компилятор. Рассказываем, чем эти слова отличаются в DomainCraft — и почему это различие определяет всё.
Когда мы говорим, что DomainCraft — компилятор, первый вопрос закономерный: «А в чём разница? Это же просто генератор кода». Снаружи так и выглядит: файл YAML на входе, папка с кодом на выходе. Разница — в том, что происходит внутри и какие гарантии вы получаете.
Как всё задумывалось: генератор для бэкенда
Изначально DomainCraft был генератором и по замыслу, и по устройству: одна цель — бэкенд на C#/ASP.NET Core, один мост, 71 шаблон. Модель описана на YAML, пайплайн прогоняет шаблоны, печатает файлы. Всё работало — пока мост был один.
Как только мы захотели добавить второй язык (TypeScript на фронтенд), проявился главный вопрос: кто гарантирует, что два независимо сгенерированных пакета кода на разных языках говорят об одном и том же?
Шаблонизатор не видит модель как структуру: для него это текстовые файлы и подстановки. Один и тот же enum может стать OrderStatus в C# и строкой "pending" в JSON-клиенте — и никто не заметит, пока в рантайме не всплывёт рассинхронизация.
Прозрение: IR был, но мы видели только языки
IR появился в DomainCraft не как инновация, а как необходимость: невозможно рендерить шаблоны корректно, если нет структуры, из которой их рендерить. Долго мы воспринимали его узко: «есть IR — значит, легко добавить ещё один бэкенд-язык». В голове было только одно измерение — сам язык.
Главное прозрение пришло, когда мы поняли: вариативность не одномерна. Транспорт (REST, gRPC, минимальный API) и персистенция (EF Core, Dapper, Marten) — две независимые оси. Тот же IR можно рендерить не только в другой язык, но и в другой слой поверх того же языка — и комбинировать слои произвольным образом. Из этого выросло описанное в прошлом посте деление на оси и слои, где N вариантов персистенции × M вариантов транспорта = N+M репозиториев, а не N×M: От монолита к осям и слоям.
Компилятор: как устроен конвейер
В DomainCraft генерация — это не прогон шаблонов, а компиляция. Пайплайн буквально повторяет классический конвейер компилятора:
domain.yaml
→ Лексер — разбирает каждое поле на токены
→ Парсер — превращает YAML в структурированную модель
→ Валидатор — проверяет семантику (связи, права, индексы, данные)
→ IR-билдер — собирает связный граф объектов
→ Рендерер — выводит код через мосты
Ключевой узел — внутреннее представление (IR). Это не промежуточный формат файлов, а разрешённая семантика: связный граф сущностей, полей, связей, прав, индексов. В нём уже нет YAML — только модель.
Мост — по сути backend компилятора для своего языка: он умеет превратить IR в файлы своего мира (C#, TypeScript, React-хуки, админка). Само IR ни от какого языка не зависит.
┌→ мост C# → ASP.NET Core API
domain.yaml → IR ─→ мост TS → типизированный клиент + хуки
└→ мост Admin → админ-панель
Что компилятор гарантирует, а шаблонизатор — нет
Ошибки ловятся до рантайма
Компилятор не соберёт исполняемый файл с неразрешёнными символами — он просто не соберётся. DomainCraft не выдаст код, в котором права ссылаются на несуществующую сущность, индекс — на отсутствующее поле, а on_delete стоит на обязательной связи. Валидатор проверяет сотни таких правил ещё до генерации: связность отношений, коллизии в правах, типы seed-данных, дубли первичных ключей, противоречивые модификаторы.
В обычном генераторе эти ошибки всплыли бы спустя время, в рантайме приложения. Здесь — в момент «компиляции», до первого деплоя.
Один контракт — во всех слоях
У модели один источник правды — IR. Из него компилируется всё:
- типы сущностей и DTO;
- wire-значения enum (
credit_card— одинаково в C#, TypeScript и JSON); - грамматика фильтров
path:op:value— и для API, и для типизированного клиента; - матрица прав — в endpoint-ы, в кнопки и в клиентские проверки;
- контракт ошибок (
409 CONCURRENCY_CONFLICT, field-errors); - миграции базы и переименования (
old_name).
C#-сервер и TypeScript-клиент не могут разойтись по проводам: оба скомпилированы из одного IR, а не написаны двумя командами по разным документациям.
Контракт проверяет независимый оракул
Скомпилированному коду мало «собралось нормально» — нужно ещё доказать, что он соответствует контракту. У DomainCraft для этого две системы проверки, и обе независимы от мостов:
- k6 — HTTP-оракул, один на все языки. Запускает сгенерированное приложение и бьёт по нему реальными запросами: статусы, права, изоляция
@owner, оптимистичные блокировки, пагинация, грамматика фильтров. Это 136 проверок, которым безразлично, на каком языке написан сервер, — только контракт. - Per-language TCK — рукописный оракул для каждого тонкого ядра. Существует то, что через HTTP не проверить: compile-time типы и wire-сериализация (
hidden/readonly-исключения, wire-значения enum, типованные фильтры). Тесты написаны людьми против контракта, а не порождены тем же IR, что и мосты, — иначе ошибка моста воспроизвелась бы в тесте и он «прошёл» бы.
Генератор «протестирован». Компилятор — сертифицирован против независимого оракула. Разница в уровне гарантий.
Один и тот же выход — всегда
Компилятор детерминирован: один исходник, одни и те же биты на выходе. В DomainCraft каждая итерация по словарям идёт через отсортированный список — два прогона дают посимвольно одинаковые файлы. Это база для аудита, для диффов в код-ревью и для уверенности, что «чуть-чуть перегенерил — и всё съехало» не случится.
Тот же компилятор работает в редакторе
Ярче всего это показывает GUI-редактор. Тот же лексер, парсер и валидатор скомпилированы в WASM и крутятся прямо в браузере: структура модели в редакторе проверяется тем же кодом, что и в CLI. Один компилятор, одно поведение — в терминале и в редакторе. Шаблонизатору такое не под силу.
Зачем это в будущем
Пока языков два — C# и TypeScript — выигрыш выглядит абстрактным. Будущее многократно его усиливает:
- Новый мост — новый backend. Dapper вместо EF Core, gRPC вместо REST — это маленький слой со своими шаблонами, а комбинация собирается флагом
--replace. Новая комбинация не требует нового «генератора». - Новые платформы — на том же контракте. Dart-мост (в планах) получит из коробки то, что уже есть у C# и TypeScript: wire-значения enum, грамматику фильтров, права — бесплатно, потому что это семантика, а не копипаста.
- ИИ-агенты строят против контракта. Агент получает типизированный клиент, в котором права, типы и query-схема уже собраны из того же IR — и пишет экраны, а не догадывается об API.
Итог
«Компилятор» — это не статус в README, а ответ на вопрос, чему вы доверяете модель. Генератор печатает файлы и уходит. Компилятор принимает модель, проверяет её, выводит один контракт и раздаёт по всем слоям продукта. А независимые оракулы (k6 и per-language TCK) подтверждают: контракт реально выполняется, а не декларирован.
Один файл, один компилятор, один контракт — и оракулы, которые за него отвечают.
domaincraft generate --domain domain.yaml --bridge csharp-rest --admin