Construire un moteur de base de données est une chose. Le faire fonctionner quand les utilisateurs interagissent avec à travers un navigateur web en est une tout autre. Le navigateur est un environnement hostile pour les opérations de base de données. Il n'y a pas de système de fichiers (directement). Il n'y a pas de stockage persistant qui survit à un rafraîchissement de page (sans effort). Il y a une frontière fondamentale entre le JavaScript côté client et la logique côté serveur qui doit être franchie avec soin.
Les sessions 201 et 203 sont celles où FlinDB a rencontré le navigateur.
L'architecture : SSR avec actions serveur
Le modèle de rendu web de FLIN est le rendu côté serveur (SSR) avec rechargement de module à chaud (HMR). Ce n'est pas un framework d'application mono-page. Ce n'est pas du WASM exécuté dans le navigateur. Le runtime FLIN s'exécute sur le serveur, rend le HTML et l'envoie au navigateur.
Le navigateur reçoit du HTML entièrement rendu plus un petit runtime JavaScript ($flin proxy) qui gère les mises à jour d'état côté client.
Session 201 : rendre le navigateur interactif
Fix 1 : liaison de données bidirectionnelle
L'attribut bind={} n'était pas traité par le rendu. La correction génère les attributs HTML appropriés :
flin<input bind={newTodo} />Rend maintenant :
html<input value="" oninput="newTodo = this.value; _flinUpdate()" data-flin-bind="newTodo" />Fix 2 : soumission de formulaire via actions serveur
Au lieu d'appeler une fonction JS, le formulaire envoie une requête POST à un endpoint /_action :
javascriptfunction _flinSubmit(actionName) {
const stateData = {};
for (const key in _state) {
stateData[key] = $flin[key];
}
fetch('/_action', { method: 'POST', body: formData })
.then(response => { if (response.ok) window.location.reload(); });
}Session 203 : le bug de persistance
La session 201 a corrigé l'interactivité. Mais quand la page se rafraîchissait, les todos disparaissaient. Le fichier WAL contenait 0 octet.
Cause racine 1 : le bytecode écrase l'état injecté
Le serveur dev injecte les variables d'état avant l'exécution du bytecode. Mais le code source FLIN déclare newTodo = "" au niveau supérieur, écrasant la valeur injectée.
La correction : les globales protégées.
rustpub fn set_global_protected(&mut self, name: String, value: Value) {
self.globals.insert(name.clone(), value);
self.protected_globals.insert(name);
}Les globales protégées ne peuvent pas être écrasées par le bytecode.
Cause racine 2 : Value::Text non géré par Trim
L'injection d'état crée Value::Text("Buy milk"). Mais OpCode::Trim ne gérait que Value::Object(id). Pour Value::Text, il retournait une chaîne vide.
Cause racine 3 : échecs silencieux du validateur
Le validateur échouait silencieusement -- pas d'erreur remontée, mais l'opération de sauvegarde était abandonnée discrètement.
Vérification
Avant la correction :
``bash
$ cat embedded/todo-app/.flindb/wal.log
# (empty - 0 bytes)
``
Après la correction :
``bash
$ cat embedded/todo-app/.flindb/wal.log
{"type":"Save","timestamp":1768567212273,"entity_type":"Todo","entity_id":1,...}
``
177 octets. Des données qui persistent.
La leçon
Les sessions 201 et 203 nous ont appris quelque chose de fondamental sur l'ingénierie de base de données : une base de données qui passe tous ses tests peut quand même échouer en production.
FlinDB avait 2 248 tests passant. Mais aucun de ces tests ne simulait le cycle complet : navigateur -> soumission de formulaire -> action serveur -> injection d'état -> exécution VM -> sauvegarde FlinDB -> écriture WAL -> destruction VM -> nouvelle VM -> rejeu WAL -> rendu de page -> navigateur.
Les bugs n'étaient pas dans la couche base de données de FlinDB. Ils étaient dans les points d'intégration.
Ce qui l'a fait fonctionner
Premièrement, le modèle de persistance basé sur le WAL. Chaque mutation est écrite dans un fichier journal. Le WAL est le pont entre les durées de vie des VM.
Deuxièmement, le rendu côté serveur. Les opérations de base de données s'exécutent sur le serveur où FlinDB a un accès direct au système de fichiers.
Troisièmement, le proxy $flin. Le runtime JavaScript côté client maintient un objet proxy qui reflète l'état côté serveur.
Le résultat est une base de données qui semble instantanée du point de vue de l'utilisateur -- taper un todo, appuyer sur Entrée, le voir apparaître -- tout en s'exécutant entièrement sur le serveur avec des garanties ACID complètes, la persistance WAL et la récupération après crash. Le navigateur est une fine couche de présentation. La base de données est réelle.
Ceci est la partie 15 de la série « How We Built FlinDB », documentant comment nous avons construit un moteur de base de données embarqué complet pour le langage de programmation FLIN.
Navigation de la série : - [068] FlinDB Hardening for Production - [069] FlinDB vs SQLite: Why We Built Our Own - [070] Persistence in the Browser (vous êtes ici)
Ceci conclut l'arc FlinDB. De la configuration zéro embarquée à la persistance navigateur, de la conception centrée sur les entités à l'event sourcing EAVT, des index hash à la recherche sémantique -- FlinDB est un moteur de base de données complet construit pour un langage qui croit que les données devraient juste fonctionner.