Un concept independant du langage qui decompose le flux metier a l’interieur des fonctions de service en blocs declaratifs (sequences).

Le probleme

Les SSOT existants ne couvrent que l’exterieur des fonctions :

SSOT existantPortee couverteInterieur de la fonction ?
OpenAPIRoutes API, parametres, schema de reponseX
SQL DDLStructure des tables, index, contraintesX

L’interieur de la fonction – le flux metier “lecture -> validation -> creation -> reponse” – n’a aucun endroit ou etre declare. Il faut lire le code d’implementation pour le comprendre. C’est cet espace vide que la sequence comble.

Hierarchie SSOT

SSaC est la troisieme couche qui s’empile au-dessus des SSOT existants.

OpenAPI       → Frontiere API (routes, parametres, reponses)
SQL DDL       → Frontiere donnees (tables, contraintes, index)
SSaC          → Interieur des fonctions (flux metier, declaration de sequence)
─────────────────────────────────────────────────
Code implemente → Genere par le codegen (gestion d'erreurs, boilerplate)

Les trois couches ci-dessus prennent en charge les declarations, et le code d’implementation en derive. L’humain n’ecrit que les declarations.

Concepts fondamentaux

sequence

Une unite de declaration qui type les blocs d’execution a l’interieur d’une fonction.

Declarez uniquement le what (quoi faire), le codegen se charge du how (comment le faire).

10 types fixes

TypeRoleExemple
authorizeVerification des droitsRequete de politique basee sur action/ressource/ID
getLecture de ressourceModel.FindByID(id) -> result
guard nilTermine si le resultat est nullErreur et sortie si absent
guard existsTermine si le resultat existeErreur et sortie si deja existant
postCreation de ressourceModel.Create(fields…) -> result
putModification de ressourceModel.Update(id, fields…)
deleteSuppression de ressourceModel.Delete(id)
passwordComparaison de mot de passeComparaison de hash, sortie en cas d’echec
callAppel externeOperations sur les ressources non exprimables en CRUD, sortie en cas d’echec
responseRetour de la reponsejson, view, redirect

Les types etant fermes, la generation de code par correspondance de templates par type est possible. C’est la raison pour laquelle le codegen symbolique fonctionne sans LLM.

Portee du model

Le model ne se limite pas aux tables de base de donnees. Toute ressource manipulable en CRUD est un model. Qu’il s’agisse d’une requete en base, d’une verification d’existence de fichier ou d’un appel API externe, tout s’exprime uniformement avec get/post/put/delete.

Exemple de syntaxe (Go PoC)

Bien que le concept soit independant du langage, l’implementation de reference utilise la syntaxe des commentaires Go. L’IA connaissant deja le Go AST, il n’y a aucun cout d’apprentissage supplementaire.

Simple (creation)

// @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) {}

Complexe (suppression + autorisation + validation + appel externe)

// @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) {}

Resultat du codegen

L’implementation derivee des declarations de sequence ci-dessus :

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,
    })
}

Comparaison du cout en tokens

Declaration sequenceCode d’implementation
Nombre de lignes10-15 lignes30-100 lignes
Contenu exprimewhat (quoi faire)how (comment le faire)
Gestion d’erreursAucune (generee par le codegen)Entierement incluse
Dependances de bibliothequesAucuneimport, initialisation, appels
Moment de modificationLors d’un changement de flux metierReflet automatique par re-execution du codegen

L’humain ne maintient que les declarations de sequence. Lorsque la declaration change, on relance le codegen pour ecraser le code d’implementation.

Principes de conception

early return – contrat commun a tous les types

C’est la condition prealable qui permet aux sequences de fonctionner en enumeration lineaire. A l’exception de response, toutes les sequences sortent immediatement en cas d’echec. call ne fait pas exception. Il suffit de lire de haut en bas, sans aucune ramification.

TypeCondition d’echec
authorizePas de droits -> return
getErreur de requete -> return
guard nilnull -> return
guard existsExiste -> return
post/put/deleteErreur DB -> return
passwordNon-correspondance -> return
callEchec -> return
responseDernier (retour)

Puisque call suit le meme contrat que guard, il n’est pas necessaire d’ajouter un guard separe apres un call. Le codegen genere automatiquement la verification d’erreur + early return pour chaque call.

@transaction

Metadonnee au niveau de la fonction. Ce n’est pas un type de sequence (parmi les 10), mais une declaration indiquant si la fonction entiere doit etre enveloppee dans une transaction.

// @transaction
// @sequence get
// @model Account.FindByID
// @param AccountID request
// @result account Account
// ...

Combine avec la structure early return, les limites de la transaction sont naturelles :

@transaction declare
  → echec du guard → rollback + return
  → derniere sequence reussie → commit
  → erreur survenue → rollback + return

Puisqu’il n’y a pas de ramification, la limite de transaction est unique : “la fonction entiere”. Il n’est pas necessaire de specifier les positions begin/end, et le codegen decide de l’enveloppement uniquement par la presence ou l’absence de @transaction. Si deux transactions sont necessaires, cela signifie qu’il y a deux responsabilites. Separez la fonction.

call – forme speciale du model

call possede la meme essence que model. Si model represente les operations de ressources exprimees en CRUD (get/post/put/delete), call represente les operations de ressources non exprimables en CRUD. Les deux appellent un travail ayant une dependance externe et retournent en cas d’echec.

Operations sur les ressources
  +-- model (CRUD)      → get/post/put/delete
  +-- call (non-CRUD)   → component ou func
  • @component – Sur inscription. Promu lorsqu’un pattern repetitif apparait 3 fois ou plus.
  • @func – Logique unique. Implementee directement par un humain ou une IA.

La logique complexe telle que la ramification avec convergence ou le traitement conditionnel dans une boucle n’etend pas la sequence mais est deleguee a call. Deleguee, mais le contrat reste le meme – sortie en cas d’echec.

@message

Metadonnee optionnelle pouvant etre attachee a tout type de sequence. Elle specifie le message a transmettre a l’utilisateur en cas d’echec.

// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"

// @sequence post
// @model Session.Create
// @param ProjectID request
// @param Command   request
// @result session Session
// @message "세션 생성에 실패했습니다"

Si @message est omis, le codegen genere automatiquement un message par defaut a partir du type + nom du modele. Il suffit de le declarer uniquement lorsqu’un message personnalise est necessaire.

Les fonctions pures n’existent pas dans la couche service

Tous les blocs declares dans une sequence sont des operations ayant une dependance externe. Les fonctionnalites pures comme la conversion de format ou le calcul de valeurs sont traitees a l’interieur du model ou a l’interieur de la fonction appelee, pas dans la couche service. La fonction de service ne fait que l’orchestration, et le calcul pur appartient a la cible de l’appel.

Reference de types

SSaC ne definit pas ses propres types. Il reference les types produits par les SSOT existants.

SourceExemple
Schema DB (SQL DDL)Project, Session
Specification API (OpenAPI)CreateSessionRequest
Definition d’interfaceFileSystem, Cache

Conventions de nommage

CategorieRegleExemple
TypePascalCase, issu du SSOTProject, Session
VariablecamelCase, declaree via @resultproject, sessionCount
Champ requestPascalCase, extrait de la requeteProjectID, Command
Variable reserveecamelCase, fournie par le frameworkcurrentUser, config

La casse differente entre types et variables permet de les distinguer par la seule declaration. project est une variable, Project est un type.

Extension linguistique

SSaC est un concept independant du langage. Go est l’implementation de reference (PoC), et il est possible de generer du code dans d’autres langages a partir de la meme declaration de sequence.

Declaration de sequence (commune)
  |
  +-- Codegen Go
  +-- Codegen Python
  +-- Codegen TypeScript
  +-- ...

La specification (definition des types, syntaxe, regles de validation) et le codegen sont separes.

L’implémentation de référence en Go est disponible sur GitHub.