Языконезависимая концепция, разбивающая бизнес-логику внутри сервисных функций на декларативные блоки (sequence).
Проблема
Существующие SSOT покрывают только то, что находится за пределами функций:
| Существующий SSOT | Область покрытия | Внутри функции? |
|---|---|---|
| OpenAPI | Маршруты API, параметры, схемы ответов | X |
| SQL DDL | Структура таблиц, индексы, ограничения | X |
Внутренняя часть функции — бизнес-поток «запрос -> валидация -> создание -> ответ» — не имеет места для декларативного описания. Его можно понять, только читая код реализации. Именно этот пробел заполняет sequence.
Иерархия SSOT
SSaC — это третий слой, надстроенный над существующими SSOT.
OpenAPI → API 경계 (경로, 파라미터, 응답)
SQL DDL → 데이터 경계 (테이블, 제약, 인덱스)
SSaC → 함수 내부 (비즈니스 흐름, sequence 선언)
─────────────────────────────────────────────────
구현 코드 → 코드젠이 생성 (에러 핸들링, 보일러플레이트)
Три верхних слоя отвечают за декларации, а код реализации выводится из этих деклараций. Человек пишет только декларации.
Основные концепции
sequence
Декларативная единица, типизирующая блоки выполнения внутри функции.
Объявляется только what (что делать), а how (как делать) заполняет codegen.
10 фиксированных типов
| Тип | Роль | Пример |
|---|---|---|
| authorize | Проверка полномочий | Запрос к политике на основе действия/ресурса/ID |
| get | Получение ресурса | Model.FindByID(id) -> result |
| guard nil | Завершение, если результат null | Ошибка и выход, если не найдено |
| guard exists | Завершение, если результат существует | Ошибка и выход, если уже существует |
| post | Создание ресурса | Model.Create(fields…) -> result |
| put | Обновление ресурса | Model.Update(id, fields…) |
| delete | Удаление ресурса | Model.Delete(id) |
| password | Сравнение пароля | Сравнение hash, выход при несовпадении |
| call | Внешний вызов | Операции с ресурсами, не выражаемые через CRUD, выход при ошибке |
| response | Возврат ответа | json, view, redirect |
Набор типов закрыт, поэтому кодогенерация возможна через сопоставление шаблонов по типам. Это основание для символьной кодогенерации без LLM.
Область применения model
model не ограничивается таблицами БД. Любой ресурс, управляемый через CRUD, является model. Запрос к БД, проверка существования файла, вызов внешнего API — всё одинаково выражается через get/post/put/delete.
Пример синтаксиса (Go PoC)
Концепция языконезависима, но в качестве эталонной реализации используется синтаксис комментариев Go. Поскольку AI уже знает Go AST, дополнительных затрат на обучение нет.
Простой (создание)
// @sequence get
// @model Project.FindByID
// @param ProjectID request
// @result project Project
// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"
// @sequence post
// @model Session.Create
// @param ProjectID request
// @param Command request
// @result session Session
// @sequence response json
// @var session
func CreateSession(w http.ResponseWriter, r *http.Request) {}
Составной (удаление + авторизация + валидация + внешний вызов)
// @sequence authorize
// @action delete
// @resource project
// @id ProjectID
// @sequence get
// @model Project.FindByID
// @param ProjectID request
// @result project Project
// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"
// @sequence get
// @model Session.CountByProjectID
// @param ProjectID request
// @result sessionCount int
// @sequence guard exists sessionCount
// @message "하위 세션이 존재하여 삭제할 수 없습니다"
// @sequence call
// @component notification
// @param project.OwnerEmail
// @param "프로젝트가 삭제됩니다"
// @sequence call
// @func cleanupProjectResources
// @param project
// @result cleaned bool
// @sequence delete
// @model Project.Delete
// @param ProjectID request
// @sequence response json
func DeleteProject(w http.ResponseWriter, r *http.Request) {}
Результат кодогенерации
Реализация, производная от приведённых выше деклараций sequence:
func CreateSession(w http.ResponseWriter, r *http.Request) {
// get
project, err := projectModel.FindByID(projectID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// guard nil
if project == nil {
http.Error(w, "프로젝트가 존재하지 않습니다", http.StatusNotFound)
return
}
// post
session, err := sessionModel.Create(projectID, command)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// response json
json.NewEncoder(w).Encode(map[string]interface{}{
"session": session,
})
}
Сравнение стоимости токенов
| Декларация sequence | Код реализации | |
|---|---|---|
| Количество строк | 10~15 строк | 30~100 строк |
| Содержание | what (что делать) | how (как делать) |
| Обработка ошибок | Отсутствует (генерируется codegen) | Полностью включена |
| Зависимости от библиотек | Отсутствуют | import, инициализация, вызовы |
| Момент изменения | При изменении бизнес-потока | Автоматически отражается при повторном запуске codegen |
Человек сопровождает только декларацию sequence. При изменении декларации достаточно перезапустить codegen, чтобы перезаписать код реализации.
Принципы проектирования
early return — общий контракт всех типов
Предпосылка, благодаря которой sequence работает как линейный список. За исключением response, каждый sequence немедленно завершает выполнение при ошибке. call — не исключение. Достаточно читать сверху вниз без ветвлений.
| Тип | Условие ошибки |
|---|---|
| authorize | Нет полномочий -> return |
| get | Ошибка запроса -> return |
| guard nil | null -> return |
| guard exists | Существует -> return |
| post/put/delete | Ошибка БД -> return |
| password | Несовпадение -> return |
| call | Ошибка -> return |
| response | Последний (возврат) |
Поскольку call следует тому же контракту, что и guard, после call не нужно добавлять отдельный guard. Codegen автоматически генерирует проверку ошибок + early return для каждого call.
@transaction
Метаданные уровня функции. Объявляет не тип sequence (их 10), а необходимость транзакционной обёртки для всей функции.
// @transaction
// @sequence get
// @model Account.FindByID
// @param AccountID request
// @result account Account
// ...
В сочетании со структурой early return границы транзакции определяются естественным образом:
@transaction объявлен
-> guard ошибка -> rollback + return
-> последний sequence успешен -> commit
-> ошибка -> rollback + return
Поскольку ветвлений нет, граница транзакции едина — «вся функция». Нет необходимости указывать позицию begin/end; codegen определяет обёртку исключительно по наличию или отсутствию @transaction. Если нужны две транзакции — значит, у функции две ответственности. Разделите функцию.
call — особая форма model
call имеет ту же сущность, что и model. Если model — это операции над ресурсами, выражаемые через CRUD (get/post/put/delete), то call — операции, не выражаемые через CRUD. Оба вызывают работу с внешними зависимостями и возвращают управление при ошибке.
Операции с ресурсами
+-- model (CRUD) -> get/post/put/delete
+-- call (не-CRUD) -> component или func
@component— регистрационная модель. Повторяющийся паттерн, появившийся 3 и более раз, повышается до component.@func— уникальная логика. Реализуется человеком или AI.
Сложная логика — ветвление с последующим слиянием, условная обработка в циклах и т.п. — не расширяет sequence, а делегируется через call. Делегирование с сохранением контракта — выход при ошибке.
@message
Опциональные метаданные, которые можно прикрепить к любому типу sequence. Задаёт сообщение, передаваемое пользователю при ошибке.
// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"
// @sequence post
// @model Session.Create
// @param ProjectID request
// @param Command request
// @result session Session
// @message "세션 생성에 실패했습니다"
Если @message не указан, codegen автоматически генерирует сообщение по умолчанию из типа + имени model. Объявляйте только тогда, когда нужно пользовательское сообщение.
Чистые функции не существуют в сервисном слое
Все блоки, объявляемые в sequence, — это операции с внешними зависимостями. Чистые функции (преобразование формата, вычисление значений и т.п.) обрабатываются не в сервисном слое, а внутри model или вызываемых функций. Сервисная функция занимается только оркестрацией (orchestration), а чистые вычисления принадлежат вызываемым объектам.
Ссылки на типы
SSaC не определяет собственные типы. Он ссылается на типы, полученные из существующих SSOT.
| Источник | Пример |
|---|---|
| Схема БД (SQL DDL) | Project, Session |
| Спецификация API (OpenAPI) | CreateSessionRequest |
| Определение интерфейсов | FileSystem, Cache |
Соглашения об именовании
| Категория | Правило | Пример |
|---|---|---|
| Тип | PascalCase, берётся из SSOT | Project, Session |
| Переменная | camelCase, объявляется через @result | project, sessionCount |
| Поле request | PascalCase, извлекается из запроса | ProjectID, Command |
| Зарезервированная переменная | camelCase, предоставляется фреймворком | currentUser, config |
Разница регистра между типами и переменными позволяет различать их только по декларации. project — переменная, Project — тип.
Языковое расширение
SSaC — языконезависимая концепция. Go является эталонной реализацией (PoC), и из одних и тех же деклараций sequence можно генерировать код на других языках.
sequence 선언 (공통)
|
+-- Go 코드젠
+-- Python 코드젠
+-- TypeScript 코드젠
+-- ...
Спецификация (определения типов, синтаксис, правила валидации) и codegen разделены.
Эталонная реализация на Go доступна на GitHub.