5 points par GN⁺ 2023-12-02 | 1 commentaires | Partager sur WhatsApp
  • Les applications frontend complexes partent d’un cache de réponses d’API, puis finissent par assumer des index manuels et l’invalidation du cache, au point de ressembler à de petites bases de données réimplémentées projet par projet
  • Dans les frameworks déclaratifs comme React, pour éviter d’appeler l’API à chaque rendu, on place un cache dans l’état local ou dans une couche Redux, et cette couche finit progressivement par jouer le rôle de stockage centralisé
  • Les stockages indexés par ID et les structures de consultation par date accélèrent les recherches, mais le maintien de la cohérence entre plusieurs structures comme CACHE et ENTRIES_BY_DATE alourdit les tests et les revues de code
  • Les modifications optimistes mettent à jour l’UI immédiatement avant la réponse du serveur, ce qui améliore la vitesse perçue, mais elles entraînent des coûts de cohérence : duplication de la logique client/serveur, suivi des changements en cours, rollback en cas d’erreur et réconciliation après redémarrage de l’app
  • SQLSync cherche à fournir, dans la stack frontend, un cache durable, des index, des contraintes, des modifications optimistes et des requêtes réactives via une base locale SQLite et une synchronisation proche de Git Rebase

Comment un cache frontend grossit jusqu’à devenir une base de données

  • La gestion des données côté frontend peut commencer par un simple cache qui stocke les réponses d’API dans des variables locales
    • Les frameworks déclaratifs comme React rerender l’arbre plusieurs fois pendant les interactions utilisateur
    • Pour éviter d’envoyer une requête API à chaque rendu, on peut stocker le résultat d’une requête ou une erreur dans l’état du composant avec useState et useEffect
    • L’exemple est simplifié par souci de clarté ; en pratique, utiliser une bibliothèque API éprouvée est aussi une option
  • Le cache peut être déplacé vers un niveau plus haut de l’arbre UI, ou en dehors de l’UI
    • Redux est une bibliothèque de gestion d’état pour React qui unifie l’état et coordonne des modifications atomiques au fil du temps
    • L’écosystème Redux s’est étendu avec des outils et des patterns pour gérer la mise en cache des données d’API
    • Ce type d’usage vise à centraliser la logique de cache, coordonner les mises à jour et partager les résultats mis en cache entre composants
  • À mesure que la couche de cache grossit, elle se rapproche d’un système de stockage centralisé qui manipule efficacement les données en fonction du moteur de rendu et des actions utilisateur

Index manuels et charge de cohérence

  • Même côté frontend, on peut stocker les données reçues du serveur dans un objet indexé par ID afin de les consulter et modifier rapidement
    • Dans les applications utilisant des API REST, il est fréquent de lire les données par lots puis d’enrichir davantage les objets nécessaires
    • Stocker les objets par ID facilite la fusion des résultats d’API dans le cache
    • Cette structure est optimisée pour créer, lire, modifier et supprimer par ID
  • Les filtrages qui nécessitent de parcourir plusieurs éléments poussent à créer des index séparés pour éviter de scanner toutes les entrées
    • Créer une structure ENTRIES_BY_DATE à partir des parties année/mois/jour de createdAt permet de retrouver rapidement les entrées d’une date donnée
    • En contrepartie, il faut maintenir en permanence la cohérence entre CACHE et ENTRIES_BY_DATE
    • Les recherches sur une plage de dates nécessitent plusieurs accès ; un tableau trié par date, avec une logique de requête et de mise à jour plus complexe, pourrait être une meilleure structure
  • Plus les index se multiplient, plus chacun exige sa propre logique de création, de mise à jour et de requête
    • La vérification de la justesse devient une charge pour les tests et les revues de code
    • Oublier une suppression ou une mise à jour dans un index peut créer des bugs difficiles à trouver
    • On peut finir par consacrer plus de temps à gérer cette complexité d’infrastructure qu’à développer de nouvelles fonctionnalités applicatives
  • Les index de vraies bases de données sont bien plus complexes que le simple fait de stocker les données sous une autre forme
    • Ils incluent des aspects comme la collecte de statistiques, la gestion des versions des données, le contrôle des transactions, les verrous et les interactions avec l’optimiseur de requêtes

Les problèmes de cohérence ajoutés par les modifications optimistes

  • Une modification optimiste simule localement l’effet d’une opération avant la réponse du serveur
    • L’UI peut réagir immédiatement, comme s’il n’y avait pas de latence réseau
    • Si le serveur prend une décision différente de celle attendue ou si une erreur survient, l’UI peut devoir annuler le changement et demander à l’utilisateur de corriger le problème
    • C’est un outil puissant lorsque le client peut bien prédire le résultat serveur, que les erreurs peuvent être gérées côté client et que les logiques restent étroitement synchronisées
  • Les mises à jour optimistes suivent généralement quatre étapes
    • L’UI déclenche une opération d’écriture
    • En supposant que le serveur acceptera, la modification est appliquée au cache local et l’UI est rerendue immédiatement
    • L’opération de modification est envoyée au serveur de façon asynchrone
    • La réponse du serveur est fusionnée dans le cache local, écrase l’ancienne modification optimiste et, si nécessaire, l’UI est rerendue
  • Maintenir la cohérence avec le serveur introduit plusieurs contraintes
    • Il faut dupliquer de la logique côté client et côté serveur pour prédire le résultat
    • Il faut suivre chaque modification en cours pour gérer les erreurs asynchrones ou les divergences serveur
    • Pour une meilleure expérience utilisateur, il peut être nécessaire de rendre durable la partie optimiste du cache afin de réconcilier les changements après un redémarrage de l’app
  • Ce processus augmente le temps de développement et le coût de vérification de la justesse, et la gestion des données peut passer avant la création de valeur utilisateur ou de fonctionnalités différenciantes

La complexité de l’invalidation récursive du cache

  • Dans les applications riches en données, la même information apparaît à plusieurs endroits du cache
    • L’exemple de cache stocke ensemble projects, tasks et users
    • Après l’achèvement d’une tâche, plusieurs parties peuvent être affectées : progression du projet, tâches assignées à l’utilisateur, nouvelles informations de tâche
  • Pour réaligner le cache avec le serveur après l’achèvement d’une tâche, plusieurs allers-retours peuvent être nécessaires
    • Signaler au serveur que la tâche est terminée
    • Rafraîchir le projet puisque sa progression a changé
    • Vérifier si de nouvelles tâches ont été assignées
    • S’il y a une nouvelle assignation, récupérer cette tâche
  • Une API plus complexe peut réduire le nombre d’allers-retours, mais il reste un couplage entre l’API ou la logique client et le modèle de données sous-jacent
    • GraphQL est une approche de ce problème, mais pas une solution complète
  • Une structure où l’UI doit savoir quelles parties du cache sont concernées par chaque modification devient fragile à mesure qu’elle grandit
    • Les relations entre données et les agrégats peuvent affecter plusieurs parties du cache local
    • Quand l’équipe d’ingénierie grandit, le problème peut franchir les frontières entre équipes et ressembler à une grande variable globale mutable dans un grand projet logiciel
  • Combiné aux modifications optimistes, cela pousse le client à répliquer davantage de logique backend pour prédire les changements serveur
    • Dans l’exemple, il peut essayer de retirer task 1 de user 1 et de recalculer la progression à partir du nombre total de tâches
    • Plus on cherche à prédire localement les modifications imbriquées, plus le client duplique la stack backend

La stack de base de données frontend proposée par SQLSync

  • SQLSync est une stack de base de données optimisée pour le frontend, construite au-dessus de SQLite
    • Son moteur de synchronisation s’appuie sur des idées issues de Git et des systèmes distribués
    • Elle est conçue pour s’intégrer naturellement aux frameworks frontend populaires comme React, Vue et Next.js
    • Son objectif est de traiter les problèmes difficiles de gestion des données afin que les développeurs puissent se concentrer sur les fonctionnalités propres à leur application
  • L’exemple d’application Todo implémente toute la couche de données avec 60 lignes de Rust et quelques requêtes SQL dispersées dans les composants
    • SQLSync fournit un cache durable, les index, contraintes, triggers et optimisations de requêtes de SQLite, les modifications optimistes, l’invalidation intelligente du cache et les requêtes réactives
  • Les données locales sont stockées dans une ou plusieurs bases SQLite
    • Les index sont faciles à créer et se synchronisent automatiquement avec les données
    • Comme côté backend, la base de données peut utiliser automatiquement les index pour accélérer les requêtes
    • SQL peut exprimer des requêtes de données complexes, et des fonctionnalités comme les triggers, les foreign keys, les constraints ou la full-text search peuvent aussi être utilisées
  • Les modifications optimistes sont traitées par un reducer
    • La structure est similaire aux concepts fondamentaux de Redux
    • Le reducer peut être écrit dans n’importe quel langage compilable en WebAssembly
    • SQLSync exécute les modifications de façon optimiste côté client, et les exécute côté serveur dans un ordre globalement cohérent
    • Le client se synchronise ensuite avec le serveur via une opération similaire à Git Rebase
  • Cette architecture a l’avantage de supprimer le besoin d’invalidation récursive du cache
    • Toute la logique de modification des données est écrite dans un reducer facile à partager entre client et serveur
    • Toutes les transformations de données survenues pendant une modification deviennent automatiquement visibles
    • Comme la synchronisation fonctionne comme Git Rebase, même si le serveur effectue des changements différents de ceux du client, le client a la garantie d’aboutir au même résultat cohérent

Travaux connexes

  • « Building data-centric apps with a reactive relational database » de Riffle traite de l’idée de stocker tout l’état applicatif, y compris l’état de l’UI, dans une seule base de données réactive
    • Les requêtes réactives offrent un modèle mental clair et s’accordent bien avec les systèmes déclaratifs comme React
    • Les problèmes de développement d’apps clientes y sont abordés avec des idées issues de la communauté des bases de données
    • Le texte couvre les avantages de modéliser l’état avec un modèle relationnel et de vrais index
  • Stepan d’Instant.db a écrit deux articles sur les bases de données dans le navigateur
    • Database in the Browser, a Spec
    • A Graph-Based Firebase
    • Ces deux articles abordent des problèmes similaires en se concentrant davantage sur la relation entre les stacks frontend et backend, et expliquent ce qui a motivé la création d’Instant.db comme successeur de Firebase fondé sur un graphe
  • CR-SQLite, de Matt Wonlaw, est une extension SQLite
    • Elle utilise des types de données répliquées sans conflit (CRDT) et un journal d’événements à ordre causal pour fusionner les données de manière cohérente
    • Elle permet à des applications peer-to-peer de stocker des données dans SQLite et de collaborer sans coordinateur central
    • C’est aussi un exemple d’exécution de SQLite dans le navigateur
  • Matt Wonlaw explore également des idées connexes

1 commentaires

 
GN⁺ 2023-12-02
Avis de Hacker News
  • Je connais bien ce projet, et son créateur est un ami ; je vais essayer de le faire venir ici pour répondre aux questions.
    C’est un architecte de bases de données expérimenté. Avec SQLsync, il a voulu permettre aux développeurs frontend d’interroger et de mettre à jour une base de données distante comme si elle se trouvait entièrement dans le navigateur. En pratique, c’est presque le cas : grâce à WASM, on peut envoyer toute une base SQLite dans le navigateur. Le cœur du système est un algorithme réactif malin mais simple, qui synchronise plusieurs clients.
    Si l’on considère qu’une grande partie du travail de développement consiste à synchroniser des données, alors React et les API REST peuvent aussi être vus comme des procédures de synchronisation, et cette approche ouvre de nouvelles possibilités. Plutôt que de créer encore une étrange base de données sur mesure sous forme d’arbre d’objets récupérés depuis une API et mis en cache, il suffit de s’appuyer sur la puissance d’une base relationnelle pour mettre à jour et interroger localement.

    • Déplacer tout le système de requêtes vers le frontend me semble être ce que beaucoup de développeurs frontend veulent vraiment. Ils veulent un système de requêtes puissant sur les données, pas réinventer sans cesse une couche de transport, REST, GraphQL, *RPC, etc.
      Cela dit, dans les entreprises web traditionnelles, l’adoption est difficile à cause des équipes backend/frontend spécialisées. Cela revient à retirer la base de données, le backend, le transport et la couche d’authentification pour les remplacer par un système en un seul bloc ; or la plupart des architectes système viennent du backend et connaissent mal ce problème. Comme cela touche en profondeur les deux côtés, ça s’intègre mal aux systèmes existants et finit par convenir surtout aux nouveaux développements. Le backend n’étant pas non plus un service AWS ou Azure, ni particulièrement compatible avec Lambda, la plupart des profils d’architectes que je rencontre n’y toucheront pas.
      Cette approche existe déjà en partie avec une technologie plus ancienne, CouchDB+PouchDB. Pour certains usages, ça fonctionne assez bien, mais le système de requêtes n’est pas idéal, et la manière de gérer l’authentification et le périmètre des données est déroutante pour la plupart des gens. Le cas le plus simple est celui où les données appartiennent entièrement à un seul utilisateur et où l’on utilise tel quel un modèle de base de données par utilisateur. En partitionnant fortement les données avec des CRDT, on réduit aussi beaucoup les problèmes de conflits.
      Mais il y a un problème de scalabilité. Quand 10 000 à 100 000 utilisateurs sont connectés, CouchDB demande énormément de CPU ; la technologie est ancienne, même si elle reste maintenue. Du point de vue de la conception système, dès que l’on commence à partager des données entre utilisateurs, la complexité augmente brutalement : on ne la résout pas vraiment, on ne fait que la déplacer, ce qui rend l’approche moins adaptée.
      Cette approche semble viser le même objectif, mais elle risque fort de rencontrer des problèmes de scalabilité similaires. J’ai hâte de voir comment elle va évoluer ; cela ressemble à un premier pas.
    • Ça ressemble beaucoup à Couchbase, qui permet d’interroger et de mettre à jour une base de données synchronisée à distance puis resynchronisée vers les pairs. On peut aussi contrôler assez facilement l’authentification ou la logique métier avec des plugins JavaScript côté serveur.
    • Je me demande sincèrement pourquoi on ne pourrait pas simplement mettre en cache les parties pertinentes dans LocalStorage / SessionStorage.
      Je me souviens qu’à une époque Chrome avait essayé d’intégrer littéralement une base de données SQL dans le navigateur, mais ça n’a pas vraiment pris, et localStorage est devenu dominant. Je ne cherche pas à minimiser l’utilité de ce projet ; en général, on choisit ce que le navigateur fournit. J’ai de grandes attentes quant aux possibilités qu’apporte WASM, et à ce qu’il pourra amener dans le navigateur à mesure qu’il mûrit ou gagne en fonctionnalités.
  • Dans une ancienne entreprise, nous utilisions un logiciel de gestion de projet avec un mécanisme de check-out/check-in pour les modifications. Quand on faisait un check-out d’un projet, on téléchargeait une copie à modifier localement ; au check-in, on la renvoyait au serveur. Pendant le check-out, le projet était verrouillé. À l’ère des applications avec mises à jour en direct, tout le monde trouvait cette approche dépassée.
    Mais après dix ans à construire des webapps SPA, cette méthode de synchronisation des données me semble maintenant avoir été en avance sur son temps.

    • Décider s’il faut prendre en charge les mises à jour en direct parallèles par plusieurs utilisateurs, ou verrouiller pour qu’une seule mise à jour ait lieu à la fois, n’est finalement pas une décision technique mais une décision métier. La question clé est de savoir si les règles métier adaptées à l’application permettent de gérer des mises à jour concurrentes en direct.
      Au bout du compte, cela revient à savoir si l’on peut implémenter une procédure qui résout de manière cohérente les incohérences entre plusieurs mises à jour simultanées. Parfois c’est possible, parfois non ; cela dépend davantage des règles métier que de la capacité technique.
      Si les règles métier ne permettent pas d’implémenter un mécanisme de résolution, alors il faut un verrouillage pour n’autoriser qu’une seule mise à jour à la fois, même si l’on possède la capacité technique de prendre en charge les mises à jour concurrentes.
    • En allant dans cette direction, on résout beaucoup de problèmes et l’implémentation devient très simple.
      Mais il est difficile de convaincre les gens que c’est vraiment ce qu’ils veulent. On tombe facilement dans la grande illusion selon laquelle tout doit toujours être disponible, alors qu’en réalité, c’est souvent une seule personne qui modifie quelque chose à la fois ; et si deux personnes ou plus doivent travailler dessus, elles doivent de toute façon se parler ou se coordonner.
      Même dans un développement entièrement distribué comme avec Git, on ne peut pas résoudre les conflits automatiquement par magie. Pour choisir la bonne modification, il faut toujours communiquer avec les autres et comprendre le contexte.
    • C’est précisément pour cela que le paradigme classique des bases de données relationnelles comme MySQL continue d’exister, même si certains le dénigrent face aux bases non relationnelles comme NoSQL ou MongoDB. On ne peut pas tout remplacer simplement parce que quelque chose est rapide ou à la mode.
      Certaines choses ont besoin de solutions éprouvées.
    • Ça fait penser à RCS : https://en.wikipedia.org/wiki/Revision_Control_System
      Je me souviens que, quand mon ancienne entreprise est passée de RCS à CVS, l’un de mes collègues était agacé que CVS ne prenne pas en charge le check-out verrouillé.
      https://en.wikipedia.org/wiki/Concurrent_Versions_System
    • J’aime bien cette approche. SQLSync fait en fait cela en continu, mais avec une coordination explicite, un modèle de check-in/check-out de ce type devrait aussi être possible.
      Je pense qu’une stratégie de verrouillage à propriétaire unique peut aussi être simulée avec SQLSync. Cela dit, selon l’application, ce ne sera peut-être pas nécessaire. Si l’objectif est de travailler hors ligne puis de fusionner quand c’est prêt, SQLSync fournit ce pattern par défaut. Si l’objectif est qu’un seul client puisse effectuer des changements, alors il faut un pattern de verrouillage centralisé, et il est probablement possible de le coordonner via SQLSync.
  • Ici se mêlent le principe selon lequel « ce qui est mesuré est géré » et l’erreur des coûts irrécupérables.
    Le vrai problème des bases de données, c’est la complexité. Chaque fonctionnalité prise isolément est généralement sûre, mais dès que stabilité, cache et indexation s’entrecroisent, la complexité explose, et il est en général absurde d’implémenter une base de données spécialisée pour son domaine.
    Mais au moment où une entreprise réalise qu’elle a déjà investi dans l’implémentation de ces trois fonctionnalités et y a consacré beaucoup de ressources, il devient politiquement difficile de recommander de les retirer, et le coût réel pour éliminer cette dette technique d’un seul coup est également élevé.
    À mon avis, le vrai problème, c’est la syntaxe SQL. Si l’expérience d’utilisation d’une base relationnelle standard était aussi agréable qu’une syntaxe familière de style C, plutôt que de l’anglais cassé, l’incitation à utiliser une DB plutôt qu’à en construire une soi-même serait plus forte. Les bases NoSQL ont été un bon pas dans cette direction, mais elles se sont généralement trop focalisées sur le big data au détriment de l’utilité au quotidien. Des choses comme Redis se sont imposées et sont très bien.
    Faciliter l’exécution de SQL est une approche raisonnable, mais dans les bonnes bases de données, par exemple Postgres que j’aime beaucoup, SQL est le langage par défaut, et il est difficile d’obtenir de l’efficacité sans l’utiliser. On a vraiment besoin d’une base de données du genre PostgresPostSQL, qui répliquerait parfaitement Postgres tout en ayant un parseur par défaut prenant en charge un langage à la bonne syntaxe.

    • Je ne vois pas ce que SQL a précisément de difficile. Je pense que tous les développeurs devraient connaître SQL. La syntaxe SQL est bonne et elle a fait ses preuves en survivant aussi longtemps. Plutôt que de la critiquer, il serait plus utile d’investir du temps pour vraiment l’apprendre.
    • SQL est souvent critiqué, et je pense moi aussi qu’il y a des raisons de le faire, mais pourquoi n’a-t-on pas réussi à créer mieux ?
      En programmation généraliste, des dizaines de langages sont utilisés et continuent d’évoluer. Même JavaScript, pourtant difficile à changer parce qu’il est exécuté par les navigateurs et qu’on ne contrôle pas celui de l’utilisateur, évolue grâce aux transpileurs et à WebAssembly.
      Mais pour les bases de données, il n’y a en pratique qu’un seul SQL. Il existe des alternatives, mais aucune ne s’approche de SQL en termes d’usage. Peut-être que SQL n’est finalement pas si mauvais.
      La raison tient peut-être au fait que le modèle relationnel est vraiment excellent. Les tentatives de s’en écarter ont de fortes chances de ne fonctionner que dans des niches. Le style déclaratif est lui aussi très bon, et il est difficile d’obtenir un grand succès en s’en éloignant. Si, au bout du compte, on ne crée qu’un SQL avec une syntaxe différente, ce ne sera pas, pour la plupart des gens, une amélioration suffisante pour changer de méthode.
    • J’aimerais que Postgres propose une API plus stable et de plus bas niveau que SQL. Elle pourrait ressembler à la forme des plans de requête obtenus avec EXPLAIN.
      Les applications écrites pour cette API pourraient implémenter une DB SQL. Il suffirait de parser SQL et d’implémenter un planificateur de requêtes qui émette des plans adaptés à cette API.
    • Je me demande si vous avez déjà envisagé d’ajouter une couche sémantique au-dessus de la base de données pour ceux qui veulent éviter d’écrire directement du SQL.
  • Je suis l’auteur. J’ai à peine parcouru la plupart des questions, et je continuerai à vérifier régulièrement si j’en ai manqué. Je me demande aussi si quelqu’un a déjà créé une meilleure façon de suivre les discussions HN.
    Les échanges jusqu’ici me réjouissent beaucoup. Le premier article se concentrait moins sur le fonctionnement concret de SQLSync que sur les motivations de l’ingénierie frontend qui m’ont amené à le créer. Je parlerai de son fonctionnement dans le prochain article.

    • Je me demande s’il est prévu de prendre en charge le mobile. C’est précisément là que j’aimerais le plus essayer ça.
  • Il ne faut pas donner aux utilisateurs un modèle mental qui peut briser la réalité de manière grave, ou invisible.
    Je crains que la synchronisation de bases de données à la place du modèle client-serveur ne soit l’un de ces cas. Le mécanisme de synchronisation peut simplement s’effondrer, ou reposer sur des hypothèses profondes qui ne sont pas satisfaites.
    Si l’on a besoin d’une UI rapide, créer un ensemble de primitives CRDT à utiliser me semble plus sûr, et le reste restera probablement de la soumission de formulaires.

    • D’accord. L’un des objectifs est que les développeurs qui utilisent SQLSync puissent comprendre facilement son modèle mental. Je suis sans doute biaisé, mais personnellement je trouve que le modèle de rebase est beaucoup plus facile à comprendre que les CRDT.
  • La synchronisation d’état entre client et serveur est un problème maudit.
    En acceptant de sacrifier un peu l’expérience utilisateur et en revenant à quelque chose de plus proche du modèle PHP / rendu côté serveur, on peut éviter entièrement ce problème. Les SPA sont bien, mais l’envoi de formulaires multipart fonctionne toujours. Avec très peu de JavaScript, on peut lisser la plupart des aspérités restantes.
    Dans nos produits web récents, l’état côté client se limite aux claims d’authentification de l’IdP tiers, à l’ID de session first-party dans les paramètres de requête, et au document courant. Honnêtement, je ne sais même pas où est stocké le premier. C’est le problème de Microsoft, pas le nôtre. Tout le reste de l’état est sur le serveur.
    Nous traitons le client comme un terminal idiot qui ne fait qu’émettre des entrées toute la journée. Nous n’utilisons ni cookies first-party ni stockage local. Cette approche a grandement amélioré l’expérience de développement ciblant iOS/Safari.
    Je voudrais donc demander quelle expérience vous cherchez réellement à offrir, et pourquoi elle justifie de séparer l’état client de l’état serveur.

    • Dans les interfaces graphiques grand public, le rendu optimiste est un flux très courant, et si vous faites cela, vous traitez bien de l’état client/serveur. On voit encore des spinners de chargement un peu partout, mais en général pour le chargement initial du contenu. Par exemple, Gmail ne vous fait pas attendre lorsqu’il archive un e-mail.
    • ElectricSQL a fait de grands progrès vers la résolution de ce problème. Il permet d’écrire dans SQLite côté client et garantit la synchronisation vers Postgres.
      Référence : https://news.ycombinator.com/item?id=37584049
  • Le modèle offline/local-first basé sur SQLite semble très en vogue en ce moment. C’est le troisième article que je lis à ce sujet cette semaine, et ça a l’air prometteur.
    Mais comment cela se compare-t-il à ElectricSQLhttps://electric-sql.com/ et PowerSynchttps://powersync.com/ ?

    • C’est effectivement un domaine très chaud. C’est très intéressant de voir émerger des approches différentes.
      ElectricSQL et PowerSync s’attaquent tous deux à un problème très difficile : la réplication partielle. L’idée est de créer une solution générale où une base de données centrale traditionnelle synchronise dans les deux sens uniquement ce dont le client a besoin, tout en prenant en charge les modifications optimistes et les questions de cohérence/résolution de conflits qui en découlent.
      L’inconvénient, c’est la complexité de mise en œuvre. Il faut suivre précisément quel sous-ensemble de la base de données complète possède chaque client, afin de pouvoir pousser les changements uniquement vers ce sous-ensemble. Il faut aussi un nouveau DSL pour spécifier quel sous-ensemble de l’état de la base télécharger, qu’il faut en plus apprendre et optimiser. Cela dit, c’est réjouissant de les voir s’attaquer à un problème très difficile, et lorsque SQLSync sera prêt à prendre en charge la réplication partielle, les bonnes pratiques auront déjà été établies.
      À l’inverse, SQLSync ne prend actuellement en charge que la synchronisation de toute la base. Tous les clients voient une vue cohérente de l’ensemble de la base de données. On peut immédiatement se demander si c’est une bonne idée, et cela ne convient pas à certaines apps. Mais si l’on pense à une app de finances personnelles, les objectifs clés sont la synchronisation entre appareils, la sauvegarde cloud, le fonctionnement hors ligne, etc. : stocker toute la base sur chaque appareil peut au contraire être le comportement souhaité. Un modèle de données orienté document comme Airtable peut aussi servir d’exemple. Si chaque Airtable est une base de données séparée, le client peut gérer quelles tables l’intéressent.
      En se concentrant sur la synchronisation de toute la base, le moteur de synchronisation devient beaucoup plus simple que les solutions qui prennent en charge la réplication partielle. L’un des avantages est que le backend est très léger. La démo actuelle (https://sqlsync-todo.pages.dev) s’exécute entièrement dans des Cloudflare Durable Objects en consommant très peu de stockage et de temps CPU.
      SQLSync a encore beaucoup de travail à faire pour rendre ces cas d’usage possibles, et reste proche d’un prototype, mais les premiers tests ont été très positifs.
  • Dans de grosses apps multitenant, quand les jeux de données individuels sont relativement petits, je me suis souvent demandé : « et si on envoyait simplement la base de données au client ? ». Ça ressemblait suffisamment à un pattern d’architecture maudit, très hors normes, pour que je n’aille pas plus loin. J’aimerais bien découvrir que j’avais tort.

    • Je l’ai déjà fait une fois pour une app de comptage de calories. Même avec des centaines de milliers d’aliments dans la base, l’app prenait beaucoup moins de place que la plupart des apps média ou des jeux.
    • Moi aussi, je me demande si cette approche est maudite. Jusqu’ici, elle s’est révélée bien meilleure que prévu. Une app comme https://sqlsync-todo.pages.dev devient triviale avec ce pattern.
      Il reste beaucoup à faire pour le prouver correctement, mais je suis assez enthousiaste à l’idée de continuer à pousser dans cette direction et de voir où cela mène.
    • La bonne réponse consiste à ramener l’UI sur le serveur, là où se trouve déjà la base de données, et à n’envoyer que du HTML au moteur de rendu HTML côté client, c’est-à-dire au navigateur web. Tout cet article revient à dire que « le frontend a franchi la ligne, dépassé la falaise et est tombé dans la mer ».
  • Ça ressemble à l’un de ces problèmes qui disparaissent complètement dès qu’on abandonne les SPA.
    Avec des solutions de la famille Hotwire ou htmx, les requêtes redeviennent simplement des requêtes serveur, et le problème consistant à les rendre rapides est beaucoup mieux compris.

    • Ce n’est pas seulement un problème de sites web. Les écosystèmes mobile et desktop devraient-ils eux aussi évoluer massivement vers des clients légers, comme les navigateurs ? Des apps simples comme Apple Reminders ou Google Tasks devraient-elles bloquer leur GUI à cause de problèmes de latence ou de connexion ?
    • J’ai récemment essayé htmx, et la complexité qu’il élimine — ainsi que la productivité qui en résulte — est vraiment délirante. Pouvoir conserver la stack que l’on veut est aussi une énorme bénédiction.
      Je l’ai utilisé avec OCaml + Web Components, et c’était une expérience productivité 10/10. Il suffit d’un outil de build qui compile plus vite qu’un battement de cil, et il n’y a pas besoin de câbler des mappings JSON entre frontend et backend, donc c’est vraiment productif.
    • Honnêtement, des solutions comme Hotwire ou Livewire ne sont pas aussi immédiates qu’une SPA.
      Personnellement, je préfère InertiaJs https://inertiajs.com. C’est une sorte de système de routeur frontend qui synchronise l’état avec le serveur « à l’ancienne ».
    • C’est le même genre de propos que « ne faites pas de webapps très interactives » ou « n’exploitez pas de service qui nécessite plus d’une VM ». C’est un point de vue absurde.
    • Je pense que le problème ne disparaît pas quand on s’éloigne des SPA, mais quand on s’éloigne des fonctionnalités très interactives que les bons produits ont souvent.
      C’est particulièrement vrai pour un produit qui doit fonctionner dans des régions où Internet est instable.
  • Je suis en train d’écrire un article très similaire sur les « bases de données full-stack ». Il traite du schéma où beaucoup d’apps recréent, dans le code client frontend, la logique du backend et de la base de données. La solution que nous recommandons consiste à choisir une base de données pouvant s’exécuter à la fois côté serveur et côté client, puis à synchroniser les deux
    La raison pour laquelle nous n’utilisons pas SQLite dans notre produit, c’est que, franchement, SQL n’est pas l’outil adapté pour interroger des données applicatives. Il ne s’emboîte pas facilement avec les structures de données voulues dans le code client, et presque toutes les bases de données SQL n’offrent aucun moyen de s’abonner aux changements d’une requête sans interroger celle-ci en boucle
    Si l’idée d’avoir une base de données complète côté client vous plaît et que vous voulez une intégration profonde avec TypeScript/JavaScript, jetez un œil à ce que nous construisons : https://github.com/aspen-cloud/triplit

    • En fait, Postgres offre un excellent moyen de s’abonner aux changements en temps réel via le WAL. Je maintiens aussi cette bibliothèque open source
      https://github.com/cpursley/walex
    • SQLite fournit effectivement le mécanisme nécessaire pour détecter les changements via l’update hook. Dommage que beaucoup de bindings SQLite ne l’exposent pas
      Je l’utilise de façon très simple. Quand les données d’une table sous-jacente changent, la requête est automatiquement réexécutée. Ce n’est sans doute pas aussi efficace que de mettre à jour les résultats de façon incrémentale, mais les requêtes SQLite étant généralement très rapides, je ne pense pas que ce soit un gros problème
    • Je ne suis pas d’accord avec l’idée que cela ne s’emboîte pas facilement avec les structures de données voulues dans le code client. La normalisation des données est importante pour les applications frontend réactives et, en pratique, nécessaire pour garder les données à jour. Toutes les opérations CRUD deviennent aussi beaucoup plus faciles à gérer
    • Avec Realm Sync et Mongo + Kotlin Multiplatform, on peut couvrir presque toutes les plateformes : serveur, web, mobile, desktop, etc. Bien sûr, cela a un coût. Les alternatives m’intéressent vraiment, et je me demande si cet aspect figure aussi dans l’article