(04) Systèmes / Étude de cas
yourFinance
Les soldes sont garantis par la base de données.
yourFinance est une application de finances personnelles multi-utilisateur : une API en Rust, PostgreSQL et un seul client pour iOS, Android, le web et le bureau. Le code est privé ; cette page montre comment elle est construite et pourquoi.
- Début
- Juillet 2025
- Version
- 1.2.0, octobre 2026
- Équipe
- Deux développeurs
- Code
- Privé
Rust · Axum · SQLx · PostgreSQL 18 · Expo · React Native · Tauri · Docker
Le problème
Des données de finances personnelles ne servent que si l'on peut se fier aux soldes. L'application est utilisée par plusieurs personnes sur plusieurs appareils, et une erreur dans un client ou dans un handler ne doit pas pouvoir laisser un compte avec un mauvais montant.
Elle doit aussi gérer plusieurs devises, reprendre tout l'historique d'une autre application (Money Manager) et afficher des statistiques qui ne sont jamais périmées.
Contraintes
- L'API n'écrit jamais un solde : chaque variation est un mouvement, y compris l'ajustement.
- Chaque ligne personnelle appartient à un utilisateur, et PostgreSQL répète la règle avec la row-level security sur chaque connexion du pool.
- Un seul code pour iOS, Android et le web ; l'application de bureau est l'export web dans une coque Tauri.
- Un changement du schéma ou de l'API doit casser le build, pas l'application en service : le SQL est vérifié à la compilation et les types du client sont générés à partir du document OpenAPI.
Architecture
Trois niveaux : le client se limite à présenter, l'API authentifie, valide et agrège, la base de données est la source de vérité. L'API est un workspace Cargo de crates en couches : chacun ne dépend que de ceux du dessous, et la CI vérifie que le domaine reste sans E/S.
yourf-api
HTTP : routes fines, authentification, traduction des erreurs, OpenAPI.
yourf-worker
Tâches de fond : notifications push, récapitulatif des alertes, mouvements récurrents, rapprochement des soldes.
yourf-runtime
Ce que les deux binaires partagent : configuration, télémétrie, cycle de vie.
yourf-db
Dépôts SQLx, périmètre de la row-level security, écritures dans une seule transaction.
yourf-import
Lecteurs des fichiers d'autres applications vers un format neutre, sans base de données.
yourf-domain
Modèles, contrats et règles métier. Aucune E/S.
Deux binaires, une seule image : l'API et le worker se déploient et montent en charge séparément.
Une requête, de bout en bout
La création d'un mouvement :
- 01Client
Le formulaire valide le brouillon et l'envoie avec le jeton.
- 02API
Elle authentifie le jeton, charge l'utilisateur, valide le corps et insère le mouvement avec ses répartitions par compte et par catégorie dans une seule transaction.
- 03Base de données
Des triggers différés vérifient que les répartitions tombent juste et que comptes, catégories et étiquettes appartiennent à l'utilisateur, puis mettent à jour les soldes. Une violation annule tout et devient un 400 avec un code de règle stable.
- 04Client
En cas de succès, les listes, les totaux et le tableau de bord se rechargent.
Choix et compromis
- 01
L'intégrité vit dans la base de données
Les soldes, la propriété des données et les règles sur les catégories sont garantis par des triggers. Cela veut dire écrire de la logique en SQL plutôt qu'en Rust ; en échange, aucun client et aucun handler ne peut corrompre les données.
- 02
Des soldes vérifiables, pas une partie double complète
Chaque effet sur un solde est une écriture signée, avec une seule définition utilisée à la fois par le trigger qui écrit les soldes et par la tâche qui les rapproche. Une partie double complète ne vaudrait la réécriture que si une fonction avait besoin d'un bilan.
- 03
Un virement est un mouvement avec deux montants
Ce qui a quitté le compte d'origine et ce qui est arrivé à destination. La différence couvre aussi bien un change qu'une commission, qui n'apparaît donc pas comme une dépense dans les statistiques.
- 04
Pas de file de tâches
Le worker prend les lignes à traiter avec FOR UPDATE SKIP LOCKED et la file des notifications push est réveillée par LISTEN/NOTIFY. Chaque passage est idempotent : l'exécuter deux fois, ou sur plusieurs réplicas, donne le même résultat.
- 05
Des statistiques calculées à chaque requête
Le tableau de bord et les statistiques agrègent les mouvements en SQL à chaque fois : pas d'agrégats précalculés ni de cache, donc les chiffres ne sont jamais périmés. Le prix à payer : les requêtes doivent être rapides par elles-mêmes.
Mesures
Le profilage des requêtes analytiques en a montré deux dont le coût suivait tout l'historique au lieu de la période choisie. Avec le plan générique d'une requête préparée, le planificateur choisissait une boucle imbriquée qui relançait une jointure pour chaque mouvement. Les index n'y étaient pour rien : la correction a consisté à réécrire la vue pour que chaque branche parte du mouvement.
| Requête | Avant | Après |
|---|---|---|
| Flux quotidiens (tendance des statistiques)daily_flows | 64.5 ms | 1.9 ms |
| Dépenses essentielles (résumé du tableau de bord)essential_expenses | 48.0 ms | 1.5 ms |
L'application



La suite
- Une date d'échéance sur les comptes, avec un avertissement avant qu'elle n'arrive.
- L'amortissement : un coût réparti sur plusieurs mois dans les statistiques, alors que le solde reste en trésorerie.
- Un assistant qui répond aux questions sur les statistiques et les comptes avec des outils en lecture seule : le modèle choisit l'outil, chaque chiffre est calculé par le backend.
Code et démonstration
Le dépôt est privé. Sur demande, je montre le code et l'application en fonctionnement.