Créer une App Moderne — Avalonia UI & .NET

🧭 Module 5 — Initiation à la Clean Architecture

Empêcher le ViewModel de devenir un monolithe où tout est mélangé.

Créer une App Moderne — Avalonia UI & .NET

📝 Changelog

  • Introduction des couches Domaine et Infrastructure.
  • Création d'une abstraction ITodoRepository.
  • Première implémentation mémoire pour préparer les stockages réels.
  • Ajout de la configuration du conteneur DI dans App.axaml.cs.
  • Ajout d'un exemple minimal de test unitaire xUnit sur le ViewModel.
Créer une App Moderne — Avalonia UI & .NET

Objectif du module

  • Définir des responsabilités claires.
  • Extraire l'accès aux données hors du ViewModel.
  • Introduire l'inversion de dépendance.
  • Préparer une application testable et évolutive.
Créer une App Moderne — Avalonia UI & .NET

Problème à résoudre

  • Le ViewModel gère déjà l'état.
  • Si on ajoute le disque, le HTTP et le WebSocket ici, tout se mélange.
  • Les tests deviennent lourds.
  • L'évolution devient risquée.
Créer une App Moderne — Avalonia UI & .NET

Contrat métier minimal

public interface ITodoRepository
{
    Task<IReadOnlyList<TodoItem>> GetAllAsync();
    Task AddAsync(TodoItem item);
}
  • Le ViewModel dépend d'une promesse, pas d'un détail technique.
Créer une App Moderne — Avalonia UI & .NET

Le concept clé: inversion de dépendance

  • Le centre du système ne doit pas connaître l'extérieur.
  • L'interface décrit le besoin.
  • L'infrastructure implémente ce besoin.
  • On change le stockage sans réécrire l'écran.
Créer une App Moderne — Avalonia UI & .NET

Vue d'ensemble des couches

Couches Clean Architecture avec UI, Application, Domaine et Infrastructure séparées

Créer une App Moderne — Avalonia UI & .NET

Schéma des dépendances

Créer une App Moderne — Avalonia UI & .NET

Première implémentation: mémoire vive

public sealed class InMemoryTodoRepository : ITodoRepository
{
    private readonly List<TodoItem> _items = new();
    public Task<IReadOnlyList<TodoItem>> GetAllAsync() => Task.FromResult<IReadOnlyList<TodoItem>>(_items);
    public Task AddAsync(TodoItem item) { _items.Add(item); return Task.CompletedTask; }
}
Créer une App Moderne — Avalonia UI & .NET

Ce que gagne l'application

  • Remplacement facile du stockage.
  • Tests unitaires du ViewModel sans disque ni réseau.
  • Architecture plus lisible pour l'équipe.
  • Meilleure anticipation des modules suivants.
Créer une App Moderne — Avalonia UI & .NET

Configurer le conteneur DI dans Avalonia

// App.axaml.cs
public partial class App : Application
{
    public IServiceProvider? Services { get; private set; }

    public override void OnFrameworkInitializationCompleted()
    {
        var services = new ServiceCollection();
        services.AddSingleton<ITodoRepository, InMemoryTodoRepository>();
        services.AddSingleton<TodoListViewModel>();
        Services = services.BuildServiceProvider();

        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            desktop.MainWindow = new MainWindow
            {
                DataContext = Services.GetRequiredService<TodoListViewModel>()
            };
        base.OnFrameworkInitializationCompleted();
    }
}
  • Le conteneur crée et injecte les dépendances automatiquement.
  • On swap l'implémentation en changeant une seule ligne de configuration.
Créer une App Moderne — Avalonia UI & .NET

Tester le ViewModel sans interface

Un ViewModel bien construit se teste avec xUnit sans lancer Avalonia.

dotnet add package xunit
dotnet add package xunit.runner.visualstudio
public class TodoListViewModelTests
{
    [Fact]
    public async Task AjouterTache_AjouteLaTacheALaListe()
    {
        var repo = new InMemoryTodoRepository();
        var vm = new TodoListViewModel(repo);
        vm.NouvelleTache = "Écrire des tests";

        await vm.AjouterTacheCommand.ExecuteAsync(null);

        Assert.Single(vm.Taches);
        Assert.Equal("Écrire des tests", vm.Taches[0].Title);
    }
}
  • Zéro dépendance à l'UI.
  • L'architecture est validée si ce test passe.
Créer une App Moderne — Avalonia UI & .NET

Livrable du module

  • L'application se comporte comme avant.
  • Le code est désormais séparé par responsabilités.
  • Le ViewModel dépend de ITodoRepository et non d'une classe concrète.