Aller au contenu

Architecture

L'application est locale (base SQLite fichier, sans serveur), en JavaFX 26 / Java 25, injectée par Guice 7. L'organisation est en paquet-par-fonctionnalité : chaque écran/parcours vit dans son propre paquet, qui contient lui-même ses 4 couches MVVM.

Les patrons derrière cette architecture

Chaque choix (MVVM, contrats, DAO, Template Method…) applique un patron de conception au service des principes SOLID. La page Patterns et principes les détaille un par un (le quoi, le pourquoi ici, et les principes servis).

Carte du code

src/main/java/fr/univ_amu/iut/
├── App.java                     ← point d'entrée JavaFX (amorçage Guice + chrome)
├── module-info.java             ← module JPMS « vigiechiro » (open module)
├── commun/                      ← LE SOCLE partagé par toutes les fonctionnalités
│   ├── persistence/             ·   infrastructure DAO (SQLite, transactions, migrations)
│   ├── model/                   ·   domaine transverse (Horloge, Prefixe, Verdict, Statut...)
│   ├── viewmodel/               ·   état observable du chrome (NavigationViewModel...)
│   ├── view/                    ·   chrome (MainView, Navigateur) + contrats Ouvrir* / RafraichirAuRetour
│   ├── di/                      ·   racine de composition Guice (RacineInjecteur, modules socle)
│   └── outils/                  ·   harnais de capture d'écran (ApercuFx...)
├── sites/        passage/       importation/   qualification/   lot/
├── validation/   multisite/     diagnostic/    bibliotheque/   recherche/   ← les 10 fonctionnalités
├── cli/                         ← interface en ligne de commande (import/export scriptables)
└── perf/outils/                 ← bancs de mesure de performance

Les deux dernières entrées sont des surfaces transverses, documentées à part : cli/ (seconde façade headless sur le métier) et perf/outils/ (bancs de mesure des objectifs O3/O5).

Les 4 couches d'une fonctionnalité

Chaque feature (ex. sites/) suit le même découpage, du métier vers l'IHM :

Sous-paquet Rôle Règle clé
model/ Entités (records), services, et model/dao/ (accès SQLite) Aucune dépendance JavaFX (réutilisable, testable seul)
viewmodel/ État observable + logique de présentation Importe javafx.beans uniquement, jamais javafx.scene/fxml/stage
view/ Controller + *.fxml + *.css Se lie aux propriétés du ViewModel ; ne parle jamais à la base
di/ Le module Guice qui assemble la feature Publie ses services/VM au conteneur

Le sens MVVM

Le model ignore l'IHM ; le viewmodel porte l'état en propriétés observables (IntegerProperty, ObservableList…) sans toucher aux composants graphiques ; la view observe le viewmodel par data binding JavaFX.

Une feature peut être sans écran

Toutes n'ont pas les 4 couches. recherche/ (#144) est une feature de service : model + di seulement, pas de view. La recherche globale est exposée par un contrat commun.model.RechercheGlobale (Separated Interface), implémenté par recherche.model.ServiceRechercheGlobale qui agrège les autres features, et surfacé par le chrome (la barre de recherche de MainView) plutôt que par un écran dédié.

Les règles d'architecture sont des tests

Ces frontières ne sont pas qu'une convention : elles sont vérifiées automatiquement par ArchUnit. Casser une frontière fait échouer la CI.

Test Ce qu'il garantit
model_sans_javafx Aucun paquet ..model.. ne dépend de javafx.. (le métier reste réutilisable).
persistance_sans_javafx commun.persistence et ..model.dao.. ignorent JavaFX.
viewmodel_sans_javafx_ui Un ..viewmodel.. n'importe pas javafx.scene/fxml/stage (javafx.beans OK).
view_sans_jdbc Un ..view.. ne touche jamais ..model.dao.. ni java.sql.. : il passe par les VM/services.
features_sans_cycle Les slices fr.univ_amu.iut.(*) sont sans cycle (hors racine de composition commun.di).
pas_de_dependance_inter_feature_vers_la_vue Une feature ne dépend pas du view/viewmodel d'une autre feature (le socle commun est partagé).

Le chrome (fenêtre + zone centrale + fil d'Ariane) est porté par le socle commun.view :

  • Navigateur pilote la zone centrale et l'historique (pile d'écrans EtapeNavigation). Les écrans restent vivants au retour (état préservé).
  • Contrats optionnels qu'un écran peut implémenter sur son controller : GardeQuitter (confirmer la sortie si saisie non enregistrée), EmplacementNavigation (fil d'Ariane hiérarchique), RafraichirAuRetour (recharger ses données quand on y revient).

Comment une feature en ouvre une autre sans en dépendre (inversion de dépendance) : le socle publie un contrat Ouvrir* dans commun.view, la feature cible l'implémente via sa classe Navigation* (bindée par son module Guice), et la feature appelante l'obtient par injection.

// commun/view/OuvrirPassage.java — le CONTRAT (dans le socle)
public interface OuvrirPassage {
    void ouvrir(Long idPassage, ContexteSite contexte);
}

// passage/view/NavigationPassage.java — l'IMPLÉMENTATION (dans la feature passage)
@Singleton
public class NavigationPassage implements OuvrirPassage {
    @Override public void ouvrir(Long idPassage, ContexteSite contexte) {
        FXMLLoader loader = new FXMLLoader(NavigationPassage.class.getResource("Passage.fxml"));
        loader.setControllerFactory(injector::getInstance);          // controller injecté par Guice
        Parent vue = loader.load();
        ((PassageController) loader.getController()).ouvrirSur(idPassage, contexte);
        navigateur.empiler(vue, "passage", controleur.libelleFil(), controleur);
    }
}

Ainsi sites ouvre M-Passage sans dépendre de passage.view : la règle ArchUnit pas_de_dependance_inter_feature_vers_la_vue reste respectée. Tous les contrats existants : OuvrirSite, OuvrirPassage, OuvrirVerification, OuvrirImportation, OuvrirLot, OuvrirValidation, OuvrirDiagnostic.

Persistance

SQLite (fichier vigiechiro.db dans l'espace de travail), via des DAO écrits en PreparedStatement (pas d'ORM). Le schéma évolue par migrations versionnées src/main/resources/db/migration/V0x__*.sql, appliquées par MigrationSchema sur la SourceDeDonnees.

Le cœur du domaine est l'agrégat « nuit de capture » (feature passage), qui avance dans un workflow à états :

IMPORTE → TRANSFORME → VERIFIE → PRET_A_DEPOSER → DEPOSE

Le détail des entités, des 19 tables et de la correspondance avec le MCD du brief est sur Modèle de données et domaine ; le mécanisme d'accès (DAO, transactions, migrations) sur Persistance.

Injection de dépendances

RacineInjecteur assemble le socle + tous les modules */di/*Module.java des features. Les Controller FXML sont eux aussi injectés via une controllerFactory posée sur le FXMLLoader. Certaines valeurs transverses sont fournies par binding nommé (ex. @Named("idUtilisateurCourant"), l'utilisateur courant de l'application mono-utilisateur).


Pour mettre tout cela en pratique, suivez Ajouter une fonctionnalité.