Un concepto independiente del lenguaje que descompone el flujo de negocio dentro de las funciones de servicio en bloques declarativos (sequences).
El problema
El SSOT existente solo cubre lo que está fuera de una función:
| SSOT existente | Cobertura | ¿Dentro de funciones? |
|---|---|---|
| OpenAPI | Rutas de API, parámetros, schemas de respuesta | No |
| SQL DDL | Estructuras de tablas, índices, restricciones | No |
El interior de una función — el flujo de negocio de “consultar -> validar -> crear -> responder” — no tiene dónde declararse. Solo se puede entender leyendo el código de implementación. Los sequences llenan ese vacío.
Estructura jerárquica de SSOT
SSaC es la tercera capa que se construye sobre el SSOT existente.
OpenAPI → Frontera de API (rutas, parámetros, respuestas)
SQL DDL → Frontera de datos (tablas, restricciones, índices)
SSaC → Interior de funciones (flujo de negocio, declaración de sequences)
─────────────────────────────────────────────────
Código impl. → Generado por codegen (manejo de errores, boilerplate)
Las tres capas superiores se encargan de la declaración, y el código de implementación se deriva de ellas. Lo único que las personas escriben son declaraciones.
Conceptos fundamentales
sequence
Una unidad declarativa que tipifica los bloques de ejecución dentro de una función.
Declara solo el what (qué hacer), y el codegen completa el how (cómo hacerlo).
10 tipos fijos
| Tipo | Rol | Ejemplo |
|---|---|---|
| authorize | Verificación de permisos | Consulta de políticas basada en action/resource/ID |
| get | Consulta de recursos | Model.FindByID(id) -> result |
| guard nil | Salir si el resultado es null | Error y salida si no existe |
| guard exists | Salir si el resultado existe | Error y salida si ya existe |
| post | Creación de recursos | Model.Create(fields…) -> result |
| put | Actualización de recursos | Model.Update(id, fields…) |
| delete | Eliminación de recursos | Model.Delete(id) |
| password | Comparación de contraseñas | Salir si falla la comparación de hash |
| call | Invocación externa | Manipulación de recursos no expresable como CRUD; salir en caso de fallo |
| response | Retorno de respuesta | json, view, redirect |
Como los tipos son cerrados, la generación de código mediante coincidencia de plantillas por tipo es posible. Esta es la base del codegen simbólico sin necesidad de LLMs.
Alcance de model
Un model no se limita a las tablas de base de datos. Cualquier recurso que pueda manejarse mediante CRUD califica como model. Consultas a base de datos, verificaciones de existencia de archivos y llamadas a APIs externas se expresan de manera uniforme mediante get/post/put/delete.
Ejemplo de sintaxis (Go PoC)
Aunque SSaC es un concepto independiente del lenguaje, la implementación de referencia usa la sintaxis de comentarios de Go. Como la IA ya comprende el AST de Go, no hay costo adicional de aprendizaje.
Simple (Creación)
// @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) {}
Complejo (Eliminación + Autorización + Validación + Llamada externa)
// @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) {}
Resultado del codegen
La implementación derivada de las declaraciones de sequence anteriores:
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,
})
}
Comparación de costo en tokens
| Declaración de sequence | Código de implementación | |
|---|---|---|
| Líneas | 10~15 líneas | 30~100 líneas |
| Contenido expresado | what (qué hacer) | how (cómo hacerlo) |
| Manejo de errores | Ninguno (generado por codegen) | Todo incluido |
| Dependencia de bibliotecas | Ninguna | import, inicialización, invocación |
| Momento de cambio | Al cambiar el flujo de negocio | Se refleja automáticamente al re-ejecutar codegen |
Las personas solo mantienen las declaraciones de sequence. El código de implementación se sobrescribe re-ejecutando codegen cuando cambian las declaraciones.
Principios de diseño
early return — El contrato universal de todos los tipos
Esta es la premisa que hace viable la disposición lineal de sequences. Todos los sequences excepto response salen inmediatamente en caso de fallo. call no es excepción. Solo hay que leer de arriba abajo, sin ramificaciones.
| Tipo | Condición de fallo |
|---|---|
| authorize | Sin permisos -> return |
| get | Error de consulta -> return |
| guard nil | null -> return |
| guard exists | Existe -> return |
| post/put/delete | Error de BD -> return |
| password | No coincide -> return |
| call | Fallo -> return |
| response | Último (retorna) |
Como call sigue el mismo contrato que guard, no es necesario agregar un guard separado después de un call. El codegen genera automáticamente verificaciones de error y early returns para cada call.
@transaction
Metadato a nivel de función. No es uno de los 10 tipos de sequence, sino que declara si toda la función debe envolverse en una transacción.
// @transaction
// @sequence get
// @model Account.FindByID
// @param AccountID request
// @result account Account
// ...
Combinado con la estructura de early return, los límites de la transacción surgen naturalmente:
@transaction declarado
→ fallo de guard → rollback + return
→ último sequence exitoso → commit
→ ocurre error → rollback + return
Como no hay ramificaciones, el límite de la transacción es singular: “toda la función”. No es necesario especificar posiciones de begin/end; el codegen decide si envolver basándose únicamente en la presencia o ausencia de @transaction. Si se necesitan dos transacciones, significa que hay dos responsabilidades. Divide la función.
call — Una forma especial de model
call comparte la misma esencia que model. Si model representa la manipulación de recursos expresada como CRUD (get/post/put/delete), entonces call representa la manipulación de recursos que no puede expresarse como CRUD. Ambos invocan trabajo con dependencias externas y retornan en caso de fallo.
Manipulación de recursos
+-- model (CRUD) → get/post/put/delete
+-- call (no-CRUD) → component o func
@component— Basado en registro. Se promueve cuando un patrón repetido aparece tres o más veces.@func— Lógica única. Implementada directamente por una persona o IA.
La lógica compleja como ramificaciones con posterior convergencia o procesamiento condicional dentro de bucles no se maneja extendiendo sequences, sino delegando a call. Se delega, pero el contrato permanece igual — salir en caso de fallo.
@message
Metadato que puede adjuntarse opcionalmente a todos los tipos de sequence. Especifica el mensaje a transmitir al usuario en caso de fallo.
// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"
// @sequence post
// @model Session.Create
// @param ProjectID request
// @param Command request
// @result session Session
// @message "세션 생성에 실패했습니다"
Cuando se omite @message, el codegen genera automáticamente un mensaje predeterminado a partir del tipo + nombre del modelo. Solo se declara cuando se necesita un mensaje personalizado.
Las funciones puras no existen en la capa de servicio
Todos los bloques declarados en un sequence implican trabajo con dependencias externas. La funcionalidad pura como conversión de formato o cálculo de valores no pertenece a la capa de servicio, sino al interior del model o de la función invocada. Las funciones de servicio solo orquestan; el cálculo puro pertenece al destinatario de la llamada.
Referencias de tipos
SSaC no define sus propios tipos. Referencia tipos derivados de fuentes SSOT existentes.
| Fuente | Ejemplo |
|---|---|
| Schema de BD (SQL DDL) | Project, Session |
| Especificación de API (OpenAPI) | CreateSessionRequest |
| Definiciones de interfaz | FileSystem, Cache |
Convención de nomenclatura
| Categoría | Regla | Ejemplo |
|---|---|---|
| Tipo | PascalCase, obtenido de SSOT | Project, Session |
| Variable | camelCase, declarada con @result | project, sessionCount |
| Campo de request | PascalCase, extraído de la solicitud | ProjectID, Command |
| Variable reservada | camelCase, proporcionada por el framework | currentUser, config |
Las mayúsculas y minúsculas difieren entre tipos y variables, por lo que se pueden distinguir solo viendo la declaración. project es una variable, Project es un tipo.
Extensión de lenguajes
SSaC es un concepto independiente del lenguaje. Go es la implementación de referencia (PoC), y las mismas declaraciones de sequence pueden generar código en otros lenguajes.
Declaraciones de sequence (común)
|
+-- Go codegen
+-- Python codegen
+-- TypeScript codegen
+-- ...
La especificación (definiciones de tipos, sintaxis, reglas de validación) y el codegen están separados.
La implementación de referencia en Go está disponible en GitHub.