Skip to Content
Mélodium 0.10.1 is now available!
DocsExemplesAPI SQL Utilisateurs

API SQL Utilisateurs

Source: 03_sql_user_api

Une API REST minimale reposant sur SQLite (ou PostgreSQL). Le pool de connexions à la base de données et le serveur HTTP sont tous deux déclarés comme modèles, initialisés une seule fois et partagés entre toutes les tracks de requêtes concurrentes.

Exécution

melodium run 03_sql_user_api/Compo.toml # ou avec PostgreSQL : melodium run 03_sql_user_api/Compo.toml --db_url postgres://user:pass@localhost/mydb
$ curl http://127.0.0.1:8080/users OK $ curl -X POST http://127.0.0.1:8080/users -d '{"name":"Alice","email":"alice@example.com"}' {"name":"Alice","email":"alice@example.com"}

Fonctionnement

Deux modèles sont déclarés en tête de main :

model db: AppDb(db_url=db_url) model server: HttpServer(host=|from_ipv4(|localhost_ipv4()), port=port)

AppDb encapsule SqlPool avec un nombre de connexions min/max configurable. HttpServer accepte toutes les requêtes entrantes et les achemine vers le sous-traitement ayant enregistré la route correspondante.

Séquence de démarrage

Les connexions dans main imposent un ordre de démarrage strict : connect ouvre le pool, connected se déclenche dès que le pool est prêt, déclenchant createTable. Ce n’est qu’après createTable.done que le serveur HTTP démarre, garantissant que le schéma existe avant qu’une requête n’arrive :

treatment main( const db_url: string = "sqlite://users.db", const port: u16 = 8080 ) model db: AppDb(db_url=db_url) model server: HttpServer(host=|from_ipv4(|localhost_ipv4()), port=port) { startup() connect[sql_pool=db]() startup.trigger -> connect.trigger connected[sql_pool=db]() createTable[db=db]() connected.trigger -> createTable.trigger start[http_server=server]() logReady: logInfoMessage(label="server", message="SQL user API ready") createTable.done -> start.trigger createTable.done -> logReady.trigger listUsers[db=db, http_server=server]() echoCreate[http_server=server]() }

createTable exécute un CREATE TABLE IF NOT EXISTS via executeRaw, puis convertit le nombre de lignes affectées en signal de complétion Block<void> :

treatment createTable[db: SqlPool]() input trigger: Block<void> output done: Block<void> { executeRaw[sql_pool=db](sql="CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL)") checkAffected: check<u64>() emitDone: emit<void>(value=_) Self.trigger -> executeRaw.trigger,affected -> checkAffected.value,check -> emitDone.trigger,emit -> Self.done }

main treatment diagram Voir dans Compositeur Studio

Gestionnaires de routes

listUsers et echoCreate appellent chacun connection[http_server=server](method=…, route=…) pour enregistrer leur route. Ils s’exécutent de manière concurrente et indépendante ; Mélodium crée automatiquement une nouvelle track pour chaque connexion entrante.

Le pattern bodyTrigger: trigger<byte>() convertit le flux Stream<byte> du corps entrant en un événement Block<void> de démarrage. Le statut, les en-têtes et le traitement du corps sont tous pilotés depuis ce signal unique en parallèle :

treatment listUsers[db: SqlPool, http_server: HttpServer]() { connection[http_server=http_server](method=|get(), route="/users") status: emit<HttpStatus>(value=|ok()) headers: emit<StringMap>(value=|smap([])) bodyTrigger: trigger<byte>() connection.data -> bodyTrigger.stream,start --> status.trigger,emit -> connection.status bodyTrigger.start --------> headers.trigger,emit -> connection.headers queryUsers[db=db]() bodyTrigger.start -> queryUsers.trigger logRows: logDataInfos<Map>(label="users") queryUsers.rows -> logRows.data okMsg: emit<string>(value="OK\n") streamMsg: stream<string>() encode() bodyTrigger.start -> okMsg.trigger,emit -> streamMsg.block,stream -> encode.text,data -> connection.data }

La --> (double flèche) est le fan-out : une sortie alimente deux entrées simultanément. queryUsers encapsule l’appel sqlFetch, diffusant les lignes du résultat en Stream<Map> :

treatment queryUsers[db: SqlPool]() input trigger: Block<void> output rows: Stream<Map> { emitBind: emit<Map>(value=|map([])) sqlFetch[sql_pool=db](sql="SELECT id, name, email FROM users ORDER BY id", bindings=[]) Self.trigger -> emitBind.trigger,emit -> sqlFetch.bind,data -> Self.rows }

echoCreate décode le corps de la requête en JSON et le renvoie tel quel en texte brut :

treatment echoCreate[http_server: HttpServer]() { connection[http_server=http_server](method=|post(), route="/users") status: emit<HttpStatus>(value=|ok()) headers: emit<StringMap>(value=|smap([])) bodyTrigger: trigger<byte>() connection.data -> bodyTrigger.stream,start --> status.trigger,emit -> connection.status bodyTrigger.start --------> headers.trigger,emit -> connection.headers decode() toJson() unwrapBody: unwrapOr<Json>(default=|null()) toString<Json>() encode() connection.data -> decode.data,text -> toJson.text,json -> unwrapBody.option,value -> toString.value,into -> encode.text,data -> connection.data }

Dépendances

[dependencies] std = "0.10.1" # flux de base, journalisation, structures de données http = "0.10.1" # serveur et client HTTP net = "0.10.1" # utilitaires d'adresses IP json = "0.10.1" # parsing et sérialisation JSON sql = "0.10.1" # pool de connexions SQL et requêtes encoding = "0.10.1" # encodage / décodage UTF-8