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 existenteCobertura¿Dentro de funciones?
OpenAPIRutas de API, parámetros, schemas de respuestaNo
SQL DDLEstructuras de tablas, índices, restriccionesNo

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

TipoRolEjemplo
authorizeVerificación de permisosConsulta de políticas basada en action/resource/ID
getConsulta de recursosModel.FindByID(id) -> result
guard nilSalir si el resultado es nullError y salida si no existe
guard existsSalir si el resultado existeError y salida si ya existe
postCreación de recursosModel.Create(fields…) -> result
putActualización de recursosModel.Update(id, fields…)
deleteEliminación de recursosModel.Delete(id)
passwordComparación de contraseñasSalir si falla la comparación de hash
callInvocación externaManipulación de recursos no expresable como CRUD; salir en caso de fallo
responseRetorno de respuestajson, 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 sequenceCódigo de implementación
Líneas10~15 líneas30~100 líneas
Contenido expresadowhat (qué hacer)how (cómo hacerlo)
Manejo de erroresNinguno (generado por codegen)Todo incluido
Dependencia de bibliotecasNingunaimport, inicialización, invocación
Momento de cambioAl cambiar el flujo de negocioSe 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.

TipoCondición de fallo
authorizeSin permisos -> return
getError de consulta -> return
guard nilnull -> return
guard existsExiste -> return
post/put/deleteError de BD -> return
passwordNo coincide -> return
callFallo -> 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.

FuenteEjemplo
Schema de BD (SQL DDL)Project, Session
Especificación de API (OpenAPI)CreateSessionRequest
Definiciones de interfazFileSystem, Cache

Convención de nomenclatura

CategoríaReglaEjemplo
TipoPascalCase, obtenido de SSOTProject, Session
VariablecamelCase, declarada con @resultproject, sessionCount
Campo de requestPascalCase, extraído de la solicitudProjectID, Command
Variable reservadacamelCase, proporcionada por el frameworkcurrentUser, 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.