Partie 1 sur 3 de la série Mes Recettes : Partie 2 – Architecture · Partie 3 – Tests & Livraison | English
Pourquoi ce projet existe
Je voulais un moyen rapide et portable d’indexer mes recettes de livres de cuisine — relier auteurs, livres, pages, notes et évaluations — avec un minimum d’infrastructure backend. Plutôt que de monter une API personnalisée + une base de données, j’ai associé Blazor WebAssembly (C# dans le navigateur) à Supabase (PostgreSQL managé, Auth, Realtime, REST).
Objectifs :
- Expérience single-page, navigation instantanée
- Modèles fortement typés de bout en bout
- Authentification sans réécrire les flux
- Base pour ajouter plus tard recherche, mode hors‑ligne et collaboration temps réel
Choix technologiques (et pourquoi)
Préoccupation | Choix | Raison |
---|---|---|
Runtime SPA & UI | Blazor WebAssembly (.NET 9) | C# partout, composants réutilisables |
Composants UI | MudBlazor | Material Design, dialogues, tableaux, theming |
Backend | Supabase | PostgreSQL + Auth + Realtime + REST + RLS |
Modélisation | PostgREST + attributs C# | Mappage direct, peu de couches au départ |
Validation | DataAnnotations | Intégré, simple, testable |
Tests | xUnit | Standard .NET, exécution parallèle |
Déploiement (cible) | Hébergement statique/CDN | Coût réduit + distribution mondiale |
Architecture haut niveau
Le navigateur télécharge l’application Blazor une fois; ensuite les appels vont directement aux endpoints REST Supabase (SDK officiel).
Extrait de code clé
Configuration de Supabase au démarrage :
var options = new SupabaseOptions
{
AutoRefreshToken = true,
AutoConnectRealtime = true
};
builder.Services.AddSingleton(sp =>
new Client(
builder.Configuration["supabase:Url"] ?? string.Empty,
builder.Configuration["supabase:Key"] ?? string.Empty,
options
)
);
builder.Services.AddScoped<ISupabaseAuthWrapper, SupabaseAuthWrapper>();
builder.Services.AddScoped<AuthService>();
Isolation de l’authentification :
public class SupabaseAuthWrapper : ISupabaseAuthWrapper
{
private readonly Supabase.Client _client;
public async Task<Session?> SignIn(string email, string password) =>
await _client.Auth.SignIn(email, password);
public async Task SignOut() => await _client.Auth.SignOut();
public User? CurrentUser => _client.Auth.CurrentUser;
}
Modèle relié directement à la table :
[Table("recettes")]
public class Recipe : BaseModel
{
[PrimaryKey("id")] public int Id { get; set; }
[Column("name"), Required] public string Name { get; set; } = string.Empty;
[Column("rating"), Range(1,5)] public int Rating { get; set; }
[Column("notes")] public string? Notes { get; set; }
[Column("book_id")] public int? BookId { get; set; }
[Reference(typeof(Book), joinType: ReferenceAttribute.JoinType.Left, true)]
public Book? Book { get; set; }
}
Réalisé vs. prévu
Domaine | Statut |
---|---|
Auth (email/mot de passe) | Implémenté |
Modèles (Recette / Livre / Auteur / Jonction) | Implémentés |
UI CRUD de base | Implémentée (dialogues & pages) |
Validation note (1–5) | Implémentée + tests |
Repository / Unit of Work | Documenté (pas encore câblé) |
Couche de cache | Prévu (pattern rédigé) |
Mises à jour temps réel | Activées côté config; amélioration ciblée |
Recherche plein texte & index | Prévu (exemples SQL) |
Pipeline CI/CD | Documenté; workflow à ajouter |
Leçons à ce stade
- Supabase + Blazor réduit fortement le temps jusqu’à la première fonctionnalité.
- Garder les modèles proches du schéma est acceptable tôt; on abstraira plus tard.
- DataAnnotations restent suffisants pour les contraintes initiales.
- MudBlazor accélère l’UI mais il faut centraliser les décisions de thème tôt.
Prochaines étapes
- Introduire un
RecipeService
+ décorateur de cache - RLS pour contraindre la propriété par utilisateur
- Tests d’intégration sur instance Supabase de staging
- Écoute Realtime pour refléter les modifications concurrentes
Source
GitHub : https://github.com/mongeon/RecettesIndex