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 existant | Portee couverte | Interieur de la fonction ? |
|---|---|---|
| OpenAPI | Routes API, parametres, schema de reponse | X |
| SQL DDL | Structure des tables, index, contraintes | X |
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
| Type | Role | Exemple |
|---|---|---|
| authorize | Verification des droits | Requete de politique basee sur action/ressource/ID |
| get | Lecture de ressource | Model.FindByID(id) -> result |
| guard nil | Termine si le resultat est null | Erreur et sortie si absent |
| guard exists | Termine si le resultat existe | Erreur et sortie si deja existant |
| post | Creation de ressource | Model.Create(fields…) -> result |
| put | Modification de ressource | Model.Update(id, fields…) |
| delete | Suppression de ressource | Model.Delete(id) |
| password | Comparaison de mot de passe | Comparaison de hash, sortie en cas d’echec |
| call | Appel externe | Operations sur les ressources non exprimables en CRUD, sortie en cas d’echec |
| response | Retour de la reponse | json, 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 sequence | Code d’implementation | |
|---|---|---|
| Nombre de lignes | 10-15 lignes | 30-100 lignes |
| Contenu exprime | what (quoi faire) | how (comment le faire) |
| Gestion d’erreurs | Aucune (generee par le codegen) | Entierement incluse |
| Dependances de bibliotheques | Aucune | import, initialisation, appels |
| Moment de modification | Lors d’un changement de flux metier | Reflet 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.
| Type | Condition d’echec |
|---|---|
| authorize | Pas de droits -> return |
| get | Erreur de requete -> return |
| guard nil | null -> return |
| guard exists | Existe -> return |
| post/put/delete | Erreur DB -> return |
| password | Non-correspondance -> return |
| call | Echec -> return |
| response | Dernier (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.
| Source | Exemple |
|---|---|
| Schema DB (SQL DDL) | Project, Session |
| Specification API (OpenAPI) | CreateSessionRequest |
| Definition d’interface | FileSystem, Cache |
Conventions de nommage
| Categorie | Regle | Exemple |
|---|---|---|
| Type | PascalCase, issu du SSOT | Project, Session |
| Variable | camelCase, declaree via @result | project, sessionCount |
| Champ request | PascalCase, extrait de la requete | ProjectID, Command |
| Variable reservee | camelCase, fournie par le framework | currentUser, 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.