Java Spring Angular — Révision Tech Lead
📖 0/0 notions
⚡0/0
Java & JVM
Fondamentaux, versions récentes, JVM, concurrence, Collections, Streams
📖 Cours complet — 81 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/81 maîtrisées

1Plateforme Java & versions23

JVM, JRE et JDKLa JVM exécute le bytecode ; le JRE = JVM + bibliothèques standard ; le JDK = JRE + outils de développement (javac, jshell, jdb…).

La JVM exécute le bytecode ; le JRE = JVM + bibliothèques standard ; le JDK = JRE + outils de développement (javac, jshell, jdb…).

La JVM (Java Virtual Machine) est un programme qui charge les fichiers .class, vérifie leur bytecode, gère la mémoire (heap, GC) et exécute le code. Le JRE (Java Runtime Environment) ajoute les bibliothèques standard (java.util, java.net…) : c'est ce qu'il faut pour *lancer* une application. Le JDK (Java Development Kit) ajoute le compilateur javac et les outils : c'est ce qu'il faut pour *développer*. Depuis Java 11, on ne distribue plus de JRE séparé : on télécharge un JDK (Temurin, Corretto, Oracle…).

bash
javac Hello.java      # le compilateur (JDK) produit Hello.class (bytecode)
java Hello            # la JVM charge et exécute le bytecode
java -version         # ex. openjdk version "21.0.4" 2024-07-16 LTS
🎯 En entretien« Write once, run anywhere » : c'est le bytecode qui est portable ; chaque OS a sa propre JVM.
Bytecode et compilation JITLe bytecode est le code intermédiaire produit par javac ; le JIT le compile en code machine pendant l'exécution pour les parties les plus utilisées.

Le bytecode est le code intermédiaire produit par javac ; le JIT le compile en code machine pendant l'exécution pour les parties les plus utilisées.

javac ne produit pas de code machine mais du bytecode, un jeu d'instructions indépendant du processeur. Au démarrage, la JVM l'interprète ; quand une méthode devient « chaude » (appelée très souvent), le compilateur JIT (Just-In-Time, C1 puis C2) la compile en code machine optimisé. C'est pour cela qu'une application Java est plus lente les premières secondes (« warm-up ») puis très rapide.

bash
javap -c Hello.class   # affiche le bytecode d'une classe
# 0: getstatic  #7  // Field java/lang/System.out
# 3: ldc        #13 // String Bonjour
# 5: invokevirtual  // Method println
🎯 En entretienL'AOT (GraalVM native image, ou le cache AOT de Java 24/25) sert justement à réduire ce temps de warm-up.
LTS (Long-Term Support)Version Java maintenue plusieurs années (correctifs de sécurité) ; aujourd'hui 8, 11, 17, 21 et 25. À privilégier en production.

Version Java maintenue plusieurs années (correctifs de sécurité) ; aujourd'hui 8, 11, 17, 21 et 25. À privilégier en production.

Une nouvelle version de Java sort tous les 6 mois (mars et septembre). Les versions intermédiaires ne sont supportées que 6 mois ; une LTS sort tous les 2 ans et reçoit des correctifs pendant des années. En entreprise (banque, assurance), on reste sur les LTS : 17 et 21 sont les plus déployées, 25 (septembre 2025) est la plus récente.

VersionAnnéeApport phare
82014Lambdas, Streams, Optional
112018var en lambda, HttpClient
172021Records, Sealed classes
212023Virtual Threads, pattern matching switch
252025Scoped Values, constructeurs flexibles
🎯 En entretienSpring Boot 3 et 4 exigent au minimum Java 17 : c'est la vraie « barre » moderne.
var (inférence de type locale)Mot-clé (Java 10) qui laisse le compilateur déduire le type d'une variable locale à partir de sa valeur. Le typage reste statique.

Mot-clé (Java 10) qui laisse le compilateur déduire le type d'une variable locale à partir de sa valeur. Le typage reste statique.

var ne rend pas Java dynamique : le type est fixé à la compilation, il est juste écrit par le compilateur à ta place. Utilisable uniquement pour les variables locales initialisées (pas pour les champs, paramètres ou retours).

java
var clients = new ArrayList<Client>();   // type = ArrayList<Client>
var total = BigDecimal.ZERO;              // type = BigDecimal
// clients = "texte";   ❌ erreur de compilation : le type reste ArrayList<Client>
for (var c : clients) { total = total.add(c.solde()); }
⚠️ PiègeÀ éviter quand le type n'est pas évident à la lecture : var x = service.process(); nuit à la lisibilité.
HttpClient (Java 11)Client HTTP standard du JDK (java.net.http), synchrone ou asynchrone, compatible HTTP/2.

Client HTTP standard du JDK (java.net.http), synchrone ou asynchrone, compatible HTTP/2.

Avant Java 11, il fallait HttpURLConnection (verbeux) ou une bibliothèque externe. HttpClient est immuable, réutilisable et thread-safe ; il supporte HTTP/2 et les appels asynchrones via CompletableFuture. Dans une application Spring, on utilise plutôt RestClient, qui peut d'ailleurs s'appuyer dessus.

java
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.exemple.sn/taux"))
        .header("Accept", "application/json").GET().build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());
Lambda et interface fonctionnelleUne lambda est une fonction anonyme (params) -> corps ; elle implémente une interface fonctionnelle, c'est-à-dire une interface à une seule méthode abstraite.

Une lambda est une fonction anonyme (params) -> corps ; elle implémente une interface fonctionnelle, c'est-à-dire une interface à une seule méthode abstraite.

Depuis Java 8, on peut passer du comportement comme une valeur. Une interface fonctionnelle possède exactement une méthode abstraite (ex. Runnable, Comparator<T>, Function<T,R>, Predicate<T>, Supplier<T>, Consumer<T>). L'annotation @FunctionalInterface fait vérifier cette règle par le compilateur.

java
@FunctionalInterface
interface RegleFrais { BigDecimal calculer(BigDecimal montant); }

RegleFrais fraisFixes = m -> new BigDecimal("500");
RegleFrais pourcent   = m -> m.multiply(new BigDecimal("0.01"));

Predicate<Client> majeur = c -> c.age() >= 18;
Function<Client, String> nom = c -> c.nom().toUpperCase();
🎯 En entretienLes variables locales utilisées dans une lambda doivent être « effectivement finales » (jamais réassignées).
Référence de méthodeRaccourci Classe::methode pour une lambda qui ne fait qu'appeler une méthode existante.

Raccourci Classe::methode pour une lambda qui ne fait qu'appeler une méthode existante.

Quatre formes : méthode statique (Integer::parseInt), méthode d'instance d'un objet donné (System.out::println), méthode d'instance du paramètre (Order::getCustomerName), constructeur (ArrayList::new).

java
orders.stream().map(Order::getCustomerName)     // = o -> o.getCustomerName()
      .forEach(System.out::println);            // = s -> System.out.println(s)
Supplier<List<String>> fabrique = ArrayList::new; // = () -> new ArrayList<>()
OptionalConteneur (Java 8) qui contient une valeur ou rien ; il rend explicite qu'un résultat peut être absent et évite les NullPointerException.

Conteneur (Java 8) qui contient une valeur ou rien ; il rend explicite qu'un résultat peut être absent et évite les NullPointerException.

Optional<T> sert surtout comme type de retour (ex. findById d'un repository). On le consomme avec map, orElse, orElseThrow, ifPresent plutôt qu'avec get().

java
Optional<Client> c = repo.findById(id);
String email = c.map(Client::email).orElse("inconnu");
Client client = repo.findById(id)
        .orElseThrow(() -> new NotFoundException("Client " + id + " introuvable"));
⚠️ PiègeNe pas utiliser Optional pour des champs d'entité ou des paramètres de méthode, et ne jamais appeler get() sans vérifier : on retombe sur le problème du null.
Méthode default (interface)Méthode avec un corps dans une interface (Java 8) : elle permet d'ajouter un comportement sans casser les classes qui implémentent déjà l'interface.

Méthode avec un corps dans une interface (Java 8) : elle permet d'ajouter un comportement sans casser les classes qui implémentent déjà l'interface.

Avant Java 8, ajouter une méthode à une interface obligeait à modifier toutes ses implémentations. Une méthode default fournit une implémentation par défaut, que les classes peuvent redéfinir. Les interfaces peuvent aussi avoir des méthodes static et (Java 9) private.

java
interface Notifiable {
    void envoyer(String message);
    default void envoyerUrgent(String message) {   // implémentation fournie
        envoyer("[URGENT] " + message);
    }
}
RecordClasse de données immuable (Java 16, LTS 17) : le compilateur génère constructeur, accesseurs, equals, hashCode et toString.

Classe de données immuable (Java 16, LTS 17) : le compilateur génère constructeur, accesseurs, equals, hashCode et toString.

Un record déclare ses composants dans l'en-tête ; tous les champs sont private final. Idéal pour les DTO, les messages Kafka, les clés composées, les value objects. On peut ajouter un constructeur compact pour valider les données.

java
public record VirementDto(String ibanSource, String ibanDest, BigDecimal montant) {
    public VirementDto {                              // constructeur compact
        if (montant.signum() <= 0) throw new IllegalArgumentException("montant > 0 requis");
    }
}
var v = new VirementDto("SN01…", "SN02…", new BigDecimal("15000"));
v.montant();          // accesseur généré (pas getMontant())
⚠️ PiègeUn record ne peut pas être une entité JPA (il faut un constructeur sans argument et des champs mutables) ; il sert pour les DTO et projections.
Sealed class / interfaceHiérarchie fermée (Java 17) : sealed ... permits A, B liste exhaustivement les seuls sous-types autorisés.

Hiérarchie fermée (Java 17) : sealed ... permits A, B liste exhaustivement les seuls sous-types autorisés.

Une classe ou interface sealed contrôle qui peut l'étendre. Chaque sous-type doit être final, sealed ou non-sealed. Avantage majeur : le compilateur connaît tous les cas, donc un switch sur ce type peut être exhaustif sans default.

java
public sealed interface ResultatPaiement permits Accepte, Refuse, EnAttente {}
public record Accepte(String reference) implements ResultatPaiement {}
public record Refuse(String motif)     implements ResultatPaiement {}
public record EnAttente()              implements ResultatPaiement {}
🎯 En entretienSi on ajoute un 4e sous-type, tous les switch non exhaustifs ne compilent plus : le compilateur te montre chaque endroit à adapter.
Pattern matching pour instanceofJava 16 : if (obj instanceof Client c) teste le type et déclare directement la variable typée, sans cast manuel.

Java 16 : if (obj instanceof Client c) teste le type et déclare directement la variable typée, sans cast manuel.

java
// Avant
if (obj instanceof Client) { Client c = (Client) obj; System.out.println(c.nom()); }
// Depuis Java 16
if (obj instanceof Client c && c.estActif()) { System.out.println(c.nom()); }
Pattern matching pour switch et Record PatternsJava 21 : un switch peut tester le type d'un objet et déstructurer un record (case Cercle(var r) ->), avec contrôle d'exhaustivité.

Java 21 : un switch peut tester le type d'un objet et déstructurer un record (case Cercle(var r) ->), avec contrôle d'exhaustivité.

Combiné aux sealed interfaces, c'est le remplaçant moderne des chaînes de if/else instanceof et du pattern Visitor. Les record patterns extraient directement les composants d'un record. Les gardes when ajoutent une condition.

java
String message = switch (resultat) {
    case Accepte(var ref)                      -> "Paiement OK, réf. " + ref;
    case Refuse(var motif) when motif.isBlank() -> "Refusé";
    case Refuse(var motif)                     -> "Refusé : " + motif;
    case EnAttente e                           -> "En attente de validation";
};   // pas de default : le compilateur sait que tous les cas sont couverts
Sequenced Collections (Java 21)Nouvelles interfaces qui donnent à toutes les collections ordonnées des méthodes communes : getFirst(), getLast(), addFirst(), reversed().

Nouvelles interfaces qui donnent à toutes les collections ordonnées des méthodes communes : getFirst(), getLast(), addFirst(), reversed().

Avant Java 21, récupérer le dernier élément dépendait du type (list.get(list.size()-1), deque.getLast(), sortedSet.last()…). SequencedCollection, SequencedSet et SequencedMap uniformisent cela.

java
List<String> etapes = new ArrayList<>(List.of("créé", "validé", "payé"));
etapes.getFirst();      // "créé"
etapes.getLast();       // "payé"
etapes.reversed();      // vue inversée, sans copie
LinkedHashMap<String, Integer> m = new LinkedHashMap<>();
m.putFirst("prioritaire", 1);
Virtual Threads (Java 21)Threads ultra-légers gérés par la JVM (et non par l'OS) : on peut en créer des millions ; parfaits pour les traitements qui attendent des I/O.

Threads ultra-légers gérés par la JVM (et non par l'OS) : on peut en créer des millions ; parfaits pour les traitements qui attendent des I/O.

Un thread classique (« platform thread ») correspond à un thread de l'OS : il coûte ~1 Mo de pile et on n'en crée que quelques milliers. Un virtual thread est monté sur un thread porteur uniquement quand il calcule ; dès qu'il attend (appel HTTP, requête SQL), la JVM le démonte et libère le porteur. On garde un code bloquant simple tout en ayant la scalabilité du code réactif.

java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10_000; i++) {
        int id = i;
        executor.submit(() -> appelerApiExterne(id));   // 10 000 appels concurrents
    }
}   // close() attend la fin de toutes les tâches
Thread.ofVirtual().start(() -> System.out.println("léger !"));
properties
# Spring Boot 3.2+ : Tomcat et @Async utilisent des virtual threads
spring.threads.virtual.enabled=true
🎯 En entretienGain sur l'I/O-bound uniquement ; pour du calcul CPU pur, le nombre de cœurs reste la limite.
⚠️ PiègeNe pas les mettre en pool (ils sont jetables) et éviter les longs blocs synchronized qui « épinglent » le thread porteur (corrigé en grande partie depuis Java 24).
Flexible Constructor Bodies (JEP 513, Java 25)Java 25 autorise des instructions (validation, calculs) avant l'appel à super(...) ou this(...) dans un constructeur.

Java 25 autorise des instructions (validation, calculs) avant l'appel à super(...) ou this(...) dans un constructeur.

Avant, super(...) devait être la toute première instruction, ce qui obligeait à valider les arguments *après* la construction du parent, ou via des méthodes statiques. Désormais on peut valider d'abord, à condition de ne pas utiliser this avant super.

java
public class CompteEpargne extends Compte {
    public CompteEpargne(BigDecimal soldeInitial) {
        if (soldeInitial.signum() < 0)
            throw new IllegalArgumentException("Solde négatif");   // ✅ avant super() en Java 25
        super(soldeInitial);
    }
}
Scoped Values (JEP 506, Java 25)Valeur immuable partagée avec tout le code appelé dans une portée (et ses threads enfants) ; alternative moderne et sûre à ThreadLocal, adaptée aux virtual threads.

Valeur immuable partagée avec tout le code appelé dans une portée (et ses threads enfants) ; alternative moderne et sûre à ThreadLocal, adaptée aux virtual threads.

ThreadLocal est mutable, doit être nettoyé à la main et coûte cher avec des millions de virtual threads. Une ScopedValue est liée pour la durée d'un appel puis disparaît automatiquement. Usage typique : propager l'utilisateur courant ou un identifiant de corrélation.

java
static final ScopedValue<String> UTILISATEUR = ScopedValue.newInstance();

ScopedValue.where(UTILISATEUR, "abdoulaye").run(() -> traiterRequete());

void traiterRequete() {
    log.info("Action de {}", UTILISATEUR.get());   // lisible partout dans la portée
}
Compact source files et main d'instance (JEP 512, Java 25)Java 25 permet d'écrire un programme sans déclarer de classe et avec un simple void main(), idéal pour les scripts et l'apprentissage.

Java 25 permet d'écrire un programme sans déclarer de classe et avec un simple void main(), idéal pour les scripts et l'apprentissage.

java
// Fichier Hello.java — lancé avec : java Hello.java
void main() {
    IO.println("Bonjour Dakar !");   // classe utilitaire java.lang.IO (Java 25)
}
Module Import Declarations (JEP 511, Java 25)import module java.base; importe d'un coup tous les packages exportés par un module.

import module java.base; importe d'un coup tous les packages exportés par un module.

Pratique pour les scripts et prototypes : une seule ligne remplace des dizaines d'imports (java.util.*, java.io.*…). En code de production, les imports explicites restent plus lisibles.

java
import module java.base;   // List, Map, Path, Files, Stream… tous disponibles
Compact Object Headers (JEP 519, Java 25)Option de la JVM (finale en Java 25) qui réduit l'en-tête de chaque objet de 12 à 8 octets : moins de mémoire et meilleure utilisation du cache CPU pour les applications qui créent beaucoup de petits objets.

Option de la JVM (finale en Java 25) qui réduit l'en-tête de chaque objet de 12 à 8 octets : moins de mémoire et meilleure utilisation du cache CPU pour les applications qui créent beaucoup de petits objets.

Chaque objet Java porte un en-tête (verrou, hash, classe). En le compactant, le heap occupé baisse souvent de 10 à 20 %. Ce n'est pas activé par défaut en Java 25.

bash
java -XX:+UseCompactObjectHeaders -jar app.jar
Primitive Patterns (JEP 507, preview en Java 25)Fonctionnalité en preview : autorise les types primitifs (int, double…) dans les patterns de instanceof et switch.

Fonctionnalité en preview : autorise les types primitifs (int, double…) dans les patterns de instanceof et switch.

« Preview » signifie que la fonctionnalité est livrée pour retour d'expérience : il faut --enable-preview et l'API peut encore changer. À citer comme tendance, pas à utiliser en production.

java
// javac --release 25 --enable-preview
String categorie = switch (codeHttp) {
    case int c when c >= 500 -> "Erreur serveur";
    case int c when c >= 400 -> "Erreur client";
    case int c               -> "OK";
};
Structured Concurrency (preview)API en preview (JEP 505 en Java 25) qui traite un groupe de tâches concurrentes comme une seule unité : si une échoue, les autres sont annulées.

API en preview (JEP 505 en Java 25) qui traite un groupe de tâches concurrentes comme une seule unité : si une échoue, les autres sont annulées.

Avec un ExecutorService classique, si une sous-tâche échoue, les autres continuent et peuvent fuir. StructuredTaskScope garantit que les sous-tâches ne survivent pas à leur portée. Encore en preview en Java 25 : bon sujet de culture, pas pour la production.

java
try (var scope = StructuredTaskScope.open()) {          // API preview Java 25
    var client = scope.fork(() -> clientService.charger(id));
    var comptes = scope.fork(() -> compteService.lister(id));
    scope.join();                                      // attend les deux ou échoue
    return new Synthese(client.get(), comptes.get());
}
Text blocksChaînes multilignes délimitées par """ (Java 15) : idéales pour du JSON, du SQL ou du HTML dans le code.

Chaînes multilignes délimitées par """ (Java 15) : idéales pour du JSON, du SQL ou du HTML dans le code.

java
String sql = """
        SELECT c.id, c.nom
        FROM client c
        WHERE c.statut = 'ACTIF'
        """;

2Programmation orientée objet16

Classe et objetUne classe est un modèle (attributs + méthodes) ; un objet est une instance concrète de ce modèle, créée avec new et stockée dans le heap.

Une classe est un modèle (attributs + méthodes) ; un objet est une instance concrète de ce modèle, créée avec new et stockée dans le heap.

java
public class Compte {                 // la classe : le plan
    private BigDecimal solde = BigDecimal.ZERO;
    public void crediter(BigDecimal m) { solde = solde.add(m); }
}
Compte c1 = new Compte();             // un objet (instance)
Compte c2 = new Compte();             // un autre objet, état indépendant
EncapsulationCacher l'état interne d'un objet (champs private) et n'exposer que des méthodes qui garantissent ses règles (invariants).

Cacher l'état interne d'un objet (champs private) et n'exposer que des méthodes qui garantissent ses règles (invariants).

L'encapsulation protège la cohérence : personne ne peut mettre un solde négatif « à la main » si la seule porte d'entrée est une méthode qui vérifie la règle. Ce n'est pas « des getters/setters partout » : un setter public sur chaque champ casse l'encapsulation.

java
public class Compte {
    private BigDecimal solde = BigDecimal.ZERO;        // état caché
    public void debiter(BigDecimal montant) {
        if (montant.compareTo(solde) > 0) throw new SoldeInsuffisantException();
        solde = solde.subtract(montant);               // l'invariant solde >= 0 est garanti
    }
    public BigDecimal solde() { return solde; }        // lecture seule
}
🎯 En entretienPhrase d'entretien : « l'encapsulation, c'est protéger les invariants métier, pas générer des setters ».
HéritageUne classe fille (extends) réutilise et spécialise le code de sa classe mère ; super appelle le constructeur ou les méthodes du parent.

Une classe fille (extends) réutilise et spécialise le code de sa classe mère ; super appelle le constructeur ou les méthodes du parent.

L'héritage exprime une relation « est un » (un CompteEpargne *est un* Compte). Java n'autorise qu'un seul parent de classe. Utilisé avec excès, il crée des hiérarchies fragiles : une modification du parent casse les enfants.

java
public class Compte {
    protected BigDecimal solde;
    public Compte(BigDecimal solde) { this.solde = solde; }
    public BigDecimal frais() { return new BigDecimal("1000"); }
}
public class CompteEpargne extends Compte {
    public CompteEpargne(BigDecimal s) { super(s); }
    @Override public BigDecimal frais() { return BigDecimal.ZERO; }  // spécialisation
}
⚠️ PiègeLimiter la profondeur à 2-3 niveaux et préférer la Composition quand la relation est « a un » ou « utilise ».
CompositionConstruire un objet en y plaçant d'autres objets (relation « a un ») au lieu d'hériter ; plus souple et plus testable que l'héritage.

Construire un objet en y plaçant d'autres objets (relation « a un ») au lieu d'hériter ; plus souple et plus testable que l'héritage.

Avec la composition, on assemble des comportements via des champs (souvent des interfaces injectées). On peut changer une partie sans toucher aux autres et la remplacer par un mock en test. C'est le principe « favor composition over inheritance » du livre Design Patterns.

java
public class ServiceVirement {
    private final CalculFrais calculFrais;      // "a un" calculateur de frais
    private final Notificateur notificateur;    // "a un" notificateur
    public ServiceVirement(CalculFrais f, Notificateur n) { this.calculFrais = f; this.notificateur = n; }
}
PolymorphismeCapacité d'un même appel à produire un comportement différent selon le type réel de l'objet (résolu à l'exécution).

Capacité d'un même appel à produire un comportement différent selon le type réel de l'objet (résolu à l'exécution).

On manipule des objets via un type commun (interface ou classe mère), et c'est l'implémentation concrète qui s'exécute. C'est ce qui permet d'ajouter un nouveau cas sans modifier le code appelant (principe Open/Closed).

java
List<MoyenPaiement> moyens = List.of(new OrangeMoney(), new Wave(), new CarteBancaire());
for (MoyenPaiement m : moyens) {
    m.payer(montant);   // chaque classe exécute SA version de payer()
}
Override vs OverloadOverride (redéfinition) = même signature dans une sous-classe, choisie à l'exécution. Overload (surcharge) = même nom, paramètres différents, choisie à la compilation.

Override (redéfinition) = même signature dans une sous-classe, choisie à l'exécution. Overload (surcharge) = même nom, paramètres différents, choisie à la compilation.

OverrideOverload
OùClasse filleMême classe
SignatureIdentiqueParamètres différents
RésolutionExécution (dynamique)Compilation (statique)
java
class Compte { BigDecimal frais() { return BigDecimal.TEN; } }
class Premium extends Compte {
    @Override BigDecimal frais() { return BigDecimal.ZERO; }      // override
}
class Calcul {
    int somme(int a, int b) { return a + b; }                     // overload
    BigDecimal somme(BigDecimal a, BigDecimal b) { return a.add(b); }
}
🎯 En entretienToujours mettre @Override : le compilateur signale une erreur si la signature ne correspond pas (faute de frappe, mauvais type).
AbstractionNe montrer que l'essentiel (le « quoi ») et cacher les détails d'implémentation (le « comment »), via interfaces et classes abstraites.

Ne montrer que l'essentiel (le « quoi ») et cacher les détails d'implémentation (le « comment »), via interfaces et classes abstraites.

Le code appelant dépend d'un contrat (PaymentGateway.charger()) sans savoir s'il parle à Wave, Orange Money ou à un mock. Cela réduit le couplage et permet de changer d'implémentation sans impact.

java
public interface PaymentGateway {                  // le "quoi"
    Confirmation charger(String telephone, BigDecimal montant);
}
@Service class WaveGateway implements PaymentGateway { /* le "comment" : appels HTTP Wave */ }
InterfaceContrat qui déclare des méthodes à implémenter ; une classe peut implémenter plusieurs interfaces. Peut contenir des méthodes default, static et private.

Contrat qui déclare des méthodes à implémenter ; une classe peut implémenter plusieurs interfaces. Peut contenir des méthodes default, static et private.

Une interface n'a pas d'état d'instance (seulement des constantes public static final). Elle définit un comportement attendu, ce qui permet le polymorphisme et l'injection de dépendances (Spring injecte l'implémentation).

java
public interface Auditable { String auteur(); Instant dateModification(); }
public interface Exportable { byte[] versPdf(); }
public class Contrat implements Auditable, Exportable { /* ... */ }  // multi-implémentation
Classe abstraiteClasse non instanciable (abstract) qui partage état et code entre sous-classes et impose certaines méthodes à implémenter.

Classe non instanciable (abstract) qui partage état et code entre sous-classes et impose certaines méthodes à implémenter.

On la choisit quand plusieurs classes ont un vrai socle commun (champs, constructeur, logique partielle). Pattern typique : Template Method, où la classe abstraite fixe l'algorithme et délègue certaines étapes.

java
public abstract class Rapport {
    protected final LocalDate date = LocalDate.now();     // état partagé
    public final String generer() {                        // algorithme fixé
        return entete() + corps() + "\nGénéré le " + date;
    }
    protected String entete() { return "== RAPPORT ==\n"; }
    protected abstract String corps();                     // étape imposée
}
equals() et hashCode()equals() définit l'égalité logique de deux objets ; hashCode() donne un entier utilisé par les HashMap/HashSet. Contrat : deux objets égaux doivent avoir le même hashCode.

equals() définit l'égalité logique de deux objets ; hashCode() donne un entier utilisé par les HashMap/HashSet. Contrat : deux objets égaux doivent avoir le même hashCode.

Si on redéfinit equals() sans hashCode(), un objet ajouté dans un HashSet peut devenir « introuvable » : la recherche va dans le mauvais bucket. Les records génèrent les deux automatiquement.

java
public final class Iban {
    private final String valeur;
    public Iban(String v) { this.valeur = v.replace(" ", ""); }
    @Override public boolean equals(Object o) {
        return o instanceof Iban other && valeur.equals(other.valeur);
    }
    @Override public int hashCode() { return valeur.hashCode(); }
}
⚠️ PiègeNe jamais baser hashCode sur un champ mutable : si le champ change, l'objet est perdu dans la HashMap.
== vs equals()== compare les références (même objet en mémoire ?) ; equals() compare le contenu (même valeur logique ?).

== compare les références (même objet en mémoire ?) ; equals() compare le contenu (même valeur logique ?).

java
String a = "OK";
String b = "OK";
String c = new String("OK");
a == b;          // true  : même littéral, partagé dans le String Pool
a == c;          // false : c est un autre objet
a.equals(c);     // true  : même contenu
Integer x = 127, y = 127;   x == y;   // true  (cache -128..127)
Integer p = 128, q = 128;   p == q;   // false (deux objets) → toujours equals()
⚠️ PiègeLe cache des Integer entre -128 et 127 fait passer certains tests avec == puis échouer en production : toujours equals() pour les objets.
ImmuabilitéUn objet immuable ne change plus après sa création ; toute « modification » crée un nouvel objet. Il est thread-safe par nature.

Un objet immuable ne change plus après sa création ; toute « modification » crée un nouvel objet. Il est thread-safe par nature.

Recette : classe final, champs private final, pas de setter, copies défensives des collections reçues. Exemples du JDK : String, BigDecimal, LocalDate, List.of(...). Les records sont immuables (en surface).

java
public final class Montant {
    private final BigDecimal valeur;
    private final String devise;
    public Montant(BigDecimal v, String d) { this.valeur = v; this.devise = d; }
    public Montant ajouter(Montant autre) {             // retourne un NOUVEL objet
        return new Montant(valeur.add(autre.valeur), devise);
    }
}
String PoolZone de la JVM où les littéraux String sont stockés une seule fois et partagés, ce qui économise la mémoire. Possible uniquement parce que String est immuable.

Zone de la JVM où les littéraux String sont stockés une seule fois et partagés, ce qui économise la mémoire. Possible uniquement parce que String est immuable.

Quand on écrit "XOF" à plusieurs endroits, tous pointent vers le même objet. new String("XOF") force la création d'un nouvel objet hors du pool. intern() permet d'y placer une chaîne manuellement.

java
String d1 = "XOF";
String d2 = "XOF";
System.out.println(d1 == d2);                       // true : même objet du pool
System.out.println(new String("XOF") == d1);        // false
StringBuilder vs StringBufferDeux classes de chaîne mutable : StringBuilder (rapide, non thread-safe) est le choix par défaut ; StringBuffer (synchronized) est historique.

Deux classes de chaîne mutable : StringBuilder (rapide, non thread-safe) est le choix par défaut ; StringBuffer (synchronized) est historique.

Comme String est immuable, s += x dans une boucle crée un nouvel objet à chaque tour (coût O(n²)). StringBuilder modifie un tampon interne (O(n)).

java
var sb = new StringBuilder();
for (Transaction t : transactions) {
    sb.append(t.reference()).append(';').append(t.montant()).append('\n');
}
String csv = sb.toString();
🎯 En entretienLe compilateur optimise déjà a + b + c sur une seule ligne ; le problème n'existe que dans les boucles.
finalMot-clé : variable non réassignable, méthode non redéfinissable, classe non héritable.

Mot-clé : variable non réassignable, méthode non redéfinissable, classe non héritable.

final sur un champ ne rend pas l'objet référencé immuable : une final List peut toujours recevoir des éléments. En Spring, les dépendances injectées par constructeur sont déclarées final.

java
private final OrderRepo repo;        // assignée une seule fois (constructeur)
public final String generer() {...}  // ne peut pas être @Override
public final class Iban {...}        // ne peut pas être étendue
final List<String> l = new ArrayList<>(); l.add("ok");  // ✅ autorisé : la référence est finale, pas le contenu
staticMembre qui appartient à la classe et non à une instance : partagé par tous les objets, accessible sans new.

Membre qui appartient à la classe et non à une instance : partagé par tous les objets, accessible sans new.

Utilisé pour les constantes (static final), les méthodes utilitaires et les fabriques. Un champ static mutable est un état global partagé entre threads : source fréquente de bugs de concurrence.

java
public final class Devises {
    public static final String XOF = "XOF";                 // constante
    public static BigDecimal arrondir(BigDecimal m) {        // utilitaire
        return m.setScale(0, RoundingMode.HALF_UP);
    }
}
Devises.arrondir(new BigDecimal("1250.6"));                 // 1251

3Collections10

Complexité algorithmique (notation Big O)Mesure de l'évolution du temps d'un algorithme quand la taille n des données augmente : O(1) constant, O(log n) logarithmique, O(n) linéaire, O(n²) quadratique.

Mesure de l'évolution du temps d'un algorithme quand la taille n des données augmente : O(1) constant, O(log n) logarithmique, O(n) linéaire, O(n²) quadratique.

On s'intéresse au pire cas ou au cas moyen pour de grandes valeurs de n. Amorti signifie « en moyenne sur une série d'opérations » : un ArrayList.add est O(1) amorti car le redimensionnement (O(n)) est rare.

NotationExemple1 000 éléments1 000 000 éléments
O(1)HashMap.get1 étape1 étape
O(log n)TreeMap.get~10~20
O(n)parcours de liste1 0001 000 000
O(n²)double boucle1 000 00010¹²
ArrayListListe basée sur un tableau redimensionnable : accès par index en O(1), ajout en fin en O(1) amorti, insertion/suppression au début en O(n).

Liste basée sur un tableau redimensionnable : accès par index en O(1), ajout en fin en O(1) amorti, insertion/suppression au début en O(n).

C'est la liste par défaut dans 95 % des cas. Quand le tableau interne est plein, il est recopié dans un tableau 1,5 fois plus grand.

java
List<String> statuts = new ArrayList<>(100);   // capacité initiale si la taille est connue
statuts.add("CREE");
statuts.get(0);          // O(1)
statuts.add(0, "BROUILLON");   // O(n) : décale tous les éléments
LinkedListListe doublement chaînée : insertion/suppression O(1) aux extrémités, mais accès par index en O(n). Rarement le bon choix en pratique.

Liste doublement chaînée : insertion/suppression O(1) aux extrémités, mais accès par index en O(n). Rarement le bon choix en pratique.

Chaque élément est un nœud avec des pointeurs vers le précédent et le suivant : beaucoup d'objets, mauvaise localité mémoire. Pour une file ou une pile, ArrayDeque est presque toujours plus rapide.

java
LinkedList<String> l = new LinkedList<>();
l.addFirst("a"); l.addLast("b");   // O(1)
l.get(5000);                       // O(n) : parcourt les nœuds un par un
HashMapTable de hachage clé → valeur : get/put en O(1) amorti grâce à hashCode() qui désigne directement le « bucket ». Non ordonnée, non thread-safe.

Table de hachage clé → valeur : get/put en O(1) amorti grâce à hashCode() qui désigne directement le « bucket ». Non ordonnée, non thread-safe.

La clé passe par hashCode() pour choisir un bucket, puis equals() départage les collisions. Depuis Java 8, un bucket qui contient plus de 8 éléments devient un arbre rouge-noir (pire cas O(log n) au lieu de O(n)). Capacité doublée quand le facteur de charge 0,75 est dépassé.

java
Map<String, BigDecimal> soldes = new HashMap<>();
soldes.put("SN-001", new BigDecimal("250000"));
soldes.getOrDefault("SN-999", BigDecimal.ZERO);
soldes.merge("SN-001", new BigDecimal("5000"), BigDecimal::add);  // incrément atomique logique
⚠️ PiègeEn multithread, utiliser ConcurrentHashMap : une HashMap modifiée par plusieurs threads peut perdre des données.
TreeMapMap triée par clé (arbre rouge-noir) : opérations en O(log n), parcours dans l'ordre et requêtes par plage (avant/après une clé).

Map triée par clé (arbre rouge-noir) : opérations en O(log n), parcours dans l'ordre et requêtes par plage (avant/après une clé).

java
TreeMap<LocalDate, BigDecimal> historique = new TreeMap<>();
historique.put(LocalDate.of(2026, 1, 1), new BigDecimal("1000"));
historique.put(LocalDate.of(2026, 3, 1), new BigDecimal("1500"));
historique.floorEntry(LocalDate.of(2026, 2, 15));   // dernière valeur connue au 15/02
historique.subMap(debut, true, fin, true);          // plage de dates
HashSetEnsemble sans doublons basé sur une HashMap : add/contains en O(1) amorti, sans ordre garanti.

Ensemble sans doublons basé sur une HashMap : add/contains en O(1) amorti, sans ordre garanti.

java
Set<String> ibansVus = new HashSet<>();
if (!ibansVus.add(iban)) {            // add() renvoie false si déjà présent
    log.warn("IBAN en double : {}", iban);
}
🎯 En entretienBesoin de l'ordre d'insertion → LinkedHashSet ; besoin d'un tri → TreeSet.
PriorityQueueFile à priorité (tas binaire) : l'élément le plus prioritaire est toujours en tête ; lecture O(1), ajout/retrait O(log n).

File à priorité (tas binaire) : l'élément le plus prioritaire est toujours en tête ; lecture O(1), ajout/retrait O(log n).

java
PriorityQueue<Tache> taches = new PriorityQueue<>(Comparator.comparing(Tache::echeance));
taches.add(new Tache("relance", LocalDate.now().plusDays(3)));
taches.add(new Tache("clôture", LocalDate.now()));
taches.poll();   // "clôture" : l'échéance la plus proche sort en premier
ArrayDequeFile à double extrémité basée sur un tableau circulaire : ajout/retrait O(1) aux deux bouts. Le meilleur choix pour une pile ou une file.

File à double extrémité basée sur un tableau circulaire : ajout/retrait O(1) aux deux bouts. Le meilleur choix pour une pile ou une file.

java
Deque<String> pile = new ArrayDeque<>();
pile.push("A"); pile.push("B");
pile.pop();                    // "B" (LIFO) — remplace la vieille classe Stack
Deque<String> file = new ArrayDeque<>();
file.offer("1"); file.offer("2");
file.poll();                   // "1" (FIFO)
Collections immuables (List.of, Map.of)List.of(), Set.of(), Map.of() (Java 9) créent des collections non modifiables : toute tentative d'ajout lève UnsupportedOperationException.

List.of(), Set.of(), Map.of() (Java 9) créent des collections non modifiables : toute tentative d'ajout lève UnsupportedOperationException.

java
List<String> devises = List.of("XOF", "EUR", "USD");
devises.add("GBP");                       // ❌ UnsupportedOperationException
List<String> copie = List.copyOf(source); // copie défensive immuable
⚠️ PiègeCes collections refusent null : List.of("a", null) lève une NullPointerException.
Comparable vs ComparatorComparable définit l'ordre « naturel » dans la classe elle-même (compareTo) ; Comparator définit un ordre externe, à la demande.

Comparable définit l'ordre « naturel » dans la classe elle-même (compareTo) ; Comparator définit un ordre externe, à la demande.

java
record Client(String nom, int age) implements Comparable<Client> {
    public int compareTo(Client o) { return nom.compareTo(o.nom); }   // ordre naturel : par nom
}
clients.sort(Comparator.comparing(Client::age).reversed()
                       .thenComparing(Client::nom));                  // ordre ad hoc

4Streams6

Stream APIPipeline déclaratif (Java 8) pour transformer des collections : source → opérations intermédiaires → opération terminale. Ne modifie pas la source.

Pipeline déclaratif (Java 8) pour transformer des collections : source → opérations intermédiaires → opération terminale. Ne modifie pas la source.

Un stream décrit *ce qu'on veut* (filtrer, transformer, regrouper) plutôt que *comment* le faire avec des boucles. Un stream ne se consomme qu'une seule fois.

java
BigDecimal totalActifs = comptes.stream()
        .filter(Compte::estActif)          // intermédiaire
        .map(Compte::solde)                // intermédiaire
        .reduce(BigDecimal.ZERO, BigDecimal::add);   // terminale
Opérations intermédiaires vs terminales (évaluation paresseuse)Les opérations intermédiaires (filter, map, sorted…) ne font rien tant qu'une opération terminale (collect, forEach, count…) n'est pas appelée.

Les opérations intermédiaires (filter, map, sorted…) ne font rien tant qu'une opération terminale (collect, forEach, count…) n'est pas appelée.

Le stream est paresseux : il traite les éléments un par un à travers toute la chaîne et s'arrête dès que possible (limit, findFirst, anyMatch court-circuitent).

java
Stream<String> s = noms.stream().filter(n -> { System.out.println("test " + n); return n.length() > 3; });
// rien n'est affiché ici : aucune opération terminale
s.findFirst();   // maintenant seulement, et on s'arrête au premier trouvé
IntermédiairesTerminales
filter, map, flatMap, distinct, sorted, limit, skip, peekcollect, toList, forEach, reduce, count, findFirst, anyMatch, min, max
filter, map et flatMapfilter garde les éléments qui satisfont une condition ; map transforme chaque élément ; flatMap transforme puis « aplatit » des sous-listes en un seul flux.

filter garde les éléments qui satisfont une condition ; map transforme chaque élément ; flatMap transforme puis « aplatit » des sous-listes en un seul flux.

java
List<String> emails = clients.stream()
        .filter(c -> c.ville().equals("Dakar"))   // garde
        .map(Client::email)                       // transforme Client → String
        .toList();
List<Transaction> toutes = comptes.stream()
        .flatMap(c -> c.transactions().stream())  // List<List<T>> → Stream<T>
        .toList();
collect et Collectorscollect() termine un stream en construisant un résultat ; la classe Collectors fournit les recettes : toList, toMap, groupingBy, counting, joining…

collect() termine un stream en construisant un résultat ; la classe Collectors fournit les recettes : toList, toMap, groupingBy, counting, joining…

java
Map<Statut, List<Order>> parStatut = orders.stream()
        .collect(Collectors.groupingBy(Order::getStatus));

Map<Statut, Long> compte = orders.stream()
        .collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));

Map<String, Client> parId = clients.stream()
        .collect(Collectors.toMap(Client::id, c -> c));   // ⚠️ IllegalStateException si id en double

String liste = noms.stream().collect(Collectors.joining(", ", "[", "]"));
toList() vs Collectors.toList()stream.toList() (Java 16) renvoie une liste non modifiable ; collect(Collectors.toList()) renvoie en pratique une ArrayList modifiable.

stream.toList() (Java 16) renvoie une liste non modifiable ; collect(Collectors.toList()) renvoie en pratique une ArrayList modifiable.

java
List<String> a = noms.stream().toList();
a.add("x");                                   // ❌ UnsupportedOperationException
List<String> b = noms.stream().collect(Collectors.toList());
b.add("x");                                   // ✅ fonctionne (ArrayList)
🎯 En entretienPréférer toList() : plus court et l'immutabilité évite des effets de bord.
Parallel streamsparallelStream() découpe le travail sur le ForkJoinPool commun ; utile seulement pour de gros calculs CPU, sans I/O ni état partagé.

parallelStream() découpe le travail sur le ForkJoinPool commun ; utile seulement pour de gros calculs CPU, sans I/O ni état partagé.

java
long nb = grandeListe.parallelStream().filter(this::calculLourd).count();
⚠️ PiègeDans une application web, éviter : tous les parallel streams partagent le même pool, un appel lent bloque les autres requêtes. Jamais d'I/O (base, HTTP) dans un parallel stream.

5Exceptions6

Hiérarchie Throwable : Error vs ExceptionTout ce qui peut être levé hérite de Throwable : Error = problème grave de la JVM (à ne pas attraper) ; Exception = problème applicatif (checked ou unchecked).

Tout ce qui peut être levé hérite de Throwable : Error = problème grave de la JVM (à ne pas attraper) ; Exception = problème applicatif (checked ou unchecked).

text
Throwable
├── Error                 (OutOfMemoryError, StackOverflowError) → ne pas catcher
└── Exception             (checked : IOException, SQLException…)
    └── RuntimeException  (unchecked : NullPointerException, IllegalArgumentException…)
Exception checkedException que le compilateur oblige à traiter (try/catch) ou à déclarer (throws). Hérite d'Exception mais pas de RuntimeException.

Exception que le compilateur oblige à traiter (try/catch) ou à déclarer (throws). Hérite d'Exception mais pas de RuntimeException.

Elle représente une situation externe prévisible dont l'appelant peut se remettre (fichier absent, réseau coupé). Exemples : IOException, SQLException.

java
public String lire(Path p) throws IOException {   // déclarée dans la signature
    return Files.readString(p);
}
try { lire(Path.of("config.json")); }
catch (IOException e) { log.error("Lecture impossible", e); }
Exception uncheckedException héritant de RuntimeException, que le compilateur n'oblige pas à traiter ; elle signale en général une erreur de programmation ou une règle métier violée.

Exception héritant de RuntimeException, que le compilateur n'oblige pas à traiter ; elle signale en général une erreur de programmation ou une règle métier violée.

Spring privilégie les unchecked (ex. DataAccessException) : elles traversent les couches sans polluer les signatures et déclenchent le rollback par défaut de @Transactional.

java
public class SoldeInsuffisantException extends RuntimeException {
    public SoldeInsuffisantException(String compte) {
        super("Solde insuffisant sur le compte " + compte);
    }
}
🎯 En entretien@Transactional ne fait rollback que sur les unchecked par défaut ; pour une checked, il faut rollbackFor = Exception.class.
try-with-resourcesSyntaxe try (ressource) {...} (Java 7) qui ferme automatiquement les ressources (AutoCloseable), même en cas d'exception.

Syntaxe try (ressource) {...} (Java 7) qui ferme automatiquement les ressources (AutoCloseable), même en cas d'exception.

java
try (var conn = dataSource.getConnection();
     var ps = conn.prepareStatement("SELECT solde FROM compte WHERE id = ?")) {
    ps.setLong(1, id);
    try (var rs = ps.executeQuery()) { /* ... */ }
}   // ps puis conn fermés automatiquement, dans l'ordre inverse
Avaler une exception (anti-pattern) et chaînage« Avaler » = attraper une exception sans rien faire : le bug devient invisible. Il faut au minimum logger, ou relancer en gardant la cause.

« Avaler » = attraper une exception sans rien faire : le bug devient invisible. Il faut au minimum logger, ou relancer en gardant la cause.

java
// ❌ l'erreur disparaît
try { paiement.executer(); } catch (Exception e) { }

// ✅ relancer avec contexte métier ET la cause d'origine
try { paiement.executer(); }
catch (IOException e) {
    throw new PaiementException("Échec appel opérateur pour " + ref, e);   // e = cause
}
Exception métier personnaliséeException dédiée à une règle du domaine (CompteBloqueException, NotFoundException) ; elle rend le code lisible et se traduit en code HTTP via un handler global.

Exception dédiée à une règle du domaine (CompteBloqueException, NotFoundException) ; elle rend le code lisible et se traduit en code HTTP via un handler global.

java
public class NotFoundException extends RuntimeException {
    public NotFoundException(String message) { super(message); }
}
// traduite en 404 par @RestControllerAdvice (voir Spring)

6Mémoire JVM & Garbage Collector7

Heap (tas)Zone mémoire partagée par tous les threads où vivent tous les objets créés avec new ; gérée par le Garbage Collector.

Zone mémoire partagée par tous les threads où vivent tous les objets créés avec new ; gérée par le Garbage Collector.

Sa taille se règle avec -Xms (initiale) et -Xmx (maximale). En conteneur, on préfère -XX:MaxRAMPercentage=75 pour s'adapter à la limite mémoire du pod.

bash
java -Xms512m -Xmx2g -jar app.jar
java -XX:MaxRAMPercentage=75 -jar app.jar   # recommandé dans Docker/Kubernetes
Young Generation et Old GenerationLe heap est découpé par âge : les objets neufs naissent dans la Young Gen (nettoyée souvent et vite) ; ceux qui survivent sont promus dans l'Old Gen (nettoyée plus rarement).

Le heap est découpé par âge : les objets neufs naissent dans la Young Gen (nettoyée souvent et vite) ; ceux qui survivent sont promus dans l'Old Gen (nettoyée plus rarement).

Hypothèse générationnelle : la plupart des objets meurent jeunes (variables temporaires d'une requête). Nettoyer souvent une petite zone est très efficace (« minor GC »). L'Old Gen contient les caches, singletons, sessions ; sa collecte (« major/full GC ») est plus coûteuse.

Stack (pile d'appels)Mémoire propre à chaque thread qui contient les frames d'appel : variables locales, paramètres, adresse de retour. Libérée automatiquement à la sortie de la méthode.

Mémoire propre à chaque thread qui contient les frames d'appel : variables locales, paramètres, adresse de retour. Libérée automatiquement à la sortie de la méthode.

Les types primitifs locaux et les références sont sur la pile ; les objets eux-mêmes sont dans le heap. Une récursion infinie remplit la pile → StackOverflowError.

java
void traiter() {
    int compteur = 0;                 // sur la stack du thread
    Client c = new Client("Awa");     // la référence c est sur la stack, l'objet dans le heap
}   // frame dépilée : compteur et c disparaissent
MetaspaceZone hors heap (Java 8+) qui stocke les métadonnées des classes chargées ; elle a remplacé la PermGen.

Zone hors heap (Java 8+) qui stocke les métadonnées des classes chargées ; elle a remplacé la PermGen.

Elle grandit avec le nombre de classes (frameworks, proxies générés). Une fuite de classloaders (redéploiements à chaud) provoque OutOfMemoryError: Metaspace. Limite réglable avec -XX:MaxMetaspaceSize.

Garbage Collector (GC)Mécanisme automatique qui libère la mémoire des objets devenus inaccessibles (plus aucune référence vers eux).

Mécanisme automatique qui libère la mémoire des objets devenus inaccessibles (plus aucune référence vers eux).

Le GC part des « racines » (variables de pile, champs statiques) et marque tout ce qui est atteignable ; le reste est récupéré. G1 est le GC par défaut (bon compromis débit/pauses) ; ZGC (générationnel depuis Java 21) vise des pauses de l'ordre de la milliseconde même sur de très gros heaps.

bash
java -XX:+UseZGC -Xmx8g -jar app.jar        # pauses très courtes
java -Xlog:gc -jar app.jar                   # journaliser l'activité du GC
⚠️ PiègeLe GC ne corrige pas une fuite : un objet encore référencé (cache statique sans limite) ne sera jamais libéré.
Fuite mémoireDes objets inutiles restent référencés (cache sans limite, listener jamais retiré, ThreadLocal non nettoyé) : le GC ne peut pas les libérer et la mémoire monte jusqu'à l'OutOfMemoryError.

Des objets inutiles restent référencés (cache sans limite, listener jamais retiré, ThreadLocal non nettoyé) : le GC ne peut pas les libérer et la mémoire monte jusqu'à l'OutOfMemoryError.

java
// ❌ grossit à chaque requête, jamais vidé
private static final Map<String, Rapport> CACHE = new HashMap<>();
// ✅ cache borné avec expiration (Caffeine)
Cache<String, Rapport> cache = Caffeine.newBuilder().maximumSize(10_000)
        .expireAfterWrite(Duration.ofMinutes(10)).build();
🎯 En entretienDiagnostic : heap dump (jcmd <pid> GC.heap_dump) analysé avec Eclipse MAT ; chercher le « dominator » qui retient la mémoire.
OutOfMemoryError et StackOverflowErrorOutOfMemoryError : le heap (ou le Metaspace) est plein malgré le GC. StackOverflowError : la pile d'un thread est pleine, souvent à cause d'une récursion infinie.

OutOfMemoryError : le heap (ou le Metaspace) est plein malgré le GC. StackOverflowError : la pile d'un thread est pleine, souvent à cause d'une récursion infinie.

java
int factorielle(int n) { return n * factorielle(n - 1); }   // pas de cas d'arrêt → StackOverflowError
bash
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps -jar app.jar

7Concurrence13

Thread et RunnableUn thread est un fil d'exécution indépendant ; Runnable est la tâche (sans résultat) qu'il exécute. Callable est la variante qui renvoie un résultat.

Un thread est un fil d'exécution indépendant ; Runnable est la tâche (sans résultat) qu'il exécute. Callable est la variante qui renvoie un résultat.

On crée rarement des threads à la main : on soumet des tâches à un ExecutorService qui gère un pool.

java
Runnable tache = () -> System.out.println("Exécuté par " + Thread.currentThread().getName());
Thread t = new Thread(tache);
t.start();     // ⚠️ start(), pas run() : run() s'exécuterait dans le thread courant
t.join();      // attendre la fin
Race condition (condition de course)Bug où le résultat dépend de l'ordre d'exécution de plusieurs threads qui accèdent à une donnée partagée sans synchronisation.

Bug où le résultat dépend de l'ordre d'exécution de plusieurs threads qui accèdent à une donnée partagée sans synchronisation.

java
class Compteur { int valeur = 0; void incr() { valeur++; } }   // lire → +1 → écrire
// 2 threads × 1000 appels → résultat souvent < 2000 : des incréments sont perdus
🎯 En entretienTrois remèdes : ne pas partager (objets immuables, variables locales), synchroniser (synchronized, locks), ou utiliser des classes atomiques/concurrentes.
volatileGarantit que l'écriture d'une variable par un thread est immédiatement visible par les autres (pas de cache CPU) et interdit certains réordonnancements. Ne garantit PAS l'atomicité.

Garantit que l'écriture d'une variable par un thread est immédiatement visible par les autres (pas de cache CPU) et interdit certains réordonnancements. Ne garantit PAS l'atomicité.

Parfait pour un drapeau d'arrêt lu par un thread et écrit par un autre. Inutile pour un compteur : compteur++ reste 3 opérations (lire, incrémenter, écrire).

java
private volatile boolean actif = true;
public void arreter() { actif = false; }           // thread A
public void boucle() { while (actif) { traiter(); } }   // thread B voit le changement
synchronizedVerrou intrinsèque : un seul thread à la fois exécute le bloc ou la méthode sur un même objet ; garantit exclusion mutuelle ET visibilité.

Verrou intrinsèque : un seul thread à la fois exécute le bloc ou la méthode sur un même objet ; garantit exclusion mutuelle ET visibilité.

java
public class Compte {
    private BigDecimal solde = BigDecimal.ZERO;
    public synchronized void crediter(BigDecimal m) { solde = solde.add(m); }
    public void debiter(BigDecimal m) {
        synchronized (this) {                       // bloc synchronisé
            if (solde.compareTo(m) < 0) throw new SoldeInsuffisantException("…");
            solde = solde.subtract(m);
        }
    }
}
⚠️ PiègeDans un système à plusieurs instances (plusieurs pods), synchronized ne protège rien entre les instances : il faut un verrou en base (SELECT … FOR UPDATE, @Version) ou distribué.
AtomicInteger et CASClasses (java.util.concurrent.atomic) qui offrent des opérations atomiques sans verrou grâce à l'instruction processeur Compare-And-Swap.

Classes (java.util.concurrent.atomic) qui offrent des opérations atomiques sans verrou grâce à l'instruction processeur Compare-And-Swap.

java
private final AtomicLong compteurRequetes = new AtomicLong();
compteurRequetes.incrementAndGet();                  // atomique, thread-safe
compteurRequetes.updateAndGet(v -> Math.max(v, 10)); // mise à jour atomique par fonction
ConcurrentHashMapMap thread-safe et performante : verrouillage fin par bucket, lectures sans verrou, opérations atomiques compute/merge.

Map thread-safe et performante : verrouillage fin par bucket, lectures sans verrou, opérations atomiques compute/merge.

java
ConcurrentHashMap<String, LongAdder> appelsParClient = new ConcurrentHashMap<>();
appelsParClient.computeIfAbsent(clientId, k -> new LongAdder()).increment();
⚠️ Piègeif (!map.containsKey(k)) map.put(k, v) n'est pas atomique même sur une ConcurrentHashMap : utiliser putIfAbsent ou computeIfAbsent.
java.util.concurrent et ExecutorServicejava.util.concurrent regroupe les outils de concurrence haut niveau ; ExecutorService gère un pool de threads auquel on soumet des tâches.

java.util.concurrent regroupe les outils de concurrence haut niveau ; ExecutorService gère un pool de threads auquel on soumet des tâches.

Réutiliser des threads évite le coût de création et limite la concurrence. Toujours fermer l'executor (ou utiliser try-with-resources depuis Java 19).

java
ExecutorService pool = Executors.newFixedThreadPool(8);
Future<Rapport> f = pool.submit(() -> genererRapport(mois));
Rapport r = f.get(30, TimeUnit.SECONDS);    // attente avec timeout
pool.shutdown();
CompletableFutureRésultat asynchrone composable (Java 8) : on enchaîne des traitements (thenApply, thenCombine) sans bloquer, avec gestion d'erreur (exceptionally).

Résultat asynchrone composable (Java 8) : on enchaîne des traitements (thenApply, thenCombine) sans bloquer, avec gestion d'erreur (exceptionally).

java
CompletableFuture<Client> client = CompletableFuture.supplyAsync(() -> clientApi.get(id), pool);
CompletableFuture<List<Compte>> comptes = CompletableFuture.supplyAsync(() -> compteApi.list(id), pool);
Synthese s = client.thenCombine(comptes, Synthese::new)     // les deux appels en parallèle
        .orTimeout(3, TimeUnit.SECONDS)
        .exceptionally(ex -> Synthese.degradee())
        .join();
ForkJoinPoolPool spécialisé « diviser pour régner » avec vol de tâches (work stealing) ; utilisé par parallelStream et par défaut par CompletableFuture.

Pool spécialisé « diviser pour régner » avec vol de tâches (work stealing) ; utilisé par parallelStream et par défaut par CompletableFuture.

Chaque thread a sa propre file ; un thread inoccupé « vole » le travail des autres. Idéal pour des calculs récursifs CPU-bound, pas pour des appels bloquants.

Deadlock (interblocage)Deux threads (ou transactions) s'attendent mutuellement : chacun tient un verrou dont l'autre a besoin, et aucun n'avance.

Deux threads (ou transactions) s'attendent mutuellement : chacun tient un verrou dont l'autre a besoin, et aucun n'avance.

java
// Thread 1 : lock(compteA) puis lock(compteB)
// Thread 2 : lock(compteB) puis lock(compteA)   → blocage mutuel
// ✅ Remède : toujours prendre les verrous dans le même ordre (ex. par id croissant)
Compte premier = a.id() < b.id() ? a : b, second = premier == a ? b : a;
synchronized (premier) { synchronized (second) { transferer(a, b, montant); } }
🎯 En entretienDiagnostic : jstack <pid> affiche « Found one Java-level deadlock » avec les threads concernés.
Thread-safeSe dit d'un code ou d'un objet qui reste correct quand plusieurs threads l'utilisent en même temps.

Se dit d'un code ou d'un objet qui reste correct quand plusieurs threads l'utilisent en même temps.

Les beans Spring sont des singletons partagés par toutes les requêtes : ils doivent être sans état mutable (stateless) ou thread-safe. Un champ private int compteur dans un @Service est un bug de concurrence.

I/O-bound vs CPU-boundI/O-bound : le temps est passé à attendre (base, réseau, fichiers). CPU-bound : le temps est passé à calculer. Le bon outil de concurrence dépend de ce profil.

I/O-bound : le temps est passé à attendre (base, réseau, fichiers). CPU-bound : le temps est passé à calculer. Le bon outil de concurrence dépend de ce profil.

ProfilExempleOutil adapté
I/O-boundAPI qui appelle 3 services et une baseVirtual threads, async
CPU-boundCalcul de scoring sur 1 M de lignesPool = nb de cœurs, ForkJoin
Reactive StreamsStandard d'échange asynchrone de flux avec contre-pression (backpressure) : le consommateur indique combien d'éléments il peut traiter. Implémenté par Reactor (Spring WebFlux) et RxJava.

Standard d'échange asynchrone de flux avec contre-pression (backpressure) : le consommateur indique combien d'éléments il peut traiter. Implémenté par Reactor (Spring WebFlux) et RxJava.

Le modèle réactif (Mono/Flux) permet de servir beaucoup de connexions avec peu de threads, au prix d'un code plus complexe à lire et déboguer. Depuis Java 21, les virtual threads offrent souvent la même scalabilité avec du code impératif classique.

java
Flux<Transaction> flux = webClient.get().uri("/transactions").retrieve()
        .bodyToFlux(Transaction.class)
        .filter(t -> t.montant().compareTo(SEUIL) > 0)
        .limitRate(100);   // backpressure : demande 100 éléments à la fois

📋 Synthèse express — Java

🗓 Versions Java LTS

Java 8
LTS · 2014
  • Lambdas & Streams
  • Optional
  • Default methods
Java 11
LTS · 2018
  • var (local type inference)
  • HttpClient
  • String methods
Java 17
LTS · 2021
  • Records
  • Sealed Classes
  • Pattern Matching instanceof
Java 21
LTS · Sept 2023 ⭐
  • Virtual Threads (JEP 444)
  • Pattern Matching switch
  • Sequenced Collections
  • Record Patterns
Java 25
LTS · Sept 2025 🆕
  • Flexible Constructors (JEP 513)
  • Scoped Values (JEP 506)
  • Compact source files (JEP 512)
  • Compact Object Headers (JEP 519)
  • En preview : Primitive Patterns (JEP 507), Structured Concurrency
Virtual Threads Java 21 : Threads ultra-légers gérés par la JVM (Project Loom). Permettent des millions de threads concurrents. Idéal pour I/O-bound tasks. Thread.ofVirtual().start(runnable)

🧱 Piliers OOP

🔒 Encapsulation

Private fields + getters/setters. Invariants protégés. Records Java 17+ = encapsulation immutable.

🧬 Héritage

extends + super. Préférer composition. Max 2-3 niveaux d'héritage.

🎭 Polymorphisme

Override (runtime) vs Overload (compile-time). Sealed classes pour hiérarchies exhaustives.

🎨 Abstraction

Interfaces > classes abstraites. Interfaces fonctionnelles + lambdas.

📚 Collections — Complexités

StructureAccèsInsertionRechercheCas d'usage
ArrayListO(1)O(n) têteO(n)Lecture fréquente
LinkedListO(n)O(1)O(n)Files, Deque
HashMap—O(1) amort.O(1) amort.Cache key-value
TreeMap—O(log n)O(log n)Trié, ranges
HashSet—O(1) amort.O(1) amort.Unicité
PriorityQueueO(1) headO(log n)O(n)Scheduling
ArrayDequeO(1)O(1)O(n)Pile (LIFO) et file (FIFO) efficaces

🌊 Streams — Opérateurs clés

// Chaîne typique List<String> results = orders.stream() .filter(o -> o.getStatus() == Status.ACTIVE) .map(Order::getCustomerName) .distinct() .sorted() .limit(10) .collect(Collectors.toList()); // Collecteurs avancés Map<Status, List<Order>> grouped = orders.stream() .collect(Collectors.groupingBy(Order::getStatus)); // Java 16+ toList() (immutable) var names = orders.stream().map(Order::getName).toList();

💡 Records & Sealed Classes (Java 17+)

// Record = POJO immutable auto-généré record UserDto(Long id, String name, String email) {} // Sealed class = hiérarchie fermée sealed interface Shape permits Circle, Rectangle, Triangle {} record Circle(double radius) implements Shape {} // Pattern matching switch (Java 21) double area = switch (shape) { case Circle(var r) -> Math.PI * r * r; case Rectangle(var w, var h) -> w * h; case Triangle(var b, var h) -> 0.5 * b * h; };

🔀 Interface vs Classe Abstraite

CritèreInterfaceClasse Abstraite
Héritage multiple✅ implements plusieurs❌ extends une seule
État partagé❌ (constantes seulement)✅ champs d'instance
Constructeur❌✅
Méthodesabstract, default, staticabstract + concrètes
Quand choisirContrat / comportement purSocle commun réel avec état

🔒 Immuabilité & String

Pourquoi String est immuable ?
  • Sécurité — clés de Map, paramètres réseau non altérables
  • String Pool — partage de littéraux sans copie
  • Cache hashCode — calculé une fois, réutilisé
  • Thread-safe — pas besoin de synchronisation
StringBuilder vs StringBuffer

StringBuilder : mutable, non thread-safe, O(n). StringBuffer : synchronized, plus lent. Concaténation en boucle = toujours StringBuilder.

// ❌ O(n²) — crée N objets String String s = ""; for(var item : list) s += item; // ✅ O(n) var sb = new StringBuilder(); list.forEach(sb::append);

⚠️ Exceptions — Checked vs Unchecked

Checked (IOException, SQLException) : déclarées dans la signature, le compilateur les impose. Utiles quand le caller peut raisonnablement récupérer.
Unchecked (RuntimeException, NullPointerException, IllegalArgumentException) : erreurs de programmation, invariants violés. Spring préfère les unchecked pour ne pas polluer les couches.
⛔ Ne jamais avaler : catch(Exception e) {} — logger au moins ou relancer avec contexte.

🧵 JVM & Concurrence

Mémoire JVM
  • Heap — objets, Young Gen + Old Gen
  • Stack — frames d'appel, variables locales (1 par thread)
  • Metaspace — métadonnées des classes (Java 8+ : remplace PermGen)
  • GC — libère objets inaccessibles, ne corrige pas les fuites de référence
volatile & synchronized

volatile : garantit la visibilité inter-threads ET ordonnancement mémoire, mais pas l'atomicité. compteur++ n'est pas atomique même avec volatile.

synchronized : mutual exclusion + visibilité. Préférer AtomicInteger, ConcurrentHashMap ou java.util.concurrent.

⚡ Quiz Rapide — Java
1. Quelle est la complexité d'un get() sur une HashMap en cas idéal ?
A
O(n)
B
O(log n)
C
O(1) amorti
D
O(n log n)
✅ C. O(1) amorti. HashMap utilise hashCode() pour accéder directement au bucket. En cas de collisions massives (hash mal distribué), dégradation en O(n) ou O(log n) (TreeNode depuis Java 8).
2. Quelle fonctionnalité Java 21 permet des millions de threads légers ?
A
CompletableFuture
B
Virtual Threads (Project Loom)
C
Fork/Join Pool
D
Reactive Streams
✅ B. Virtual Threads (JEP 444). Gérés par la JVM, non par l'OS. Idéaux pour code I/O-bound. Thread.ofVirtual().start(task)
3. Que fait le mot-clé sealed sur une interface Java 17 ?
A
La rend final (non implémentable)
B
Restreint les classes autorisées à l'implémenter via permits
C
Interdit les méthodes default
D
Génère automatiquement les getters
✅ B. sealed interface Shape permits Circle, Rectangle : seules ces classes peuvent implémenter Shape. Permet un exhaustive pattern matching dans switch.
4. Quelle est la différence entre == et equals() pour des String ?
A
Aucune différence, les deux comparent le contenu
B
== compare les références, equals() compare le contenu
C
== est plus rapide donc toujours préféré
D
equals() compare les références
✅ B. == compare l'adresse mémoire (référence). equals() compare le contenu. "a" == "a" peut être true via le String Pool, mais new String("a") == new String("a") est false.
5. Pourquoi préférer l'interface à la classe abstraite pour définir un contrat ?
A
Les interfaces sont plus rapides à l'exécution
B
Une classe peut implémenter plusieurs interfaces mais ne peut étendre qu'une seule classe
C
Les interfaces compilent plus vite
D
Les interfaces peuvent avoir des champs mutable
✅ B. L'héritage multiple d'implémentation est impossible en Java, mais une classe peut implémenter N interfaces. Choisir une interface pour le contrat (comportement), une classe abstraite quand on a un état partagé réel et des méthodes partiellement implémentées.
6. volatile garantit-il l'atomicité de compteur++ ?
A
Oui, volatile rend toute opération atomique
B
Non, volatile garantit la visibilité mais pas l'atomicité des opérations composées
C
Oui, car ++ est une instruction unique
D
volatile n'existe pas en Java
✅ B. volatile garantit la visibilité inter-threads et empêche la réorganisation mémoire, mais compteur++ est en réalité 3 opérations : lire, incrémenter, écrire. Deux threads peuvent lire la même valeur simultanément. Utiliser AtomicInteger.incrementAndGet() à la place.
Spring Boot & Spring Cloud
IoC, REST API, JPA, Security, versions récentes 3.x et 4.0
📖 Cours complet — 78 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/78 maîtrisées

1Fondations de Spring15

Spring Framework vs Spring BootSpring Framework fournit le conteneur IoC, l'AOP, le web MVC, les transactions ; Spring Boot ajoute l'auto-configuration, les starters et un serveur embarqué pour démarrer sans configuration manuelle.

Spring Framework fournit le conteneur IoC, l'AOP, le web MVC, les transactions ; Spring Boot ajoute l'auto-configuration, les starters et un serveur embarqué pour démarrer sans configuration manuelle.

Avec Spring « nu », il fallait déclarer soi-même la DataSource, le TransactionManager, le DispatcherServlet… Spring Boot détecte les bibliothèques présentes et configure tout avec des valeurs par défaut raisonnables, que l'on surcharge dans application.yml. L'application se lance comme un simple java -jar (Tomcat embarqué).

java
@SpringBootApplication          // = @Configuration + @EnableAutoConfiguration + @ComponentScan
public class BanqueApplication {
    public static void main(String[] args) {
        SpringApplication.run(BanqueApplication.class, args);
    }
}
🎯 En entretienVersions actuelles : Spring Boot 4.x repose sur Spring Framework 7 ; Spring Boot 3.x sur Spring Framework 6.
Auto-configurationMécanisme de Spring Boot qui crée automatiquement des beans selon le classpath et les propriétés (ex. une DataSource si un driver JDBC est présent).

Mécanisme de Spring Boot qui crée automatiquement des beans selon le classpath et les propriétés (ex. une DataSource si un driver JDBC est présent).

Chaque auto-configuration est conditionnelle (@ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty). Si tu déclares ton propre bean, celui de Boot s'efface. Pour voir ce qui a été activé ou non : --debug au démarrage.

java
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
public class MaDataSourceAutoConfig {
    @Bean
    @ConditionalOnMissingBean          // seulement si l'application n'en définit pas
    DataSource dataSource(DataSourceProperties p) { return p.initializeDataSourceBuilder().build(); }
}
bash
java -jar app.jar --debug    # rapport "CONDITIONS EVALUATION REPORT"
StarterDépendance Maven/Gradle « tout-en-un » qui apporte un ensemble cohérent de bibliothèques pour un besoin (web, JPA, sécurité…) avec des versions compatibles.

Dépendance Maven/Gradle « tout-en-un » qui apporte un ensemble cohérent de bibliothèques pour un besoin (web, JPA, sécurité…) avec des versions compatibles.

xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>   <!-- Spring MVC + Jackson + Tomcat -->
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
🎯 En entretienAvec Spring Boot 4, l'auto-configuration est découpée en modules plus fins : certaines fonctions demandent désormais un starter explicite (ex. starters de test dédiés).
IoC (Inversion of Control)Principe où ce n'est plus ton code qui crée ses dépendances : le conteneur Spring les crée, les relie et gère leur cycle de vie.

Principe où ce n'est plus ton code qui crée ses dépendances : le conteneur Spring les crée, les relie et gère leur cycle de vie.

Sans IoC, OrderService ferait new JpaOrderRepo() : couplage fort, impossible à remplacer en test. Avec IoC, la classe déclare ce dont elle a besoin et le conteneur le fournit. L'Injection de dépendances est la technique qui réalise l'IoC.

java
// ❌ sans IoC : la classe choisit et construit sa dépendance
private final OrderRepo repo = new JpaOrderRepo();
// ✅ avec IoC : elle la reçoit
public OrderService(OrderRepo repo) { this.repo = repo; }
Injection de dépendancesTechnique où les dépendances d'un objet lui sont fournies de l'extérieur, de préférence par le constructeur.

Technique où les dépendances d'un objet lui sont fournies de l'extérieur, de préférence par le constructeur.

Trois formes : par constructeur (recommandée), par setter, par champ (@Autowired sur un attribut, à éviter). Le constructeur rend les dépendances obligatoires et final, rend la classe testable sans Spring (new OrderService(mockRepo)) et révèle les classes qui ont trop de dépendances.

java
@Service
public class OrderService {
    private final OrderRepo repo;
    private final PaymentGateway gateway;
    // un seul constructeur → @Autowired implicite depuis Spring 4.3
    public OrderService(OrderRepo repo, PaymentGateway gateway) {
        this.repo = repo;
        this.gateway = gateway;
    }
}
⚠️ PiègeL'injection par champ (@Autowired private Repo repo;) cache les dépendances et oblige à démarrer Spring ou utiliser la réflexion pour tester.
BeanObjet créé, configuré et géré par le conteneur Spring (ApplicationContext). Par défaut, un seul exemplaire (singleton) par contexte.

Objet créé, configuré et géré par le conteneur Spring (ApplicationContext). Par défaut, un seul exemplaire (singleton) par contexte.

Tout ce que Spring injecte est un bean : services, repositories, contrôleurs, mais aussi objets techniques (ObjectMapper, DataSource). Un bean est déclaré par un stéréotype (@Service…) ou une méthode @Bean.

java
@Component
public class GenerateurReference {
    public String nouvelle() { return "TX-" + UUID.randomUUID(); }
}
ApplicationContextLe conteneur IoC de Spring : il scanne les classes, crée les beans, résout les dépendances, applique les proxies (transactions, sécurité) et publie des événements.

Le conteneur IoC de Spring : il scanne les classes, crée les beans, résout les dépendances, applique les proxies (transactions, sécurité) et publie des événements.

java
@Component
public class Demarrage implements ApplicationRunner {
    private final ApplicationContext ctx;
    public Demarrage(ApplicationContext ctx) { this.ctx = ctx; }
    public void run(ApplicationArguments args) {
        System.out.println(ctx.getBeanDefinitionCount() + " beans chargés");
    }
}
Stéréotypes : @Component, @Service, @Repository, @ControllerAnnotations qui déclarent une classe comme bean lors du scan ; elles indiquent aussi son rôle dans l'architecture.

Annotations qui déclarent une classe comme bean lors du scan ; elles indiquent aussi son rôle dans l'architecture.

AnnotationRôleParticularité
@ComponentBean génériqueBase des autres
@ServiceLogique métierPurement sémantique
@RepositoryAccès aux donnéesTraduit les exceptions SQL en DataAccessException
@Controller / @RestControllerCouche webMappe les requêtes HTTP
🎯 En entretienToutes créent des singletons ; le choix sert surtout la lisibilité et certains traitements (traduction d'exceptions pour @Repository).
@Configuration et @Bean@Configuration marque une classe de configuration ; chaque méthode @Bean qu'elle contient déclare un bean, utile pour des classes que tu ne peux pas annoter (bibliothèques tierces).

@Configuration marque une classe de configuration ; chaque méthode @Bean qu'elle contient déclare un bean, utile pour des classes que tu ne peux pas annoter (bibliothèques tierces).

java
@Configuration
public class ClientsConfig {
    @Bean
    RestClient waveClient(RestClient.Builder builder,
                          @Value("${wave.base-url}") String baseUrl) {
        return builder.baseUrl(baseUrl).build();
    }
}
Scopes de beanDurée de vie d'un bean : singleton (un par contexte, défaut), prototype (nouveau à chaque injection), request (un par requête HTTP), session (un par session HTTP).

Durée de vie d'un bean : singleton (un par contexte, défaut), prototype (nouveau à chaque injection), request (un par requête HTTP), session (un par session HTTP).

java
@Component
@Scope("prototype")
public class PanierCalcul { /* état propre à chaque utilisation */ }

@Component
@RequestScope                       // un exemplaire par requête HTTP
public class ContexteRequete { private String correlationId; }
⚠️ PiègeUn singleton est partagé par toutes les requêtes simultanées : pas de champ mutable (compteur, utilisateur courant) dans un @Service.
@AutowiredAnnotation qui demande à Spring d'injecter une dépendance ; facultative sur un constructeur unique depuis Spring 4.3.

Annotation qui demande à Spring d'injecter une dépendance ; facultative sur un constructeur unique depuis Spring 4.3.

Si plusieurs beans du même type existent, Spring ne sait pas lequel choisir : on précise avec @Qualifier("nom") ou on marque un bean @Primary.

java
public NotificationService(@Qualifier("sms") Notificateur notificateur) { … }
Configuration externalisée : application.yml, @Value, @ConfigurationPropertiesLes paramètres (URL, timeouts, secrets) vivent hors du code, dans application.yml ou des variables d'environnement ; on les lit avec @Value ou, mieux, une classe @ConfigurationProperties typée.

Les paramètres (URL, timeouts, secrets) vivent hors du code, dans application.yml ou des variables d'environnement ; on les lit avec @Value ou, mieux, une classe @ConfigurationProperties typée.

yaml
banque:
  virement:
    plafond-journalier: 5000000
    timeout: 3s
java
@ConfigurationProperties(prefix = "banque.virement")
public record VirementProperties(BigDecimal plafondJournalier, Duration timeout) {}

@SpringBootApplication
@ConfigurationPropertiesScan
public class App {}
🎯 En entretienOrdre de priorité : variables d'environnement et arguments en ligne de commande l'emportent sur application.yml ; c'est ainsi qu'on injecte les secrets en production.
Profils (@Profile)Un profil active une configuration ou des beans selon l'environnement (dev, test, prod) : fichier application-prod.yml, beans annotés @Profile("prod").

Un profil active une configuration ou des beans selon l'environnement (dev, test, prod) : fichier application-prod.yml, beans annotés @Profile("prod").

java
@Bean @Profile("dev")
PaymentGateway fauxGateway() { return new PaymentGatewaySimule(); }
bash
java -jar app.jar --spring.profiles.active=prod   # charge application-prod.yml
Spring Boot ActuatorModule qui expose des endpoints de supervision : santé (/actuator/health), métriques, info, et format Prometheus.

Module qui expose des endpoints de supervision : santé (/actuator/health), métriques, info, et format Prometheus.

Indispensable en production et sur Kubernetes : les probes liveness/readiness interrogent /actuator/health/liveness et /actuator/health/readiness.

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      probes:
        enabled: true
⚠️ PiègeNe jamais exposer tous les endpoints (include: "*") publiquement : /env ou /heapdump révèlent des secrets.
Spring CloudEnsemble de projets pour les systèmes distribués : Gateway (API Gateway), Config Server, OpenFeign, Circuit Breaker, Stream (Kafka/RabbitMQ).

Ensemble de projets pour les systèmes distribués : Gateway (API Gateway), Config Server, OpenFeign, Circuit Breaker, Stream (Kafka/RabbitMQ).

Sur Kubernetes, une partie de ces besoins (découverte de services, configuration) est assurée par la plateforme ; Spring Cloud Gateway et Spring Cloud Stream restent très utilisés.

yaml
spring:
  cloud:
    gateway:
      server:
        webmvc:
          routes:
            - id: comptes
              uri: http://comptes-service:8080
              predicates:
                - Path=/api/comptes/**

2Versions & nouveautés (Boot 3 → 4)13

Jakarta EE (javax → jakarta)Depuis Spring Boot 3, les API Java EE ont changé de package : javax.persistence → jakarta.persistence, javax.servlet → jakarta.servlet, etc. Boot 4 passe à Jakarta EE 11.

Depuis Spring Boot 3, les API Java EE ont changé de package : javax.persistence → jakarta.persistence, javax.servlet → jakarta.servlet, etc. Boot 4 passe à Jakarta EE 11.

Oracle a cédé Java EE à la fondation Eclipse, qui l'a renommé Jakarta EE sans pouvoir garder le nom javax. Migrer de Boot 2 à Boot 3 impose donc de réécrire les imports (outil OpenRewrite) et de mettre à jour les bibliothèques. De Boot 3 à Boot 4, pas de nouveau renommage.

java
// Spring Boot 2.x
import javax.persistence.Entity;
import javax.validation.constraints.NotBlank;
// Spring Boot 3.x / 4.x
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotBlank;
AOT (Ahead-Of-Time) dans SpringTraitement effectué au build (et non au démarrage) : Spring pré-calcule la configuration des beans pour démarrer plus vite et permettre la compilation native.

Traitement effectué au build (et non au démarrage) : Spring pré-calcule la configuration des beans pour démarrer plus vite et permettre la compilation native.

Normalement Spring analyse les annotations par réflexion au démarrage. En mode AOT, le plugin Maven/Gradle génère du code Java qui enregistre directement les beans. Spring Boot 4 étend l'AOT à Spring Data (repositories pré-générés).

bash
./mvnw spring-boot:process-aot package
GraalVM Native ImageCompile l'application Java en exécutable natif : démarrage en dizaines de millisecondes et mémoire réduite, au prix d'un build long et de restrictions sur la réflexion.

Compile l'application Java en exécutable natif : démarrage en dizaines de millisecondes et mémoire réduite, au prix d'un build long et de restrictions sur la réflexion.

Intéressant pour les fonctions serverless et les microservices qui démarrent souvent. Moins pertinent pour une grosse application longue durée, où la JVM et son JIT restent plus performants une fois chauds.

bash
./mvnw -Pnative native:compile        # produit target/mon-app (binaire natif)
./mvnw spring-boot:build-image -Pnative   # image Docker native via buildpacks
Spring Boot 4 / Spring Framework 7 (vue d'ensemble)Génération sortie en novembre 2025 : Java 17 minimum (Java 25 recommandé), Jakarta EE 11, Jackson 3, versioning d'API natif, clients HTTP déclaratifs, null-safety JSpecify, résilience intégrée.

Génération sortie en novembre 2025 : Java 17 minimum (Java 25 recommandé), Jakarta EE 11, Jackson 3, versioning d'API natif, clients HTTP déclaratifs, null-safety JSpecify, résilience intégrée.

Points à retenir pour un entretien :

  • Baseline Java 17 (comme Boot 3), support de premier niveau de Java 25.
  • Jackson 3 par défaut (Jackson 2 encore disponible en mode déprécié).
  • Versioning d'API et HTTP Service Clients intégrés.
  • JSpecify pour la null-safety, @Retryable / @ConcurrencyLimit dans le cœur.
  • JUnit 6, @MockitoBean remplace @MockBean, RestTestClient remplace TestRestTemplate.
  • Les virtual threads restent opt-in (spring.threads.virtual.enabled=true).
🎯 En entretienLa branche 4.0 n'a qu'un support OSS court : viser 4.1 pour un nouveau projet.
JSpecify et null-safetyStandard d'annotations de nullité adopté par Spring 7 : dans un package @NullMarked, tout est non-null par défaut et @Nullable signale explicitement ce qui peut l'être.

Standard d'annotations de nullité adopté par Spring 7 : dans un package @NullMarked, tout est non-null par défaut et @Nullable signale explicitement ce qui peut l'être.

Les IDE et des outils comme NullAway détectent alors à la compilation un risque de NullPointerException. Spring annote toute son API ainsi.

java
// package-info.java
@NullMarked
package sn.banque.virement;
import org.jspecify.annotations.NullMarked;

// dans le package
public @Nullable Client chercher(String telephone) { … }   // peut renvoyer null : l'appelant est averti
Jackson 3Bibliothèque JSON par défaut de Spring Boot 4 : packages tools.jackson, configuration immuable (JsonMapper.builder()), dates ISO-8601 par défaut, exceptions non vérifiées.

Bibliothèque JSON par défaut de Spring Boot 4 : packages tools.jackson, configuration immuable (JsonMapper.builder()), dates ISO-8601 par défaut, exceptions non vérifiées.

Jackson convertit les objets Java en JSON et inversement (sérialisation/désérialisation) pour les contrôleurs REST. Les annotations (@JsonProperty, @JsonIgnore) restent dans com.fasterxml.jackson.annotation.

java
JsonMapper mapper = JsonMapper.builder()
        .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
        .build();                                      // immuable une fois construit
String json = mapper.writeValueAsString(new VirementDto("SN01", "SN02", BigDecimal.TEN));
Versioning d'API (Spring 7)Spring Framework 7 gère nativement plusieurs versions d'un endpoint : la version est lue dans un header, un paramètre, le chemin ou le media type, puis routée via l'attribut version.

Spring Framework 7 gère nativement plusieurs versions d'un endpoint : la version est lue dans un header, un paramètre, le chemin ou le media type, puis routée via l'attribut version.

properties
spring.mvc.apiversion.use.header=X-API-Version
spring.mvc.apiversion.supported=1.0,2.0
spring.mvc.apiversion.default=1.0
java
@GetMapping(path = "/comptes/{id}", version = "1.0")
CompteV1 compteV1(@PathVariable Long id) { … }

@GetMapping(path = "/comptes/{id}", version = "2.0")
CompteV2 compteV2(@PathVariable Long id) { … }   // X-API-Version: 2.0
🎯 En entretienAlternative historique encore très répandue : version dans l'URL (/api/v1/...).
HTTP Service Clients (@HttpExchange)Client HTTP déclaratif : on écrit une interface annotée et Spring génère l'implémentation (comme OpenFeign, mais intégré au framework). Boot 4 les enregistre avec @ImportHttpServices.

Client HTTP déclaratif : on écrit une interface annotée et Spring génère l'implémentation (comme OpenFeign, mais intégré au framework). Boot 4 les enregistre avec @ImportHttpServices.

java
@HttpExchange("/v1/paiements")
public interface OperateurClient {
    @GetExchange("/{id}")
    PaiementDto statut(@PathVariable String id);

    @PostExchange
    PaiementDto initier(@RequestBody DemandePaiement demande);
}

@Configuration
@ImportHttpServices(group = "operateur", types = OperateurClient.class)
class HttpClientsConfig {}   // l'URL de base se configure par groupe
RestClientClient HTTP synchrone moderne (Spring 6.1 / Boot 3.2) à API fluide ; c'est le remplaçant officiel de RestTemplate.

Client HTTP synchrone moderne (Spring 6.1 / Boot 3.2) à API fluide ; c'est le remplaçant officiel de RestTemplate.

Il partage l'infrastructure de RestTemplate (convertisseurs, intercepteurs) mais avec une API lisible et chaînée. Il s'injecte via RestClient.Builder, pré-configuré par Boot (observabilité, Jackson).

java
@Service
public class TauxService {
    private final RestClient client;
    public TauxService(RestClient.Builder builder) {
        this.client = builder.baseUrl("https://api.bceao.exemple").build();
    }
    public TauxDto taux(String devise) {
        return client.get().uri("/taux/{d}", devise)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError,
                          (req, res) -> { throw new NotFoundException("Devise inconnue " + devise); })
                .body(TauxDto.class);
    }
}
RestTemplateAncien client HTTP synchrone de Spring ; déprécié pour suppression depuis Spring Framework 7.1 (Boot 4.1), suppression prévue en 8.0. Utiliser RestClient.

Ancien client HTTP synchrone de Spring ; déprécié pour suppression depuis Spring Framework 7.1 (Boot 4.1), suppression prévue en 8.0. Utiliser RestClient.

java
// avant
User u = restTemplate.getForObject("/users/{id}", User.class, 42);
// après
User u = restClient.get().uri("/users/{id}", 42).retrieve().body(User.class);
WebClientClient HTTP non bloquant et réactif (Spring WebFlux), qui renvoie des Mono/Flux. À réserver aux applications réactives ou aux appels massivement concurrents.

Client HTTP non bloquant et réactif (Spring WebFlux), qui renvoie des Mono/Flux. À réserver aux applications réactives ou aux appels massivement concurrents.

java
Mono<TauxDto> taux = webClient.get().uri("/taux/{d}", "EUR")
        .retrieve().bodyToMono(TauxDto.class)
        .timeout(Duration.ofSeconds(2));
🎯 En entretienPour une application Spring MVC classique, RestClient + virtual threads est plus simple que WebClient.
Virtual threads dans Spring BootDepuis Boot 3.2, la propriété spring.threads.virtual.enabled=true fait traiter chaque requête Tomcat, chaque tâche @Async et chaque listener par un virtual thread. Toujours opt-in en Boot 4.

Depuis Boot 3.2, la propriété spring.threads.virtual.enabled=true fait traiter chaque requête Tomcat, chaque tâche @Async et chaque listener par un virtual thread. Toujours opt-in en Boot 4.

properties
spring.threads.virtual.enabled=true
⚠️ PiègeLe pool de connexions de la base (HikariCP, 10 par défaut) devient le vrai goulot : des milliers de virtual threads attendront une connexion.
Résilience intégrée : @Retryable et @ConcurrencyLimit (Spring 7)Spring Framework 7 intègre dans le cœur la relance automatique (@Retryable avec backoff exponentiel et jitter) et la limitation de concurrence (@ConcurrencyLimit).

Spring Framework 7 intègre dans le cœur la relance automatique (@Retryable avec backoff exponentiel et jitter) et la limitation de concurrence (@ConcurrencyLimit).

java
@Configuration
@EnableResilientMethods
class ResilienceConfig {}

@Service
class NotificationService {
    @Retryable(includes = OperateurIndisponibleException.class,
               maxRetries = 3, delay = 200, multiplier = 2, jitter = 50, maxDelay = 2000)
    public void envoyerSms(String tel, String msg) { … }

    @ConcurrencyLimit(10)            // au plus 10 appels simultanés
    public void appelerOperateur() { … }
}
🎯 En entretienPour un circuit breaker complet (états Open/Half-Open), on reste sur Resilience4j.

3API REST avec Spring MVC11

REST (principes)Style d'architecture où l'on manipule des ressources identifiées par des URL avec les verbes HTTP, de façon sans état (stateless), en échangeant des représentations (souvent JSON).

Style d'architecture où l'on manipule des ressources identifiées par des URL avec les verbes HTTP, de façon sans état (stateless), en échangeant des représentations (souvent JSON).

Bonnes pratiques : noms au pluriel (/clients), pas de verbe dans l'URL (POST /clients et non /creerClient), codes HTTP significatifs, pagination, versioning.

ActionRequêteRéponse
ListerGET /api/v1/clients?page=0&size=20200 + page
LireGET /api/v1/clients/42200 ou 404
CréerPOST /api/v1/clients201 + Location
RemplacerPUT /api/v1/clients/42200 / 204
Modifier partiellementPATCH /api/v1/clients/42200
SupprimerDELETE /api/v1/clients/42204
Verbes HTTP, sûreté et idempotenceGET lit sans effet de bord (sûr) ; PUT et DELETE sont idempotents (les répéter donne le même état) ; POST et PATCH ne le sont pas par nature.

GET lit sans effet de bord (sûr) ; PUT et DELETE sont idempotents (les répéter donne le même état) ; POST et PATCH ne le sont pas par nature.

C'est essentiel pour les relances : un client peut rejouer un GET ou un PUT sans risque, mais rejouer un POST de paiement peut débiter deux fois. D'où l'Idempotency-Key pour les POST sensibles.

VerbeSûrIdempotent
GET✅✅
PUT❌✅
DELETE❌✅
POST❌❌
PATCH❌❌ (en général)
@RestController@Controller + @ResponseBody : chaque méthode renvoie directement des données sérialisées en JSON, et non une vue HTML.

@Controller + @ResponseBody : chaque méthode renvoie directement des données sérialisées en JSON, et non une vue HTML.

java
@RestController
@RequestMapping("/api/v1/clients")
public class ClientController {
    private final ClientService service;
    public ClientController(ClientService service) { this.service = service; }

    @GetMapping("/{id}")
    public ClientDto get(@PathVariable Long id) { return service.get(id); }  // → JSON
}
🎯 En entretienUn contrôleur doit rester fin : il traduit HTTP ↔ Java, valide l'entrée et délègue au service. Pas de logique métier.
@RequestMapping, @GetMapping, @PostMapping…Annotations qui associent une URL et un verbe HTTP à une méthode Java ; @RequestMapping sur la classe fixe le préfixe commun.

Annotations qui associent une URL et un verbe HTTP à une méthode Java ; @RequestMapping sur la classe fixe le préfixe commun.

java
@RestController
@RequestMapping("/api/v1/comptes")              // préfixe
class CompteController {
    @GetMapping                 List<CompteDto> lister() { … }          // GET /api/v1/comptes
    @PostMapping                CompteDto creer(@RequestBody …) { … }   // POST
    @PatchMapping("/{id}/gel")  void geler(@PathVariable Long id) { … } // PATCH
    @DeleteMapping("/{id}")     void supprimer(@PathVariable Long id) { … }
}
@PathVariable vs @RequestParam@PathVariable lit une partie du chemin (/clients/{id}) pour identifier une ressource ; @RequestParam lit un paramètre de requête (?ville=Dakar) pour filtrer, trier ou paginer.

@PathVariable lit une partie du chemin (/clients/{id}) pour identifier une ressource ; @RequestParam lit un paramètre de requête (?ville=Dakar) pour filtrer, trier ou paginer.

java
@GetMapping("/clients/{id}")                       // GET /clients/42
ClientDto get(@PathVariable Long id) { … }

@GetMapping("/clients")                            // GET /clients?ville=Dakar&actif=true
List<ClientDto> chercher(@RequestParam String ville,
                         @RequestParam(defaultValue = "true") boolean actif) { … }
@RequestBodyIndique que le corps JSON de la requête doit être désérialisé (par Jackson) dans l'objet Java du paramètre.

Indique que le corps JSON de la requête doit être désérialisé (par Jackson) dans l'objet Java du paramètre.

java
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
ClientDto creer(@Valid @RequestBody CreateClientRequest req) { return service.creer(req); }
http
POST /api/v1/clients
Content-Type: application/json

{"nom": "Awa Ndiaye", "email": "awa@exemple.sn"}
ResponseEntityObjet qui représente la réponse HTTP complète : code de statut, en-têtes et corps. Utile quand le code ou les headers varient.

Objet qui représente la réponse HTTP complète : code de statut, en-têtes et corps. Utile quand le code ou les headers varient.

java
@PostMapping
ResponseEntity<ClientDto> creer(@Valid @RequestBody CreateClientRequest req) {
    ClientDto cree = service.creer(req);
    URI location = URI.create("/api/v1/clients/" + cree.id());
    return ResponseEntity.created(location).body(cree);       // 201 + header Location
}
@GetMapping("/{id}")
ResponseEntity<ClientDto> get(@PathVariable Long id) {
    return service.chercher(id).map(ResponseEntity::ok)
                  .orElse(ResponseEntity.notFound().build()); // 404
}
@ResponseStatusFixe le code HTTP renvoyé par une méthode de contrôleur (ex. 201 Created) ou associé à une exception.

Fixe le code HTTP renvoyé par une méthode de contrôleur (ex. 201 Created) ou associé à une exception.

java
@PostMapping
@ResponseStatus(HttpStatus.CREATED)      // 201 au lieu de 200
OrderDto create(@Valid @RequestBody CreateOrderRequest req) { … }

@ResponseStatus(HttpStatus.CONFLICT)     // 409 quand cette exception remonte
class EmailDejaUtiliseException extends RuntimeException { … }
Pagination avec PageableSpring Data lit automatiquement ?page=0&size=20&sort=nom,asc dans un objet Pageable et renvoie une Page (contenu + nombre total d'éléments).

Spring Data lit automatiquement ?page=0&size=20&sort=nom,asc dans un objet Pageable et renvoie une Page (contenu + nombre total d'éléments).

java
@GetMapping
Page<ClientDto> lister(@PageableDefault(size = 20, sort = "nom") Pageable pageable) {
    return repo.findAll(pageable).map(mapper::toDto);
}
⚠️ PiègeToujours borner la taille maximale (spring.data.web.pageable.max-page-size=100) : sinon ?size=1000000 peut faire tomber le service.
DTO (Data Transfer Object)Objet simple qui transporte les données entre couches ou sur le réseau ; c'est le contrat de l'API, distinct de l'entité JPA.

Objet simple qui transporte les données entre couches ou sur le réseau ; c'est le contrat de l'API, distinct de l'entité JPA.

Le DTO expose uniquement ce que le client doit voir (pas de mot de passe haché, pas de relations lazy), reste stable même si le schéma de base change, et porte les annotations de validation. Un record est idéal.

java
public record ClientDto(Long id, String nom, String email, String ville) {}
public record CreateClientRequest(@NotBlank String nom, @Email String email) {}
⚠️ PiègeExposer l'entité JPA directement : fuite de champs sensibles, sérialisation de relations lazy (LazyInitializationException ou requêtes en cascade) et couplage API ↔ base.
Mapper DTO ↔ Entity (MapStruct)Composant qui convertit entité ↔ DTO ; MapStruct génère ce code à la compilation à partir d'une interface, sans réflexion.

Composant qui convertit entité ↔ DTO ; MapStruct génère ce code à la compilation à partir d'une interface, sans réflexion.

java
@Mapper(componentModel = "spring")
public interface ClientMapper {
    ClientDto toDto(Client entity);
    @Mapping(target = "id", ignore = true)
    Client toEntity(CreateClientRequest req);
}

4Validation & gestion des erreurs4

Bean Validation (@Valid, @NotBlank, @Email, @Size)Standard Jakarta Validation : on annote les champs du DTO avec des contraintes, et @Valid déclenche leur vérification avant l'exécution du contrôleur.

Standard Jakarta Validation : on annote les champs du DTO avec des contraintes, et @Valid déclenche leur vérification avant l'exécution du contrôleur.

Si une contrainte échoue, Spring lève MethodArgumentNotValidException (→ 400). Contraintes courantes : @NotNull, @NotBlank, @Size, @Email, @Pattern, @Positive, @DecimalMin, @Past.

java
public record VirementRequest(
    @NotBlank @Pattern(regexp = "SN\\d{2}[A-Z0-9]{20,}") String ibanDest,
    @NotNull @Positive @Digits(integer = 12, fraction = 0) BigDecimal montant,
    @Size(max = 140) String motif) {}

@PostMapping("/virements")
VirementDto virer(@Valid @RequestBody VirementRequest req) { … }
🎯 En entretienLa validation front (Angular) améliore l'expérience ; la validation back est la seule qui protège vraiment.
MethodArgumentNotValidExceptionException levée par Spring quand un objet annoté @Valid ne respecte pas ses contraintes ; son BindingResult contient la liste des champs en erreur.

Exception levée par Spring quand un objet annoté @Valid ne respecte pas ses contraintes ; son BindingResult contient la liste des champs en erreur.

java
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
Map<String, String> erreurs(MethodArgumentNotValidException ex) {
    return ex.getBindingResult().getFieldErrors().stream()
        .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage, (a, b) -> a));
}   // {"montant": "doit être supérieur à 0"}
@RestControllerAdvice et @ExceptionHandler@RestControllerAdvice centralise la gestion des erreurs de tous les contrôleurs ; chaque méthode @ExceptionHandler traduit un type d'exception en réponse HTTP cohérente.

@RestControllerAdvice centralise la gestion des erreurs de tous les contrôleurs ; chaque méthode @ExceptionHandler traduit un type d'exception en réponse HTTP cohérente.

Sans cela, chaque contrôleur ferait ses try/catch et les formats d'erreur divergeraient. On mappe : validation → 400, NotFound → 404, conflit métier → 409, exception inattendue → 500 (sans détail technique, mais loggée).

java
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(NotFoundException.class)
    ResponseEntity<ProblemDetail> notFound(NotFoundException ex) {
        return ResponseEntity.status(404).body(ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()));
    }
    @ExceptionHandler(Exception.class)
    ResponseEntity<ProblemDetail> inattendu(Exception ex) {
        log.error("Erreur non gérée", ex);                     // détail dans les logs, pas au client
        return ResponseEntity.status(500).body(ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR));
    }
}
ProblemDetail (RFC 9457)Format standard de réponse d'erreur JSON (type, title, status, detail, instance) supporté nativement par Spring 6+.

Format standard de réponse d'erreur JSON (type, title, status, detail, instance) supporté nativement par Spring 6+.

java
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, "Solde insuffisant");
pd.setTitle("Virement refusé");
pd.setProperty("code", "SOLDE_INSUFFISANT");
json
{ "type": "about:blank", "title": "Virement refusé", "status": 409,
  "detail": "Solde insuffisant", "instance": "/api/v1/virements", "code": "SOLDE_INSUFFISANT" }
🎯 En entretienspring.mvc.problemdetails.enabled=true fait renvoyer ce format pour les erreurs standard de Spring MVC.

5Données avec JPA16

JPA vs Hibernate vs Spring Data JPAJPA est la spécification Java de mapping objet-relationnel (ORM) ; Hibernate en est l'implémentation la plus utilisée ; Spring Data JPA génère les repositories au-dessus.

JPA est la spécification Java de mapping objet-relationnel (ORM) ; Hibernate en est l'implémentation la plus utilisée ; Spring Data JPA génère les repositories au-dessus.

L'ORM traduit les objets Java en lignes SQL et inversement. On écrit repo.save(client) et Hibernate génère l'INSERT. Il faut quand même comprendre le SQL produit (activer les logs) pour éviter les problèmes de performance.

yaml
spring:
  jpa:
    show-sql: true                  # en dev seulement
    properties.hibernate.format_sql: true
    open-in-view: false
Entité JPA (@Entity, @Id)Classe Java mappée sur une table : @Entity la déclare, @Id marque la clé primaire, @Column/@Table ajustent le mapping.

Classe Java mappée sur une table : @Entity la déclare, @Id marque la clé primaire, @Column/@Table ajustent le mapping.

java
@Entity
@Table(name = "compte")
public class Compte {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, unique = true, length = 34)
    private String iban;
    @Column(nullable = false, precision = 15, scale = 2)
    private BigDecimal solde;
    @Version
    private Long version;            // verrouillage optimiste
    protected Compte() {}            // requis par JPA
}
⚠️ PiègePour l'argent : BigDecimal + colonne NUMERIC(15,2), jamais double.
Repository Spring Data (JpaRepository)Interface qui hérite de JpaRepository<Entité, TypeId> ; Spring génère l'implémentation avec save, findById, findAll, delete, pagination…

Interface qui hérite de JpaRepository<Entité, TypeId> ; Spring génère l'implémentation avec save, findById, findAll, delete, pagination…

java
public interface CompteRepository extends JpaRepository<Compte, Long> {
    Optional<Compte> findByIban(String iban);                 // requête dérivée du nom
    List<Compte> findByClientIdAndStatut(Long clientId, Statut statut);
    boolean existsByIban(String iban);
}
Requêtes dérivées du nom de méthodeSpring Data construit la requête à partir du nom de la méthode : findByStatutAndVille, countByActifTrue, findTop10ByOrderByDateDesc.

Spring Data construit la requête à partir du nom de la méthode : findByStatutAndVille, countByActifTrue, findTop10ByOrderByDateDesc.

java
List<Commande> findByStatutAndDateCreationAfter(Statut s, LocalDate d);
// → SELECT c FROM Commande c WHERE c.statut = ?1 AND c.dateCreation > ?2
long countByStatut(Statut s);
List<Commande> findTop10ByClientIdOrderByDateCreationDesc(Long clientId);
🎯 En entretienAu-delà de 2-3 critères, le nom devient illisible : passer à @Query ou à une Specification.
@Query et @Param (JPQL)@Query écrit explicitement la requête en JPQL (sur les entités) ou en SQL natif ; @Param lie un paramètre nommé.

@Query écrit explicitement la requête en JPQL (sur les entités) ou en SQL natif ; @Param lie un paramètre nommé.

JPQL manipule des entités et leurs attributs (o.status), pas des tables et colonnes.

java
@Query("SELECT o FROM Order o WHERE o.status = :s AND o.total > :min")
List<Order> findGrosses(@Param("s") Status s, @Param("min") BigDecimal min);

@Query(value = "SELECT * FROM commande WHERE created_at > now() - interval '1 day'", nativeQuery = true)
List<Order> dernieres24h();

@Modifying
@Query("UPDATE Compte c SET c.statut = 'GELE' WHERE c.id = :id")
int geler(@Param("id") Long id);
Relations @OneToMany / @ManyToOneAnnotations qui mappent les liens entre entités ; le côté @ManyToOne porte la clé étrangère, le côté @OneToMany (mappedBy) est l'inverse.

Annotations qui mappent les liens entre entités ; le côté @ManyToOne porte la clé étrangère, le côté @OneToMany (mappedBy) est l'inverse.

java
@Entity class Order {
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderLine> lines = new ArrayList<>();
}
@Entity class OrderLine {
    @ManyToOne(fetch = FetchType.LAZY)       // FK order_id dans la table order_line
    @JoinColumn(name = "order_id")
    private Order order;
}
⚠️ Piège@ManyToOne est EAGER par défaut : toujours forcer fetch = FetchType.LAZY.
Lazy vs Eager loadingLAZY charge une relation seulement au premier accès (proxy) ; EAGER la charge immédiatement avec l'entité. Par défaut : @OneToMany LAZY, @ManyToOne EAGER.

LAZY charge une relation seulement au premier accès (proxy) ; EAGER la charge immédiatement avec l'entité. Par défaut : @OneToMany LAZY, @ManyToOne EAGER.

La bonne pratique est tout en LAZY, puis charger explicitement ce dont un cas d'usage a besoin (JOIN FETCH, @EntityGraph). EAGER partout provoque des requêtes inutiles et souvent des N+1 cachés.

java
@ManyToOne(fetch = FetchType.LAZY)
private Client client;     // client.getNom() déclenche le SELECT au moment de l'accès
Problème N+1Anti-pattern où l'on exécute 1 requête pour charger N parents, puis 1 requête supplémentaire par parent pour charger ses enfants : N+1 requêtes au lieu d'une ou deux.

Anti-pattern où l'on exécute 1 requête pour charger N parents, puis 1 requête supplémentaire par parent pour charger ses enfants : N+1 requêtes au lieu d'une ou deux.

Typique avec des relations LAZY parcourues dans une boucle ou lors de la sérialisation JSON. Invisible en dev avec 10 lignes, catastrophique en production avec 10 000.

java
List<Order> orders = repo.findAll();              // 1 requête
for (Order o : orders) {
    o.getLines().size();                          // + 1 requête PAR commande → N requêtes
}
🎯 En entretienDétection : logs SQL (show-sql), statistiques Hibernate (hibernate.generate_statistics=true) ou un test qui compte les requêtes. Correction : JOIN FETCH, @EntityGraph ou @BatchSize.
JOIN FETCHClause JPQL qui charge l'entité et sa relation dans la même requête SQL (jointure), ce qui supprime le N+1 pour ce cas d'usage.

Clause JPQL qui charge l'entité et sa relation dans la même requête SQL (jointure), ce qui supprime le N+1 pour ce cas d'usage.

java
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.lines WHERE o.status = :s")
List<Order> findWithLines(@Param("s") Status s);
⚠️ PiègeJOIN FETCH sur une collection + pagination = Hibernate pagine en mémoire (avertissement HHH90003004). Paginer d'abord les ids, puis charger les relations.
@EntityGraphAnnotation qui indique quelles relations charger en même temps que l'entité pour une méthode de repository, sans écrire de JPQL.

Annotation qui indique quelles relations charger en même temps que l'entité pour une méthode de repository, sans écrire de JPQL.

java
@EntityGraph(attributePaths = {"lines", "customer"})
Optional<Order> findById(Long id);

@EntityGraph(attributePaths = "customer")
List<Order> findByStatus(Status status);
@BatchSizeAnnotation Hibernate qui charge les relations LAZY par lots (ex. 50 parents à la fois avec un IN (...)) : N+1 devient 1 + N/50 requêtes.

Annotation Hibernate qui charge les relations LAZY par lots (ex. 50 parents à la fois avec un IN (...)) : N+1 devient 1 + N/50 requêtes.

java
@OneToMany(mappedBy = "order")
@BatchSize(size = 50)
private List<OrderLine> lines;
yaml
spring.jpa.properties.hibernate.default_batch_fetch_size: 50   # global
LazyInitializationExceptionErreur levée quand on accède à une relation LAZY alors que la session Hibernate (transaction) est fermée, typiquement dans le contrôleur ou lors de la sérialisation JSON.

Erreur levée quand on accède à une relation LAZY alors que la session Hibernate (transaction) est fermée, typiquement dans le contrôleur ou lors de la sérialisation JSON.

java
@Transactional(readOnly = true)
public OrderDto get(Long id) {
    Order o = repo.findById(id).orElseThrow();
    return mapper.toDto(o);          // ✅ mapping DANS la transaction : les lignes se chargent
}
// ❌ renvoyer l'entité au contrôleur puis o.getLines() hors transaction → LazyInitializationException
Open Session In View (OSIV)Option activée par défaut dans Spring Boot qui garde la session Hibernate ouverte jusqu'à la fin de la requête HTTP ; elle masque les LazyInitializationException mais génère des requêtes cachées.

Option activée par défaut dans Spring Boot qui garde la session Hibernate ouverte jusqu'à la fin de la requête HTTP ; elle masque les LazyInitializationException mais génère des requêtes cachées.

yaml
spring.jpa.open-in-view: false   # recommandé : charger explicitement dans le service
Contexte de persistance et dirty checkingLe contexte de persistance (EntityManager) suit les entités chargées dans une transaction ; au commit, Hibernate détecte leurs modifications (dirty checking) et génère les UPDATE automatiquement.

Le contexte de persistance (EntityManager) suit les entités chargées dans une transaction ; au commit, Hibernate détecte leurs modifications (dirty checking) et génère les UPDATE automatiquement.

java
@Transactional
public void renommer(Long id, String nom) {
    Client c = repo.findById(id).orElseThrow();
    c.setNom(nom);        // pas de save() nécessaire : UPDATE généré au commit
}
ProjectionsNe charger que les colonnes utiles en renvoyant une interface ou un record au lieu de l'entité complète ; plus rapide et pas de piège lazy.

Ne charger que les colonnes utiles en renvoyant une interface ou un record au lieu de l'entité complète ; plus rapide et pas de piège lazy.

java
public record ClientResume(Long id, String nom) {}

@Query("SELECT new sn.banque.ClientResume(c.id, c.nom) FROM Client c WHERE c.ville = :v")
List<ClientResume> resumes(@Param("v") String ville);
Migrations de schéma (Flyway / Liquibase)Scripts versionnés qui font évoluer le schéma de base de façon reproductible sur tous les environnements ; exécutés au démarrage de l'application.

Scripts versionnés qui font évoluer le schéma de base de façon reproductible sur tous les environnements ; exécutés au démarrage de l'application.

sql
-- src/main/resources/db/migration/V3__ajout_statut_compte.sql
ALTER TABLE compte ADD COLUMN statut VARCHAR(20) NOT NULL DEFAULT 'ACTIF';
CREATE INDEX idx_compte_statut ON compte(statut);
⚠️ PiègeEn production : spring.jpa.hibernate.ddl-auto=validate (ou none), jamais update ou create.

6Transactions11

@TransactionalAnnotation qui exécute une méthode dans une transaction : commit si elle se termine normalement, rollback si une RuntimeException est levée.

Annotation qui exécute une méthode dans une transaction : commit si elle se termine normalement, rollback si une RuntimeException est levée.

Elle se place sur les méthodes publiques des services (pas sur les contrôleurs). Spring l'applique via un Proxy Spring qui ouvre la transaction avant l'appel et la termine après.

java
@Service
public class VirementService {
    @Transactional
    public void virer(Long source, Long dest, BigDecimal montant) {
        Compte a = repo.findById(source).orElseThrow();
        Compte b = repo.findById(dest).orElseThrow();
        a.debiter(montant);
        b.crediter(montant);       // si exception ici → le débit est annulé (rollback)
    }
}
Proxy Spring (JDK dynamic proxy / CGLIB)Objet intermédiaire généré par Spring qui enveloppe ton bean : il exécute du code avant/après chaque appel externe (transaction, sécurité, cache, @Async).

Objet intermédiaire généré par Spring qui enveloppe ton bean : il exécute du code avant/après chaque appel externe (transaction, sécurité, cache, @Async).

L'appelant reçoit le proxy, pas l'instance réelle. Spring utilise CGLIB (sous-classe générée) par défaut dans Boot. Conséquences : les méthodes private ou final ne sont pas interceptées, et un appel interne this.methode() contourne le proxy.

text
Contrôleur ──► [Proxy VirementService] ──► begin tx ──► VirementService.virer() ──► commit/rollback
AOP (Programmation orientée aspect)Technique qui ajoute un comportement transversal (logs, transactions, sécurité, métriques) autour de méthodes sans modifier leur code.

Technique qui ajoute un comportement transversal (logs, transactions, sécurité, métriques) autour de méthodes sans modifier leur code.

@Transactional, @Cacheable, @PreAuthorize, @Async sont implémentés par AOP. On peut écrire ses propres aspects.

java
@Aspect @Component
public class DureeAspect {
    @Around("@annotation(sn.banque.Chronometre)")
    public Object mesurer(ProceedingJoinPoint pjp) throws Throwable {
        long t0 = System.nanoTime();
        try { return pjp.proceed(); }
        finally { log.info("{} : {} ms", pjp.getSignature(), (System.nanoTime() - t0) / 1_000_000); }
    }
}
Auto-invocation (proxy bypass)Quand une méthode d'un bean appelle une autre méthode du même bean (this.methode()), l'appel ne passe pas par le proxy : @Transactional, @Cacheable, @Async de la méthode appelée sont ignorés.

Quand une méthode d'un bean appelle une autre méthode du même bean (this.methode()), l'appel ne passe pas par le proxy : @Transactional, @Cacheable, @Async de la méthode appelée sont ignorés.

java
@Service
public class PaiementService {
    public void traiter() {
        journaliser();                         // ❌ appel interne : pas de REQUIRES_NEW
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void journaliser() { … }
}
🎯 En entretienSolutions : déplacer la méthode dans un autre bean (la plus propre), ou s'auto-injecter le proxy (@Lazy sur le paramètre pour éviter le cycle).
Self-injectionInjecter un bean dans lui-même pour appeler ses méthodes via le proxy ; contournement du problème d'auto-invocation.

Injecter un bean dans lui-même pour appeler ses méthodes via le proxy ; contournement du problème d'auto-invocation.

java
@Service
public class PaiementService {
    private final PaiementService self;
    public PaiementService(@Lazy PaiementService self) { this.self = self; }   // @Lazy évite la dépendance circulaire
    public void traiter() { self.journaliser(); }                              // ✅ passe par le proxy
    @Transactional(propagation = Propagation.REQUIRES_NEW) public void journaliser() { … }
}
⚠️ PiègeFonctionne, mais signale souvent une classe qui a deux responsabilités : extraire un JournalService est plus clair.
Propagation REQUIRED (défaut)Rejoint la transaction en cours s'il y en a une, sinon en crée une nouvelle. Une erreur n'importe où annule tout l'ensemble.

Rejoint la transaction en cours s'il y en a une, sinon en crée une nouvelle. Une erreur n'importe où annule tout l'ensemble.

java
@Transactional                       // = propagation REQUIRED
public void commander() {
    stockService.reserver();         // @Transactional aussi → même transaction
    paiementService.debiter();       // si échec → la réservation est annulée aussi
}
Propagation REQUIRES_NEWSuspend la transaction en cours et en ouvre une nouvelle, indépendante : son commit ou rollback n'affecte pas l'appelant.

Suspend la transaction en cours et en ouvre une nouvelle, indépendante : son commit ou rollback n'affecte pas l'appelant.

Cas classique : écrire un journal d'audit qui doit être conservé même si l'opération principale échoue.

java
@Service class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void tracer(String evenement) { auditRepo.save(new Audit(evenement)); }  // commit immédiat
}
⚠️ PiègeElle consomme une 2e connexion du pool pendant que la 1re est suspendue : sous forte charge, risque d'épuisement du pool.
Autres propagations : SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER, NESTEDSUPPORTS utilise une transaction si elle existe ; NOT_SUPPORTED suspend toute transaction ; MANDATORY exige qu'une transaction existe ; NEVER interdit qu'elle existe ; NESTED crée un savepoint.

SUPPORTS utilise une transaction si elle existe ; NOT_SUPPORTED suspend toute transaction ; MANDATORY exige qu'une transaction existe ; NEVER interdit qu'elle existe ; NESTED crée un savepoint.

PropagationTransaction existantePas de transaction
REQUIREDrejointcrée
REQUIRES_NEWsuspend + créecrée
SUPPORTSrejoints'exécute sans
NOT_SUPPORTEDsuspends'exécute sans
MANDATORYrejointexception
NEVERexceptions'exécute sans
NESTEDsavepointcrée
Règles de rollback (rollbackFor)Par défaut, Spring annule la transaction sur RuntimeException et Error, mais commit sur exception checked. rollbackFor étend ce comportement.

Par défaut, Spring annule la transaction sur RuntimeException et Error, mais commit sur exception checked. rollbackFor étend ce comportement.

java
@Transactional(rollbackFor = Exception.class)     // rollback aussi sur les checked
public void importer(Path fichier) throws IOException { … }
⚠️ PiègeAttraper l'exception dans la méthode (try { … } catch (Exception e) { log… }) empêche le rollback : la transaction ne voit jamais l'erreur.
@Transactional(readOnly = true)Marque une transaction en lecture seule : Hibernate désactive le dirty checking (moins de mémoire, plus rapide) et certains drivers routent vers un réplica.

Marque une transaction en lecture seule : Hibernate désactive le dirty checking (moins de mémoire, plus rapide) et certains drivers routent vers un réplica.

java
@Service
@Transactional(readOnly = true)               // défaut pour toute la classe
public class RapportService {
    public List<LigneRapport> mensuel(YearMonth m) { … }
    @Transactional                            // écriture : surcharge locale
    public void archiver(YearMonth m) { … }
}
@Cacheable (abstraction de cache Spring)Met en cache le résultat d'une méthode selon ses paramètres ; les appels suivants avec la même clé ne réexécutent pas la méthode. @CacheEvict invalide.

Met en cache le résultat d'une méthode selon ses paramètres ; les appels suivants avec la même clé ne réexécutent pas la méthode. @CacheEvict invalide.

java
@EnableCaching @Configuration class CacheConfig {}

@Cacheable(cacheNames = "taux", key = "#devise")
public TauxDto taux(String devise) { return client.taux(devise); }   // 1er appel seulement

@CacheEvict(cacheNames = "taux", allEntries = true)
@Scheduled(cron = "0 0 6 * * *")
public void rafraichir() {}
⚠️ PiègeComme @Transactional, @Cacheable passe par un proxy : un appel interne ne lit pas le cache.

7Spring Security8

Spring Security et SecurityFilterChainFramework d'authentification et d'autorisation basé sur une chaîne de filtres Servlet exécutés avant les contrôleurs ; on la configure avec un bean SecurityFilterChain.

Framework d'authentification et d'autorisation basé sur une chaîne de filtres Servlet exécutés avant les contrôleurs ; on la configure avec un bean SecurityFilterChain.

java
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())                       // API stateless JWT
            .cors(Customizer.withDefaults())
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/v1/virements").hasRole("CLIENT")
                .anyRequest().authenticated())
            .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
            .build();
    }
}
CORS (côté Spring)Cross-Origin Resource Sharing : règle du navigateur qui bloque les appels vers un autre domaine sauf si le serveur l'autorise via des en-têtes Access-Control-Allow-*. Ce n'est pas une sécurité d'authentification.

Cross-Origin Resource Sharing : règle du navigateur qui bloque les appels vers un autre domaine sauf si le serveur l'autorise via des en-têtes Access-Control-Allow-*. Ce n'est pas une sécurité d'authentification.

Une SPA Angular sur https://app.banque.sn qui appelle https://api.banque.sn est « cross-origin ». Le navigateur envoie d'abord une requête OPTIONS (preflight) ; Spring doit répondre avec les origines, méthodes et en-têtes autorisés.

java
@Bean
CorsConfigurationSource corsConfigurationSource() {
    var cfg = new CorsConfiguration();
    cfg.setAllowedOrigins(List.of("https://app.banque.sn", "http://localhost:4200"));
    cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
    cfg.setAllowedHeaders(List.of("Authorization", "Content-Type"));
    var source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/**", cfg);
    return source;
}
⚠️ PiègeCORS ne protège que les navigateurs : curl ou Postman l'ignorent. L'authentification reste obligatoire.
CSRFCross-Site Request Forgery : un site malveillant fait envoyer par le navigateur de la victime une requête authentifiée (les cookies partent automatiquement). Se protège par un jeton CSRF ou des cookies SameSite.

Cross-Site Request Forgery : un site malveillant fait envoyer par le navigateur de la victime une requête authentifiée (les cookies partent automatiquement). Se protège par un jeton CSRF ou des cookies SameSite.

Concerne les applications qui s'authentifient par cookie de session. Une API qui exige Authorization: Bearer <JWT> n'est pas exposée : un site tiers ne peut pas ajouter cet en-tête. D'où csrf.disable() en API stateless.

java
// Application à session cookie : garder CSRF, exposer le jeton au front Angular
http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
// Angular lit le cookie XSRF-TOKEN et renvoie le header X-XSRF-TOKEN automatiquement
PasswordEncoder et BCryptBCrypt est une fonction de hachage de mot de passe lente et salée, avec un facteur de coût réglable ; PasswordEncoder est l'interface Spring qui l'encapsule.

BCrypt est une fonction de hachage de mot de passe lente et salée, avec un facteur de coût réglable ; PasswordEncoder est l'interface Spring qui l'encapsule.

Le sel (aléatoire, intégré au hash) empêche les tables précalculées ; la lenteur volontaire (coût 10-12) rend le brute force très coûteux. On ne « déchiffre » jamais un mot de passe : on compare avec matches().

java
@Bean PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();  // {bcrypt} par défaut
}
String hash = encoder.encode("MotDePasse!2026");   // {bcrypt}$2a$10$Qm… (sel inclus)
encoder.matches("MotDePasse!2026", hash);          // true
⚠️ PiègeMD5, SHA-1, SHA-256 sont trop rapides pour des mots de passe (des milliards d'essais par seconde sur GPU).
API stateless (SessionCreationPolicy.STATELESS)Le serveur ne conserve aucune session : chaque requête porte sa preuve d'authentification (JWT). Facilite la montée en charge horizontale (n'importe quel pod peut répondre).

Le serveur ne conserve aucune session : chaque requête porte sa preuve d'authentification (JWT). Facilite la montée en charge horizontale (n'importe quel pod peut répondre).

java
http.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
OAuth2 Resource Server (validation JWT)Configuration Spring Security où l'API valide les JWT émis par un serveur d'autorisation (Keycloak) : signature via JWKS, expiration, émetteur.

Configuration Spring Security où l'API valide les JWT émis par un serveur d'autorisation (Keycloak) : signature via JWKS, expiration, émetteur.

yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.banque.sn/realms/banque   # Spring récupère les clés publiques (JWKS)
java
http.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()));
🎯 En entretienLes rôles Keycloak sont dans realm_access.roles : il faut un JwtAuthenticationConverter pour les transformer en ROLE_… Spring.
@PreAuthorize (sécurité des méthodes)Vérifie une règle d'autorisation (rôle, propriété de la ressource) avant l'exécution d'une méthode, avec une expression SpEL.

Vérifie une règle d'autorisation (rôle, propriété de la ressource) avant l'exécution d'une méthode, avec une expression SpEL.

java
@PreAuthorize("hasRole('CONSEILLER')")
public List<ClientDto> portefeuille() { … }

@PreAuthorize("#compteId == authentication.principal.claims['compte_id'] or hasRole('ADMIN')")
public SoldeDto solde(Long compteId) { … }      // un client ne voit que SON compte
SecurityContextConteneur (par requête) de l'utilisateur authentifié et de ses autorités ; rempli par les filtres de sécurité, lisible partout dans le traitement.

Conteneur (par requête) de l'utilisateur authentifié et de ses autorités ; rempli par les filtres de sécurité, lisible partout dans le traitement.

java
@GetMapping("/moi")
ProfilDto moi(@AuthenticationPrincipal Jwt jwt) {
    return new ProfilDto(jwt.getSubject(), jwt.getClaimAsString("email"));
}
// ou : SecurityContextHolder.getContext().getAuthentication().getName()

📋 Synthèse express — Spring

🗓 Versions Spring Boot

VersionDateJavaNouveautés clés
2.7202211+Dernière branche 2.x, support Kotlin, Actuator amélioré
3.0Déc 202217+Jakarta EE 10 (javax→jakarta), AOT, GraalVM natif
3.2Nov 202317+RestClient, JdbcClient, support des Virtual Threads (opt-in : spring.threads.virtual.enabled)
3.4Nov 202417+Logs structurés, @MockitoBean remplace @MockBean, arrêt gracieux par défaut
3.5Mai 202517+Dernière version mineure de la ligne 3.x
4.0Nov 202517+ (25 recommandé)Spring Framework 7, Jakarta EE 11, Jackson 3, versioning d'API, HTTP Service Clients, JSpecify, JUnit 6
4.1202617+RestTemplate déprécié (Framework 7.1), Spring gRPC, protection SSRF
Migration javax → jakarta : Spring Boot 3+ utilise Jakarta EE 10. Tous les javax.* → jakarta.* (Servlet, Persistence, Validation…)

🔄 IoC & Injection de Dépendances

Constructor Injection (✅ Recommandé)
@Service public class OrderService { private final OrderRepo repo; // Spring 4.3+: @Autowired implicite public OrderService(OrderRepo repo) { this.repo = repo; } }
Scopes des beans
  • singleton — 1 instance/contexte (défaut)
  • prototype — nouvelle instance à chaque inject
  • request — 1/requête HTTP
  • session — 1/session HTTP

🌐 REST API — Bonnes pratiques

@RestController @RequestMapping("/api/v1/orders") public class OrderController { @GetMapping("/{id}") public ResponseEntity<OrderDto> get(@PathVariable Long id) { return service.findById(id) .map(ResponseEntity::ok) .orElseThrow(() -> new NotFoundException("Order not found: " + id)); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public OrderDto create(@Valid @RequestBody CreateOrderRequest req) { return service.create(req); } }

💾 JPA & N+1 Problem

⚠️ Problème N+1 : Charger N entités parent puis faire 1 requête par entité enfant = N+1 requêtes. Solution : @EntityGraph ou JOIN FETCH dans JPQL.
// ❌ N+1 : chaque Order charge ses OrderLines séparément List<Order> orders = repo.findAll(); // 1 requête orders.forEach(o -> log(o.getLines())); // N requêtes lazy // ✅ JOIN FETCH : 1 seule requête @Query("SELECT o FROM Order o JOIN FETCH o.lines WHERE o.status = :s") List<Order> findWithLines(@Param("s") Status s); // ✅ EntityGraph @EntityGraph(attributePaths = {"lines", "customer"}) Optional<Order> findById(Long id);

✅ Validation & Gestion d'erreurs globale

// DTO validé côté contrôleur record CreateUserRequest( @NotBlank String name, @Email String email, @Size(min=8) String password ) {} // Gestionnaire global d'exceptions @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleValidation(MethodArgumentNotValidException ex) { var errors = ex.getBindingResult().getFieldErrors().stream() .map(e -> e.getField() + ": " + e.getDefaultMessage()) .toList(); return new ErrorResponse("VALIDATION_ERROR", errors); } @ExceptionHandler(NotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFound(NotFoundException ex) { return new ErrorResponse("NOT_FOUND", ex.getMessage()); } }

⚠️ @Transactional — Pièges à éviter

⛔ Appel interne (proxy bypass) : Si une méthode @Transactional appelle une autre méthode @Transactional de la même classe, le proxy Spring est court-circuité → la 2e transaction n'est PAS créée !
// ❌ L'appel interne bypasse le proxy Spring @Service public class PaymentService { @Transactional public void processPayment() { debitAccount(); // ← appel interne → PAS de nouvelle transaction creditAccount(); } @Transactional(propagation = REQUIRES_NEW) public void debitAccount() { ... } // Ignoré si appelé en interne ! } // ✅ Solution : séparer en services distincts ou self-injection @Autowired PaymentService self; // proxy injecté self.debitAccount(); // ← passe par le proxy
PropagationComportement
REQUIRED (défaut)Rejoint la transaction existante ou en crée une
REQUIRES_NEWSuspend l'actuelle, crée une nouvelle (commit/rollback indépendant)
SUPPORTSUtilise la transaction si elle existe, sinon sans
NOT_SUPPORTEDSuspend toute transaction active
NEVERException si une transaction est active

🛡️ Spring Security — CORS, CSRF, BCrypt

🌍 CORS

Cross-Origin Resource Sharing. Contrôle quelles origines peuvent appeler l'API depuis un navigateur. N'est pas un mécanisme d'authentification. Configurer côté serveur Spring avec @CrossOrigin ou CorsConfigurationSource.

🛡 CSRF

Cross-Site Request Forgery. Concerne les requêtes authentifiées automatiquement (cookies). Désactivé par défaut en API stateless JWT car pas de cookie de session. Activer pour apps avec session cookie.

🔐 BCrypt

Algorithme adaptatif de hachage. Intègre un salt automatique. Coût configurable (factor 10-12 typique). Jamais MD5/SHA1 pour mots de passe — utilisés pour l'intégrité, pas le stockage de secrets.

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); String hash = encoder.encode(rawPassword); boolean ok = encoder.matches(rawPassword, hash);

🏗 Architecture couches — Règle de dépendance

Controller → Service → Repository → Entity/DB
DTO = contrat d'API (stable) · Entity = contrat de persistance (interne). Ne jamais exposer l'Entity JPA dans la réponse HTTP : fuite de données, couplage fort, risque de lazy load en sérialisation.

🆕 RestClient (Spring Boot 3.2+)

// RestClient remplace RestTemplate (déprécié depuis Spring Framework 7.1 / Boot 4.1) RestClient client = RestClient.builder() .baseUrl("https://api.example.com") .defaultHeader("Authorization", "Bearer " + token) .build(); UserDto user = client.get() .uri("/users/{id}", userId) .retrieve() .body(UserDto.class);
⚡ Quiz Rapide — Spring Boot
1. Quelle annotation Spring définit un bean singleton visible partout dans le contexte ?
A
@Component
B
@Bean
C
@Service
D
Toutes ces réponses créent un bean singleton
✅ D. @Component, @Service, @Repository, @Controller et @Bean créent tous des beans singleton par défaut dans le contexte Spring.
2. Quel est le problème N+1 en JPA et comment le résoudre ?
A
Trop d'indices en base — supprimer les index inutiles
B
N requêtes enfants après 1 requête parent — résoudre avec JOIN FETCH ou @EntityGraph
C
N threads concurrents — utiliser Virtual Threads
D
N+1 colonnes dans une table — normaliser le schéma
✅ B. Charger N entités puis une requête lazy par association = N+1 requêtes. Solution : JOIN FETCH, @EntityGraph, ou batch fetching avec @BatchSize.
3. Spring Boot 4.0 : quelle affirmation est exacte ?
A
Java 25 est le minimum obligatoire
B
Java 17 reste le minimum ; Jackson 3, le versioning d'API et les HTTP Service Clients sont intégrés
C
Les virtual threads sont activés par défaut, sans aucune propriété
D
Spring MVC est abandonné au profit de WebFlux
✅ B. Spring Boot 4.0 (nov. 2025) garde Java 17 comme minimum (Java 25 recommandé), repose sur Spring Framework 7 et Jakarta EE 11, passe à Jackson 3 et intègre le versioning d'API, les HTTP Service Clients et JSpecify. Les virtual threads restent opt-in avec spring.threads.virtual.enabled=true.
4. Que se passe-t-il si une méthode @Transactional en appelle une autre @Transactional dans la même classe ?
A
Une nouvelle transaction indépendante est créée automatiquement
B
L'annotation @Transactional de la méthode appelée est ignorée car l'appel contourne le proxy Spring
C
Spring lance une exception TransactionException
D
Cela fonctionne normalement car Spring instrumente toutes les méthodes
✅ B. Spring AOP fonctionne via proxy (CGLIB). Un appel interne (this.methode()) ne passe pas par le proxy → les aspects (@Transactional, @Cacheable…) sont ignorés. Solution : injecter le service dans lui-même (@Autowired PaymentService self) ou séparer en deux beans distincts.
5. Pourquoi désactive-t-on souvent CSRF en API REST stateless ?
A
CSRF n'existe pas en REST
B
L'API REST utilise des tokens JWT dans les headers, jamais envoyés automatiquement par le navigateur comme les cookies
C
CORS remplace CSRF
D
Spring Security ne supporte pas CSRF pour les API REST
✅ B. CSRF exploite le fait que le navigateur envoie automatiquement les cookies avec chaque requête. Avec JWT dans Authorization: Bearer ... header, un site malveillant ne peut pas injecter ce header. En session cookie, il faut CSRF. En JWT stateless, désactiver CSRF est standard et sûr.
Angular & TypeScript
Standalone, Signals, RxJS, Performance, nouveautés Angular 17 → 22
📖 Cours complet — 90 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/90 maîtrisées

1Nouveautés Angular 17 → 2219

Composant standaloneComposant autonome qui déclare lui-même ses dépendances dans imports, sans NgModule. Introduit en v14, standalone par défaut depuis v19.

Composant autonome qui déclare lui-même ses dépendances dans imports, sans NgModule. Introduit en v14, standalone par défaut depuis v19.

Avant, chaque composant devait être déclaré dans un NgModule, source de confusion (« dans quel module est-il déclaré ? »). Un composant standalone importe directement les composants, directives et pipes qu'il utilise ; l'application démarre avec bootstrapApplication.

typescript
@Component({
  selector: 'app-compte-carte',
  imports: [CurrencyPipe, RouterLink],          // ses dépendances, ici et pas dans un module
  template: `<a [routerLink]="['/comptes', compte().id]">{{ compte().solde | currency:'XOF' }}</a>`,
})
export class CompteCarteComponent {
  compte = input.required<Compte>();
}
🎯 En entretienDepuis v19, standalone: true est implicite : il ne faut plus l'écrire.
bootstrapApplication et providers fonctionnelsPoint d'entrée d'une application standalone : on démarre le composant racine et on enregistre les services globaux avec des fonctions provideXxx().

Point d'entrée d'une application standalone : on démarre le composant racine et on enregistre les services globaux avec des fonctions provideXxx().

typescript
// main.ts
bootstrapApplication(AppComponent, appConfig);

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withComponentInputBinding()),
    provideHttpClient(withInterceptors([authInterceptor, erreurInterceptor])),
  ],
};
Nouveau control flow (@if, @for, @switch)Syntaxe de template intégrée (v17, stable v18) qui remplace *ngIf, *ngFor et ngSwitch : plus lisible, typée, plus rapide et sans import.

Syntaxe de template intégrée (v17, stable v18) qui remplace *ngIf, *ngFor et ngSwitch : plus lisible, typée, plus rapide et sans import.

html
@if (client(); as c) {
  <h2>{{ c.nom }}</h2>
} @else if (chargement()) {
  <p>Chargement…</p>
} @else {
  <p>Aucun client</p>
}

@switch (compte().statut) {
  @case ('ACTIF') { <span class="ok">Actif</span> }
  @case ('GELE')  { <span class="ko">Gelé</span> }
  @default        { <span>Inconnu</span> }
}
🎯 En entretienUne migration automatique existe : ng generate @angular/core:control-flow.
track dans @for (et trackBy)Expression obligatoire dans @for qui identifie chaque élément (souvent son id) : Angular ne recrée que les lignes réellement ajoutées, supprimées ou déplacées.

Expression obligatoire dans @for qui identifie chaque élément (souvent son id) : Angular ne recrée que les lignes réellement ajoutées, supprimées ou déplacées.

Sans identifiant, quand la liste est rechargée depuis l'API (nouveaux objets), Angular détruit et recrée tout le DOM de la liste. Avec track t.id, il réutilise les nœuds existants. trackBy est l'équivalent pour l'ancien *ngFor.

html
@for (t of transactions(); track t.id; let i = $index) {
  <li>{{ i + 1 }}. {{ t.libelle }} — {{ t.montant | number }}</li>
} @empty {
  <li>Aucune transaction</li>
}
@emptyBloc optionnel de @for affiché quand la liste est vide ; remplace un @if séparé.

Bloc optionnel de @for affiché quand la liste est vide ; remplace un @if séparé.

html
@for (b of beneficiaires(); track b.id) { <app-beneficiaire [b]="b" /> }
@empty { <p>Ajoutez votre premier bénéficiaire.</p> }
@defer (vues différées)Bloc de template (v17, stable v18) dont le code est chargé à part (lazy) et affiché seulement quand un déclencheur survient : visibilité, interaction, inactivité, timer, condition.

Bloc de template (v17, stable v18) dont le code est chargé à part (lazy) et affiché seulement quand un déclencheur survient : visibilité, interaction, inactivité, timer, condition.

Le composant lourd (graphique, éditeur) sort du bundle initial : la page s'affiche plus vite. @placeholder s'affiche avant le déclenchement, @loading pendant le chargement, @error en cas d'échec.

html
@defer (on viewport; prefetch on idle) {
  <app-graphique-depenses [donnees]="depenses()" />
} @placeholder (minimum 300ms) {
  <div class="squelette"></div>
} @loading (after 100ms) {
  <p>Chargement du graphique…</p>
} @error {
  <p>Graphique indisponible</p>
}
DéclencheurQuand
on viewportLe placeholder entre dans l'écran
on interactionClic / focus sur le placeholder
on idleLe navigateur est inactif (défaut)
on timer(2s)Après un délai
when cond()Quand une condition devient vraie
Signals : signal()Un signal est un conteneur de valeur réactive : on le lit en l'appelant count(), on l'écrit avec set() ou update(), et Angular sait précisément quels templates en dépendent.

Un signal est un conteneur de valeur réactive : on le lit en l'appelant count(), on l'écrit avec set() ou update(), et Angular sait précisément quels templates en dépendent.

Contrairement à Zone.js qui revérifie tout l'arbre après chaque événement, Angular met à jour uniquement les vues qui lisent le signal modifié. Introduits en v16, stables depuis v17 (et l'essentiel de l'API réactive stable en v20).

typescript
export class PanierComponent {
  quantite = signal(1);
  articles = signal<Article[]>([]);

  ajouter(a: Article) {
    this.articles.update(liste => [...liste, a]);   // nouvelle référence (immutabilité)
  }
  reset() { this.quantite.set(1); }
}
html
<p>Quantité : {{ quantite() }}</p>
⚠️ PiègeNe pas muter l'objet contenu (articles().push(a)) : la référence ne change pas, le signal ne notifie pas.
computed()Signal dérivé, en lecture seule, recalculé automatiquement (et paresseusement, avec mémorisation) quand les signaux qu'il lit changent.

Signal dérivé, en lecture seule, recalculé automatiquement (et paresseusement, avec mémorisation) quand les signaux qu'il lit changent.

typescript
articles = signal<Article[]>([]);
total = computed(() => this.articles().reduce((s, a) => s + a.prix * a.qte, 0));
fraisLivraison = computed(() => this.total() > 50_000 ? 0 : 2_000);
🎯 En entretienUn computed ne doit pas avoir d'effet de bord (pas d'appel HTTP, pas de set() d'un autre signal).
effect()Fonction exécutée à chaque fois qu'un signal qu'elle lit change ; sert aux effets de bord (log, localStorage, synchronisation avec une API non Angular).

Fonction exécutée à chaque fois qu'un signal qu'elle lit change ; sert aux effets de bord (log, localStorage, synchronisation avec une API non Angular).

typescript
constructor() {
  effect(() => {
    localStorage.setItem('filtre', JSON.stringify(this.filtre()));   // relu à chaque changement
  });
}
⚠️ PiègeÀ utiliser en dernier recours : pour dériver une valeur, computed() ; pour réinitialiser un état dépendant, linkedSignal(). Un effect qui écrit des signaux crée vite des boucles.
linkedSignal()Signal modifiable dont la valeur est réinitialisée automatiquement quand une source change (v19, stable v20). Ex. : la sélection revient au premier élément quand la liste change.

Signal modifiable dont la valeur est réinitialisée automatiquement quand une source change (v19, stable v20). Ex. : la sélection revient au premier élément quand la liste change.

typescript
comptes = input.required<Compte[]>();
compteSelectionne = linkedSignal(() => this.comptes()[0]);   // reset si la liste change
choisir(c: Compte) { this.compteSelectionne.set(c); }       // reste modifiable à la main
input(), output() et model()Versions « signal » de @Input, @Output et du two-way binding (stables depuis v19) : input() renvoie un signal en lecture seule, output() émet un événement, model() est un input modifiable par l'enfant.

Versions « signal » de @Input, @Output et du two-way binding (stables depuis v19) : input() renvoie un signal en lecture seule, output() émet un événement, model() est un input modifiable par l'enfant.

typescript
export class FiltreComponent {
  options = input<string[]>([]);              // [options]="..."
  min = input.required<number>();             // obligatoire, vérifié à la compilation
  recherche = model('');                      // [(recherche)]="terme" côté parent
  valide = output<string>();                  // (valide)="onValide($event)"

  envoyer() { this.valide.emit(this.recherche()); }
}
html
<app-filtre [min]="0" [(recherche)]="terme" (valide)="charger($event)" />
resource() et httpResource()API pour charger des données asynchrones sous forme de signaux (value, isLoading, error) qui se rechargent quand leurs paramètres changent. Déclarées prêtes pour la production en v22.

API pour charger des données asynchrones sous forme de signaux (value, isLoading, error) qui se rechargent quand leurs paramètres changent. Déclarées prêtes pour la production en v22.

typescript
clientId = input.required<number>();
client = httpResource<Client>(() => `/api/v1/clients/${this.clientId()}`);  // rechargé si l'id change
html
@if (client.isLoading()) { <p>Chargement…</p> }
@else if (client.error()) { <p>Erreur</p> }
@else { <h2>{{ client.value()?.nom }}</h2> }
Zone.jsBibliothèque qui « patche » les API asynchrones du navigateur (événements, setTimeout, XHR) pour prévenir Angular qu'il doit lancer la détection de changements.

Bibliothèque qui « patche » les API asynchrones du navigateur (événements, setTimeout, XHR) pour prévenir Angular qu'il doit lancer la détection de changements.

Simple mais coûteux : à chaque clic ou timer, Angular revérifie potentiellement tout l'arbre de composants. Elle complique aussi les stack traces et pèse sur le bundle. Depuis v21, elle n'est plus incluse par défaut dans les nouveaux projets.

ZonelessMode sans Zone.js (stable depuis v20.2, défaut des nouveaux projets en v21) : la détection de changements est déclenchée par les signals, les événements du template, l'async pipe et markForCheck.

Mode sans Zone.js (stable depuis v20.2, défaut des nouveaux projets en v21) : la détection de changements est déclenchée par les signals, les événements du template, l'async pipe et markForCheck.

typescript
// projet existant (≤ v20) : activation explicite
bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()],
});
⚠️ PiègeUn composant qui modifie un champ « classique » dans un setTimeout ou un callback tiers ne se rafraîchit plus : passer par un signal.
Signal FormsNouvelle API de formulaires basée sur les signals : le modèle est un signal, form() construit l'arbre de champs et un schéma déclare les validations. Expérimentale en v21, stable en v22.

Nouvelle API de formulaires basée sur les signals : le modèle est un signal, form() construit l'arbre de champs et un schéma déclare les validations. Expérimentale en v21, stable en v22.

typescript
import { form, required, email, minLength, FormField } from '@angular/forms/signals';

@Component({
  imports: [FormField],
  template: `
    <input [formField]="inscription.nom" />
    <input [formField]="inscription.email" />
    @if (inscription.email().invalid()) { <small>Email invalide</small> }
  `,
})
export class InscriptionComponent {
  modele = signal({ nom: '', email: '' });
  inscription = form(this.modele, (p) => {
    required(p.nom); minLength(p.nom, 2);
    required(p.email); email(p.email);
  });
}
⚠️ PiègeL'API a été renommée plusieurs fois pendant la phase expérimentale ([control] → [field] → [formField]) : vérifier la doc de la version utilisée.
OnPush par défaut (v22) et ChangeDetectionStrategy.EagerDepuis Angular 22, les nouvelles applications utilisent OnPush par défaut ; l'ancienne stratégie « Default » est renommée Eager.

Depuis Angular 22, les nouvelles applications utilisent OnPush par défaut ; l'ancienne stratégie « Default » est renommée Eager.

Cela entérine la direction prise avec les signals : un composant ne se met à jour que si ses inputs changent, qu'un événement survient dans son template ou qu'un signal lu change.

@Service (Angular 22)Nouveau décorateur (v22) plus court pour un service singleton global, alternative à @Injectable({ providedIn: 'root' }).

Nouveau décorateur (v22) plus court pour un service singleton global, alternative à @Injectable({ providedIn: 'root' }).

typescript
@Service()
export class TauxService {
  private http = inject(HttpClient);
}
// équivalent historique :
@Injectable({ providedIn: 'root' }) export class TauxService2 {}
inject()Fonction qui récupère une dépendance depuis l'injecteur courant, utilisable dans un champ, un constructeur, un guard ou un intercepteur fonctionnel.

Fonction qui récupère une dépendance depuis l'injecteur courant, utilisable dans un champ, un constructeur, un guard ou un intercepteur fonctionnel.

typescript
export class ClientService {
  private http = inject(HttpClient);
  private config = inject(APP_CONFIG);
}
export const adminGuard: CanActivateFn = () => inject(AuthService).estAdmin();
Vitest (runner de tests par défaut)Depuis Angular 21, les nouveaux projets utilisent Vitest à la place de Karma/Jasmine : exécution plus rapide, mode watch, API compatible Jest.

Depuis Angular 21, les nouveaux projets utilisent Vitest à la place de Karma/Jasmine : exécution plus rapide, mode watch, API compatible Jest.

typescript
import { describe, it, expect } from 'vitest';
describe('total', () => {
  it('additionne les lignes', () => {
    expect(total([{ prix: 1000, qte: 2 }])).toBe(2000);
  });
});

2Composants & templates12

Composant (@Component)Brique d'interface : une classe TypeScript (état + logique) associée à un template HTML et des styles, identifiée par un sélecteur.

Brique d'interface : une classe TypeScript (état + logique) associée à un template HTML et des styles, identifiée par un sélecteur.

typescript
@Component({
  selector: 'app-solde',                         // <app-solde />
  template: `<p>Solde : {{ solde() | number }} XOF</p>`,
  styles: `p { font-weight: 600 }`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SoldeComponent {
  solde = input(0);
}
NgModule (historique)Ancien mécanisme de regroupement (declarations, imports, providers) ; remplacé par les composants standalone, mais encore présent dans beaucoup d'applications existantes.

Ancien mécanisme de regroupement (declarations, imports, providers) ; remplacé par les composants standalone, mais encore présent dans beaucoup d'applications existantes.

typescript
@NgModule({
  declarations: [ListeComptesComponent],
  imports: [CommonModule, RouterModule.forChild(routes)],
})
export class ComptesModule {}
🎯 En entretienCommonModule fournissait *ngIf, *ngFor et les pipes communs ; avec le nouveau control flow, on n'en a souvent plus besoin.
Interpolation {{ }}Affiche dans le template la valeur d'une expression du composant, convertie en texte et échappée (protection XSS).

Affiche dans le template la valeur d'une expression du composant, convertie en texte et échappée (protection XSS).

html
<h1>Bonjour {{ utilisateur().prenom }}</h1>
<p>Frais : {{ montant() * 0.01 | number:'1.0-0' }} XOF</p>
Property binding [ ][propriete]="expression" lie une propriété DOM ou un input de composant à une valeur du composant (sens composant → template).

[propriete]="expression" lie une propriété DOM ou un input de composant à une valeur du composant (sens composant → template).

html
<button [disabled]="enCours()">Valider</button>
<img [src]="avatarUrl()" [alt]="nom()" />
<app-graph [donnees]="serie()" />
<div [class.alerte]="solde() < 0" [style.width.%]="progression()"></div>
Event binding ( )(evenement)="methode($event)" exécute du code du composant quand un événement DOM ou un output survient (sens template → composant).

(evenement)="methode($event)" exécute du code du composant quand un événement DOM ou un output survient (sens template → composant).

html
<button (click)="valider()">Valider</button>
<input (keyup.enter)="chercher(saisie.value)" #saisie />
<app-carte (selectionne)="ouvrir($event)" />
Two-way binding [( )] et ngModel[(x)]="valeur" combine property binding et event binding : la valeur circule dans les deux sens. ngModel l'applique aux champs de formulaire (template-driven).

[(x)]="valeur" combine property binding et event binding : la valeur circule dans les deux sens. ngModel l'applique aux champs de formulaire (template-driven).

html
<input [(ngModel)]="recherche" />          <!-- nécessite FormsModule -->
<app-filtre [(recherche)]="terme" />       <!-- avec un model() côté enfant -->
🎯 En entretien[(x)] est du sucre pour [x]="v" (xChange)="v = $event" (« banana in a box »).
DirectiveClasse qui ajoute un comportement à un élément existant : directive d'attribut (change l'apparence/le comportement) ou structurelle (ajoute/retire du DOM, ex. l'ancien *ngIf).

Classe qui ajoute un comportement à un élément existant : directive d'attribut (change l'apparence/le comportement) ou structurelle (ajoute/retire du DOM, ex. l'ancien *ngIf).

typescript
@Directive({ selector: '[appMontantNegatif]', host: { '[class.negatif]': 'valeur() < 0' } })
export class MontantNegatifDirective {
  valeur = input.required<number>({ alias: 'appMontantNegatif' });
}
html
<span [appMontantNegatif]="solde()">{{ solde() }}</span>
PipeFonction de transformation utilisée dans le template avec | (date, currency, number, uppercase…) ; on peut créer les siens. « Pure » par défaut : recalculée seulement si l'entrée change.

Fonction de transformation utilisée dans le template avec | (date, currency, number, uppercase…) ; on peut créer les siens. « Pure » par défaut : recalculée seulement si l'entrée change.

typescript
@Pipe({ name: 'iban' })
export class IbanPipe implements PipeTransform {
  transform(v: string): string { return v.replace(/(.{4})/g, '$1 ').trim(); }   // groupes de 4
}
html
{{ compte.iban | iban }} · {{ compte.ouverture | date:'dd/MM/yyyy' }} · {{ solde | currency:'XOF':'symbol':'1.0-0' }}
@InputDécorateur (API classique) qui expose une propriété d'un composant enfant pour recevoir une valeur du parent ; required: true (v16) la rend obligatoire.

Décorateur (API classique) qui expose une propriété d'un composant enfant pour recevoir une valeur du parent ; required: true (v16) la rend obligatoire.

typescript
export class CardComponent {
  @Input({ required: true }) user!: User;
  @Input() compact = false;
}
🎯 En entretienEn code récent, préférer input() / input.required() qui donnent un signal.
@Output et EventEmitter@Output expose un événement que l'enfant émet vers le parent via un EventEmitter ; le parent l'écoute avec (nomEvenement).

@Output expose un événement que l'enfant émet vers le parent via un EventEmitter ; le parent l'écoute avec (nomEvenement).

typescript
export class CardComponent {
  @Output() selected = new EventEmitter<User>();
  select(u: User) { this.selected.emit(u); }
}
html
<app-card [user]="u" (selected)="onUserSelected($event)" />
Projection de contenu (ng-content)Permet au parent d'insérer son propre HTML à l'intérieur d'un composant enfant (comme un « slot »), éventuellement à plusieurs emplacements via select.

Permet au parent d'insérer son propre HTML à l'intérieur d'un composant enfant (comme un « slot »), éventuellement à plusieurs emplacements via select.

typescript
@Component({
  selector: 'app-panneau',
  template: `
    <header><ng-content select="[titre]" /></header>
    <section><ng-content /></section>`,
})
export class PanneauComponent {}
html
<app-panneau>
  <h3 titre>Mes comptes</h3>
  <app-liste-comptes />
</app-panneau>
ViewChild et viewChild()Donne au composant une référence vers un élément ou un composant enfant de son template ; disponible à partir de ngAfterViewInit (ou via le signal viewChild()).

Donne au composant une référence vers un élément ou un composant enfant de son template ; disponible à partir de ngAfterViewInit (ou via le signal viewChild()).

typescript
export class RechercheComponent {
  champ = viewChild.required<ElementRef<HTMLInputElement>>('saisie');
  focus() { this.champ().nativeElement.focus(); }
}
html
<input #saisie />

3Cycle de vie des composants10

Lifecycle hooks (vue d'ensemble)Méthodes qu'Angular appelle à des moments précis de la vie d'un composant : création, changement d'inputs, rendu, destruction.

Méthodes qu'Angular appelle à des moments précis de la vie d'un composant : création, changement d'inputs, rendu, destruction.

Ordre : constructor → ngOnChanges → ngOnInit → ngDoCheck → ngAfterContentInit → ngAfterContentChecked → ngAfterViewInit → ngAfterViewChecked → … → ngOnDestroy. Avec les signals, on en utilise de moins en moins (computed/effect/DestroyRef remplacent beaucoup de cas).

constructor (composant)Appelé à la création de la classe : sert uniquement à l'injection de dépendances ; les inputs ne sont pas encore disponibles.

Appelé à la création de la classe : sert uniquement à l'injection de dépendances ; les inputs ne sont pas encore disponibles.

typescript
constructor(private service: ClientService) {}   // ✅ DI
// ❌ ne pas appeler l'API ici : les @Input ne sont pas encore renseignés
ngOnChanges et SimpleChangesAppelé avant ngOnInit puis à chaque fois qu'un @Input change de référence ; reçoit un objet SimpleChanges (valeur précédente / actuelle).

Appelé avant ngOnInit puis à chaque fois qu'un @Input change de référence ; reçoit un objet SimpleChanges (valeur précédente / actuelle).

typescript
ngOnChanges(changes: SimpleChanges) {
  if (changes['compteId'] && !changes['compteId'].firstChange) {
    this.chargerTransactions(changes['compteId'].currentValue);
  }
}
🎯 En entretienAvec input(), on remplace souvent ngOnChanges par un computed() ou un httpResource() qui réagit tout seul.
ngOnInitAppelé une fois, après le premier ngOnChanges : les inputs sont disponibles. C'est l'endroit pour charger les données initiales et s'abonner aux services.

Appelé une fois, après le premier ngOnChanges : les inputs sont disponibles. C'est l'endroit pour charger les données initiales et s'abonner aux services.

typescript
export class ListeComptesComponent implements OnInit {
  private service = inject(CompteService);
  comptes = signal<Compte[]>([]);
  ngOnInit() {
    this.service.lister().subscribe(c => this.comptes.set(c));
  }
}
ngDoCheckAppelé à chaque cycle de détection de changements ; permet une détection personnalisée. Très fréquent, donc à éviter ou garder ultra léger.

Appelé à chaque cycle de détection de changements ; permet une détection personnalisée. Très fréquent, donc à éviter ou garder ultra léger.

ngAfterContentInitAppelé une fois après que le contenu projeté (ng-content) a été initialisé ; @ContentChild est alors disponible.

Appelé une fois après que le contenu projeté (ng-content) a été initialisé ; @ContentChild est alors disponible.

ngAfterViewInitAppelé une fois après le rendu de la vue du composant et de ses enfants : les @ViewChild sont disponibles, on peut manipuler le DOM (initialiser un graphique, un focus).

Appelé une fois après le rendu de la vue du composant et de ses enfants : les @ViewChild sont disponibles, on peut manipuler le DOM (initialiser un graphique, un focus).

typescript
@ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;
ngAfterViewInit() { this.chart = new Chart(this.canvas.nativeElement, this.config); }
ngOnDestroyAppelé juste avant la destruction du composant : on y libère les ressources (désabonnements, timers, listeners globaux) pour éviter les fuites mémoire.

Appelé juste avant la destruction du composant : on y libère les ressources (désabonnements, timers, listeners globaux) pour éviter les fuites mémoire.

typescript
private sub?: Subscription;
private timer?: number;
ngOnInit() {
  this.sub = this.flux.subscribe(…);
  this.timer = window.setInterval(() => this.rafraichir(), 30_000);
}
ngOnDestroy() {
  this.sub?.unsubscribe();
  clearInterval(this.timer);
}
DestroyRef et takeUntilDestroyedOpérateur RxJS (v16, @angular/core/rxjs-interop) qui se désabonne automatiquement à la destruction du composant ; plus besoin de ngOnDestroy pour les abonnements.

Opérateur RxJS (v16, @angular/core/rxjs-interop) qui se désabonne automatiquement à la destruction du composant ; plus besoin de ngOnDestroy pour les abonnements.

typescript
export class NotificationsComponent {
  constructor() {
    inject(NotificationService).flux$
      .pipe(takeUntilDestroyed())                // dans un contexte d'injection
      .subscribe(n => this.afficher(n));
  }
}
Fuite mémoire côté AngularUn abonnement, un interval ou un listener jamais libéré continue de vivre après la destruction du composant : mémoire qui grimpe et traitements fantômes.

Un abonnement, un interval ou un listener jamais libéré continue de vivre après la destruction du composant : mémoire qui grimpe et traitements fantômes.

Les observables HTTP se terminent seuls (une valeur puis complete), mais pas les flux infinis : interval, fromEvent, WebSocket, Subjects de service, valueChanges des formulaires. Remèdes : async pipe, takeUntilDestroyed, toSignal, ou ngOnDestroy.

4Formulaires6

Reactive Forms (FormGroup, FormControl)Formulaires définis dans le TypeScript : un FormGroup regroupe des FormControl avec valeur, validateurs et état (valid, dirty, touched) ; le template s'y relie par formControlName.

Formulaires définis dans le TypeScript : un FormGroup regroupe des FormControl avec valeur, validateurs et état (valid, dirty, touched) ; le template s'y relie par formControlName.

Avantages : typés (depuis v14), testables sans DOM, adaptés aux formulaires complexes et dynamiques (FormArray pour une liste de bénéficiaires…).

typescript
form = new FormGroup({
  nom: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
  montant: new FormControl<number | null>(null, [Validators.required, Validators.min(100)]),
});
html
<form [formGroup]="form" (ngSubmit)="valider()">
  <input formControlName="nom" />
  <input formControlName="montant" type="number" />
  <button [disabled]="form.invalid">Envoyer</button>
</form>
FormBuilderService qui raccourcit la création des formulaires réactifs ; NonNullableFormBuilder évite que reset() remette les valeurs à null.

Service qui raccourcit la création des formulaires réactifs ; NonNullableFormBuilder évite que reset() remette les valeurs à null.

typescript
private fb = inject(NonNullableFormBuilder);
form = this.fb.group({
  name:  ['', [Validators.required, Validators.minLength(2)]],
  email: ['', [Validators.required, Validators.email]],
  age:   [18, [Validators.min(18)]],
});
ValidatorsFonctions de validation fournies par Angular : required, min, max, minLength, maxLength, email, pattern. Chaque erreur apparaît dans control.errors.

Fonctions de validation fournies par Angular : required, min, max, minLength, maxLength, email, pattern. Chaque erreur apparaît dans control.errors.

html
@if (form.controls.email.hasError('email') && form.controls.email.touched) {
  <small>Adresse email invalide</small>
}
Validateur personnaliséFonction qui reçoit un contrôle et renvoie null (valide) ou un objet d'erreur ; peut aussi porter sur un groupe (ex. confirmation de mot de passe).

Fonction qui reçoit un contrôle et renvoie null (valide) ou un objet d'erreur ; peut aussi porter sur un groupe (ex. confirmation de mot de passe).

typescript
export const telephoneSenegal: ValidatorFn = (c) =>
  /^(\+221)?7[05678]\d{7}$/.test(c.value ?? '') ? null : { telephone: true };

export const memesMotsDePasse: ValidatorFn = (g) =>
  g.get('mdp')?.value === g.get('confirmation')?.value ? null : { differents: true };
Template-driven formsFormulaires définis dans le HTML avec ngModel et des attributs de validation (required, minlength) ; simples pour un petit formulaire, moins testables et moins typés.

Formulaires définis dans le HTML avec ngModel et des attributs de validation (required, minlength) ; simples pour un petit formulaire, moins testables et moins typés.

html
<form #f="ngForm" (ngSubmit)="envoyer(f.value)">
  <input name="email" [(ngModel)]="email" required email />
  <button [disabled]="f.invalid">OK</button>
</form>
value vs getRawValue()form.value exclut les champs désactivés ; form.getRawValue() renvoie toutes les valeurs, y compris désactivées, avec un type complet.

form.value exclut les champs désactivés ; form.getRawValue() renvoie toutes les valeurs, y compris désactivées, avec un type complet.

typescript
this.form.controls.iban.disable();
this.form.value;          // { nom: 'Awa' }            (iban absent)
this.form.getRawValue();  // { nom: 'Awa', iban: 'SN…' }

5Services, HTTP & routage8

Service et @InjectableClasse qui porte la logique réutilisable (appels HTTP, état partagé) ; @Injectable({ providedIn: 'root' }) en fait un singleton disponible dans toute l'application.

Classe qui porte la logique réutilisable (appels HTTP, état partagé) ; @Injectable({ providedIn: 'root' }) en fait un singleton disponible dans toute l'application.

typescript
@Injectable({ providedIn: 'root' })
export class ClientService {
  private http = inject(HttpClient);
  lister() { return this.http.get<ClientDto[]>('/api/v1/clients'); }
}
🎯 En entretienLes composants affichent ; les services récupèrent et transforment les données.
Injection de dépendances Angular (injecteurs hiérarchiques)Angular résout les dépendances en remontant une hiérarchie d'injecteurs (élément → composant parent → route → racine) ; un provider déclaré sur un composant donne une instance propre à ce composant et ses enfants.

Angular résout les dépendances en remontant une hiérarchie d'injecteurs (élément → composant parent → route → racine) ; un provider déclaré sur un composant donne une instance propre à ce composant et ses enfants.

typescript
@Component({
  selector: 'app-assistant-virement',
  providers: [EtatVirementService],     // nouvelle instance par assistant ouvert
})
export class AssistantVirementComponent {}
HttpClientService Angular pour les appels HTTP ; renvoie des Observables typés, parse le JSON et passe par les intercepteurs.

Service Angular pour les appels HTTP ; renvoie des Observables typés, parse le JSON et passe par les intercepteurs.

typescript
create(data: CreateClientRequest): Observable<ClientDto> {
  return this.http.post<ClientDto>('/api/v1/clients', data);
}
search(ville: string) {
  return this.http.get<Page<ClientDto>>('/api/v1/clients', { params: { ville, page: 0, size: 20 } });
}
⚠️ PiègeL'appel ne part qu'au subscribe() (ou via async pipe / toSignal) : un Observable HTTP est « froid ».
Intercepteur HTTP (HttpInterceptorFn)Fonction qui intercepte chaque requête/réponse HttpClient pour un traitement transversal : ajout du token, gestion globale des erreurs, loader, journalisation.

Fonction qui intercepte chaque requête/réponse HttpClient pour un traitement transversal : ajout du token, gestion globale des erreurs, loader, journalisation.

Les requêtes sont immuables : on les clone() pour les modifier.

typescript
export const erreurInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  return next(req).pipe(
    catchError((err: HttpErrorResponse) => {
      if (err.status === 401) router.navigate(['/login']);
      return throwError(() => err);
    })
  );
};
// enregistrement : provideHttpClient(withInterceptors([authInterceptor, erreurInterceptor]))
Router et routesLe routeur associe des URL à des composants, affichés dans <router-outlet> ; routerLink navigue sans recharger la page.

Le routeur associe des URL à des composants, affichés dans <router-outlet> ; routerLink navigue sans recharger la page.

typescript
export const routes: Routes = [
  { path: '', redirectTo: 'comptes', pathMatch: 'full' },
  { path: 'comptes', component: ListeComptesComponent },
  { path: 'comptes/:id', component: DetailCompteComponent },   // id lu via input() avec withComponentInputBinding
  { path: '**', component: PageIntrouvableComponent },
];
Lazy loading des routesChargement à la demande du code d'une route (loadComponent / loadChildren) : le bundle initial est plus petit, la première page s'affiche plus vite.

Chargement à la demande du code d'une route (loadComponent / loadChildren) : le bundle initial est plus petit, la première page s'affiche plus vite.

typescript
{ path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES) },
{ path: 'rapports', loadComponent: () => import('./rapports/rapports.component').then(m => m.RapportsComponent) },
Guard (CanActivateFn)Fonction exécutée avant d'activer une route pour l'autoriser ou rediriger (connexion, rôle, modifications non sauvegardées).

Fonction exécutée avant d'activer une route pour l'autoriser ou rediriger (connexion, rôle, modifications non sauvegardées).

typescript
export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  return auth.estConnecte() ? true : inject(Router).createUrlTree(['/login']);
};
{ path: 'virements', canActivate: [authGuard], loadComponent: … }
⚠️ PiègeUn guard n'est qu'un confort d'interface : le code est dans le navigateur et contournable. La vraie autorisation se fait côté Spring.
Environnements (environment.ts)Fichiers de configuration remplacés au build selon la cible (dev/prod) : URL de l'API, options. Ils finissent dans le bundle public.

Fichiers de configuration remplacés au build selon la cible (dev/prod) : URL de l'API, options. Ils finissent dans le bundle public.

typescript
// src/environments/environment.prod.ts
export const environment = { production: true, apiUrl: 'https://api.banque.sn' };
⚠️ PiègeJamais de secret (clé API privée, mot de passe) dans un environment : tout le bundle est lisible par n'importe qui.

6RxJS18

ObservableFlux de valeurs asynchrones (0, 1 ou plusieurs) qui ne démarre qu'à l'abonnement et peut se terminer (complete) ou échouer (error).

Flux de valeurs asynchrones (0, 1 ou plusieurs) qui ne démarre qu'à l'abonnement et peut se terminer (complete) ou échouer (error).

typescript
const tic$ = new Observable<number>(observer => {
  let i = 0;
  const id = setInterval(() => observer.next(i++), 1000);
  return () => clearInterval(id);         // nettoyage à l'unsubscribe
});
const sub = tic$.subscribe({ next: v => console.log(v), error: e => console.error(e) });
sub.unsubscribe();
🎯 En entretienConvention : suffixe $ pour les variables qui contiennent un Observable (clients$).
subscribe et Subscriptionsubscribe() démarre l'Observable et fournit les callbacks next/error/complete ; il renvoie une Subscription dont unsubscribe() arrête le flux.

subscribe() démarre l'Observable et fournit les callbacks next/error/complete ; il renvoie une Subscription dont unsubscribe() arrête le flux.

Observable vs PromiseUne Promise produit une seule valeur, démarre immédiatement et ne s'annule pas ; un Observable peut produire plusieurs valeurs, est paresseux, annulable et composable avec des opérateurs.

Une Promise produit une seule valeur, démarre immédiatement et ne s'annule pas ; un Observable peut produire plusieurs valeurs, est paresseux, annulable et composable avec des opérateurs.

typescript
const p = fetch('/api/taux');                  // la requête part TOUT DE SUITE
const o$ = this.http.get('/api/taux');         // rien ne part…
const sub = o$.subscribe();                    // …jusqu'ici
sub.unsubscribe();                             // annule la requête HTTP en cours
const valeur = await firstValueFrom(o$);       // conversion vers Promise si besoin
Observable froid vs chaudFroid : chaque abonné déclenche sa propre exécution (ex. requête HTTP rejouée à chaque subscribe). Chaud : la source émet indépendamment des abonnés, qui partagent les mêmes valeurs (événements, Subject).

Froid : chaque abonné déclenche sa propre exécution (ex. requête HTTP rejouée à chaque subscribe). Chaud : la source émet indépendamment des abonnés, qui partagent les mêmes valeurs (événements, Subject).

⚠️ PiègeDeux async pipes sur le même http.get(...) = deux requêtes HTTP. Solution : shareReplay(1) ou toSignal.
Subject et BehaviorSubjectUn Subject est à la fois Observable et émetteur (next) : il diffuse à tous ses abonnés. BehaviorSubject garde la dernière valeur et la donne immédiatement à tout nouvel abonné.

Un Subject est à la fois Observable et émetteur (next) : il diffuse à tous ses abonnés. BehaviorSubject garde la dernière valeur et la donne immédiatement à tout nouvel abonné.

typescript
@Injectable({ providedIn: 'root' })
export class SessionService {
  private utilisateur = new BehaviorSubject<Utilisateur | null>(null);
  utilisateur$ = this.utilisateur.asObservable();        // lecture seule à l'extérieur
  connecter(u: Utilisateur) { this.utilisateur.next(u); }
}
🎯 En entretienPour un état simple, un signal() dans le service remplace avantageusement le BehaviorSubject.
pipe() et opérateurspipe() chaîne des opérateurs (fonctions pures) qui transforment un Observable en un autre : map, filter, switchMap, catchError…

pipe() chaîne des opérateurs (fonctions pures) qui transforment un Observable en un autre : map, filter, switchMap, catchError…

typescript
this.clients$ = this.http.get<ClientDto[]>('/api/v1/clients').pipe(
  map(liste => liste.filter(c => c.actif)),
  tap(liste => console.log(liste.length, 'clients actifs')),
  catchError(() => of([]))
);
map, filter et tapmap transforme chaque valeur ; filter laisse passer celles qui respectent une condition ; tap exécute un effet de bord (log) sans changer la valeur.

map transforme chaque valeur ; filter laisse passer celles qui respectent une condition ; tap exécute un effet de bord (log) sans changer la valeur.

typescript
montants$.pipe(
  filter(m => m > 0),
  map(m => Math.round(m * 1.01)),
  tap(m => console.debug('montant avec frais', m))
);
switchMapPour chaque valeur, lance un nouvel Observable et annule le précédent s'il n'est pas terminé : « la dernière demande gagne ». Idéal pour l'autocomplétion et les changements de paramètres de route.

Pour chaque valeur, lance un nouvel Observable et annule le précédent s'il n'est pas terminé : « la dernière demande gagne ». Idéal pour l'autocomplétion et les changements de paramètres de route.

typescript
resultats$ = this.recherche.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(terme => this.api.chercher(terme))     // annule la requête précédente
);
⚠️ PiègeNe pas utiliser pour des écritures (POST de paiement) : une requête annulée côté client a peut-être déjà été traitée côté serveur.
mergeMapLance un Observable interne pour chaque valeur et les exécute tous en parallèle, sans annuler ; l'ordre des résultats n'est pas garanti.

Lance un Observable interne pour chaque valeur et les exécute tous en parallèle, sans annuler ; l'ordre des résultats n'est pas garanti.

typescript
from(fichiers).pipe(
  mergeMap(f => this.upload(f), 3)    // 3 envois simultanés au maximum
).subscribe();
concatMapTraite les Observables internes l'un après l'autre, dans l'ordre : le suivant ne démarre que lorsque le précédent est terminé.

Traite les Observables internes l'un après l'autre, dans l'ordre : le suivant ne démarre que lorsque le précédent est terminé.

typescript
from(operations).pipe(
  concatMap(op => this.http.post('/api/v1/operations', op))   // ordre garanti
).subscribe();
exhaustMapIgnore les nouvelles valeurs tant que l'Observable interne en cours n'est pas terminé. Parfait contre le double clic sur « Payer ».

Ignore les nouvelles valeurs tant que l'Observable interne en cours n'est pas terminé. Parfait contre le double clic sur « Payer ».

typescript
this.clicPayer$.pipe(
  exhaustMap(() => this.paiement.payer(this.panier()))   // les clics pendant l'appel sont ignorés
).subscribe();
combineLatestCombine plusieurs Observables et émet un tableau des dernières valeurs dès que l'un d'eux émet (après que tous ont émis au moins une fois).

Combine plusieurs Observables et émet un tableau des dernières valeurs dès que l'un d'eux émet (après que tous ont émis au moins une fois).

typescript
vue$ = combineLatest([this.filtre$, this.tri$, this.page$]).pipe(
  switchMap(([filtre, tri, page]) => this.api.lister({ filtre, tri, page }))
);
forkJoinLance plusieurs Observables en parallèle et émet une seule fois, quand tous sont terminés, avec leurs dernières valeurs (l'équivalent de Promise.all).

Lance plusieurs Observables en parallèle et émet une seule fois, quand tous sont terminés, avec leurs dernières valeurs (l'équivalent de Promise.all).

typescript
forkJoin({
  client: this.api.client(id),
  comptes: this.api.comptes(id),
  cartes: this.api.cartes(id),
}).subscribe(({ client, comptes, cartes }) => this.initialiser(client, comptes, cartes));
⚠️ PiègeSi un seul Observable échoue, tout échoue ; s'il ne se termine jamais (flux infini), forkJoin n'émet jamais.
debounceTime et distinctUntilChangeddebounceTime attend un silence (ex. 300 ms) avant d'émettre la dernière valeur ; distinctUntilChanged ignore une valeur identique à la précédente. Ensemble, ils évitent des appels inutiles pendant la frappe.

debounceTime attend un silence (ex. 300 ms) avant d'émettre la dernière valeur ; distinctUntilChanged ignore une valeur identique à la précédente. Ensemble, ils évitent des appels inutiles pendant la frappe.

catchError et retrycatchError intercepte une erreur pour renvoyer une valeur de repli ou relancer une erreur enrichie ; retry réessaie l'Observable source un certain nombre de fois.

catchError intercepte une erreur pour renvoyer une valeur de repli ou relancer une erreur enrichie ; retry réessaie l'Observable source un certain nombre de fois.

typescript
this.http.get<Taux>('/api/taux').pipe(
  retry({ count: 2, delay: 500 }),
  catchError((err: HttpErrorResponse) => {
    this.toast.erreur('Taux indisponible');
    return of(TAUX_PAR_DEFAUT);
  })
);
shareReplayPartage une seule exécution entre tous les abonnés et rejoue les N dernières valeurs aux nouveaux venus ; sert de cache simple dans un service.

Partage une seule exécution entre tous les abonnés et rejoue les N dernières valeurs aux nouveaux venus ; sert de cache simple dans un service.

typescript
@Injectable({ providedIn: 'root' })
export class ReferentielService {
  devises$ = inject(HttpClient).get<string[]>('/api/v1/devises').pipe(
    shareReplay({ bufferSize: 1, refCount: false })     // 1 seul appel HTTP pour toute l'app
  );
}
async pipePipe de template qui s'abonne à un Observable (ou une Promise), affiche sa dernière valeur et se désabonne automatiquement à la destruction.

Pipe de template qui s'abonne à un Observable (ou une Promise), affiche sa dernière valeur et se désabonne automatiquement à la destruction.

html
@if (clients$ | async; as clients) {
  @for (c of clients; track c.id) { <li>{{ c.nom }}</li> }
}
toSignal et toObservablePonts entre RxJS et les signals (@angular/core/rxjs-interop) : toSignal s'abonne et expose la dernière valeur comme signal (désabonnement automatique) ; toObservable fait l'inverse.

Ponts entre RxJS et les signals (@angular/core/rxjs-interop) : toSignal s'abonne et expose la dernière valeur comme signal (désabonnement automatique) ; toObservable fait l'inverse.

typescript
clients = toSignal(this.service.lister(), { initialValue: [] });
terme = signal('');
resultats = toSignal(
  toObservable(this.terme).pipe(debounceTime(300), switchMap(t => this.api.chercher(t))),
  { initialValue: [] }
);

7Performance4

Détection de changements (change detection)Mécanisme qui compare l'état des composants à ce qui est affiché et met à jour le DOM. Historiquement déclenché par Zone.js sur tout l'arbre ; aujourd'hui de plus en plus ciblé grâce aux signals.

Mécanisme qui compare l'état des composants à ce qui est affiché et met à jour le DOM. Historiquement déclenché par Zone.js sur tout l'arbre ; aujourd'hui de plus en plus ciblé grâce aux signals.

OnPushStratégie où un composant n'est revérifié que si une de ses références d'input change, qu'un événement survient dans son template, qu'un signal lu change ou que l'async pipe émet. Défaut des nouveaux projets en v22.

Stratégie où un composant n'est revérifié que si une de ses références d'input change, qu'un événement survient dans son template, qu'un signal lu change ou que l'async pipe émet. Défaut des nouveaux projets en v22.

typescript
@Component({ changeDetection: ChangeDetectionStrategy.OnPush, … })
export class ListeComponent {
  items = input<Item[]>([]);
}
// parent : this.items = [...this.items, nouvel];   ✅ nouvelle référence
//          this.items.push(nouvel);                 ❌ même référence : l'enfant OnPush ne bouge pas
SSR et hydratationLe Server-Side Rendering génère le HTML côté serveur (affichage rapide, SEO) ; l'hydratation réutilise ensuite ce DOM côté navigateur au lieu de le reconstruire. L'hydratation incrémentale (avec @defer) n'active que les parties nécessaires.

Le Server-Side Rendering génère le HTML côté serveur (affichage rapide, SEO) ; l'hydratation réutilise ensuite ce DOM côté navigateur au lieu de le reconstruire. L'hydratation incrémentale (avec @defer) n'active que les parties nécessaires.

bash
ng new banque-public --ssr
typescript
provideClientHydration(withIncrementalHydration())
Budgets de bundleSeuils définis dans angular.json : le build avertit ou échoue si le JavaScript initial dépasse une taille donnée. Garde-fou contre l'alourdissement progressif.

Seuils définis dans angular.json : le build avertit ou échoue si le JavaScript initial dépasse une taille donnée. Garde-fou contre l'alourdissement progressif.

json
"budgets": [{ "type": "initial", "maximumWarning": "500kB", "maximumError": "1MB" }]

8TypeScript13

TypeScriptSur-ensemble typé de JavaScript : les types sont vérifiés à la compilation puis effacés ; le navigateur exécute du JavaScript.

Sur-ensemble typé de JavaScript : les types sont vérifiés à la compilation puis effacés ; le navigateur exécute du JavaScript.

Les types n'existent pas à l'exécution : une réponse HTTP typée ClientDto n'est pas vérifiée. Si le backend renvoie autre chose, TypeScript ne le verra pas (valider avec Zod si nécessaire).

typescript
function frais(montant: number, taux = 0.01): number { return Math.round(montant * taux); }
frais('1000');   // ❌ erreur de compilation
interface vs typeLes deux décrivent la forme d'un objet. interface peut être étendue et fusionnée (declaration merging) ; type permet en plus unions, intersections, tuples et types calculés.

Les deux décrivent la forme d'un objet. interface peut être étendue et fusionnée (declaration merging) ; type permet en plus unions, intersections, tuples et types calculés.

typescript
interface Client { id: number; nom: string; }
interface ClientVip extends Client { conseiller: string; }

type Statut = 'ACTIF' | 'GELE' | 'CLOTURE';              // union : impossible avec interface
type CompteDetaille = Compte & { transactions: Transaction[] };   // intersection
Generics (génériques)Types paramétrés (<T>) qui rendent une fonction ou une classe réutilisable pour plusieurs types tout en gardant le typage.

Types paramétrés (<T>) qui rendent une fonction ou une classe réutilisable pour plusieurs types tout en gardant le typage.

typescript
interface Page<T> { content: T[]; totalElements: number; number: number; }

function premier<T>(liste: T[]): T | undefined { return liste[0]; }
const c = premier<Client>(clients);       // c : Client | undefined
keyof et contraintes extendskeyof T donne l'union des clés d'un type ; K extends keyof T contraint un paramètre générique à être une clé valide.

keyof T donne l'union des clés d'un type ; K extends keyof T contraint un paramètre générique à être une clé valide.

typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }
getProperty(client, 'nom');      // string
getProperty(client, 'adresse');  // ❌ erreur : 'adresse' n'est pas une clé de Client
Utility types : Partial, Required, Readonly, Pick, Omit, RecordTypes utilitaires intégrés qui dérivent un nouveau type d'un existant : tout optionnel, tout requis, lecture seule, sous-ensemble, exclusion, dictionnaire.

Types utilitaires intégrés qui dérivent un nouveau type d'un existant : tout optionnel, tout requis, lecture seule, sous-ensemble, exclusion, dictionnaire.

typescript
interface Client { id: number; nom: string; email: string; telephone?: string; }

type MajClient   = Partial<Omit<Client, 'id'>>;      // PATCH : tout optionnel sauf id exclu
type ClientCree  = Required<Client>;                 // telephone devient obligatoire
type ClientLu    = Readonly<Client>;                 // aucune propriété modifiable
type Resume      = Pick<Client, 'id' | 'nom'>;       // { id; nom }
type SoldesParDevise = Record<'XOF' | 'EUR', number>; // { XOF: number; EUR: number }
Union types et types littérauxUn type union A | B accepte l'une ou l'autre forme ; combiné à des littéraux ('ACTIF' | 'GELE'), il remplace avantageusement les chaînes libres.

Un type union A | B accepte l'une ou l'autre forme ; combiné à des littéraux ('ACTIF' | 'GELE'), il remplace avantageusement les chaînes libres.

typescript
type Devise = 'XOF' | 'EUR' | 'USD';
function convertir(m: number, d: Devise) { … }
convertir(100, 'GBP');   // ❌ refusé à la compilation
Discriminated unionUnion d'objets qui partagent une propriété littérale commune (le discriminant) ; TypeScript en déduit automatiquement le type exact dans chaque branche.

Union d'objets qui partagent une propriété littérale commune (le discriminant) ; TypeScript en déduit automatiquement le type exact dans chaque branche.

typescript
type Result<T> =
  | { success: true; data: T }
  | { success: false; error: string };

function afficher(r: Result<Client>) {
  if (r.success) console.log(r.data.nom);   // ici TS sait que data existe
  else console.error(r.error);              // et ici que error existe
}
Narrowing et type guardsLe narrowing affine un type dans une branche (typeof, instanceof, in, ===) ; un type guard est une fonction x is T qui enseigne un test personnalisé au compilateur.

Le narrowing affine un type dans une branche (typeof, instanceof, in, ===) ; un type guard est une fonction x is T qui enseigne un test personnalisé au compilateur.

typescript
function estErreurApi(e: unknown): e is { code: string; message: string } {
  return typeof e === 'object' && e !== null && 'code' in e;
}
if (estErreurApi(err)) console.log(err.code);    // err est typé
any vs unknownany désactive toute vérification (à bannir) ; unknown accepte n'importe quelle valeur mais oblige à vérifier son type avant de l'utiliser.

any désactive toute vérification (à bannir) ; unknown accepte n'importe quelle valeur mais oblige à vérifier son type avant de l'utiliser.

typescript
const a: any = JSON.parse(texte);   a.foo.bar();          // compile, plante à l'exécution
const u: unknown = JSON.parse(texte);
u.foo;                                                    // ❌ erreur : il faut d'abord affiner
if (typeof u === 'object' && u && 'foo' in u) { … }
Optional chaining (?.) et nullish coalescing (??)a?.b renvoie undefined au lieu de planter si a est null/undefined ; x ?? defaut remplace seulement null/undefined (contrairement à || qui remplace aussi 0 et '').

a?.b renvoie undefined au lieu de planter si a est null/undefined ; x ?? defaut remplace seulement null/undefined (contrairement à || qui remplace aussi 0 et '').

typescript
const ville = client?.adresse?.ville ?? 'Non renseignée';
const remise = saisie ?? 0;        // 0 saisi reste 0 (avec || il deviendrait la valeur par défaut)
Non-null assertion (!) et assignation certainevaleur! dit au compilateur « je garantis que ce n'est pas null » ; champ!: Type dit qu'un champ sera initialisé plus tard (ex. un @Input). À utiliser avec parcimonie : aucun contrôle à l'exécution.

valeur! dit au compilateur « je garantis que ce n'est pas null » ; champ!: Type dit qu'un champ sera initialisé plus tard (ex. un @Input). À utiliser avec parcimonie : aucun contrôle à l'exécution.

typescript
@Input({ required: true }) user!: User;          // initialisé par Angular
const el = document.getElementById('total')!;   // risqué si l'élément n'existe pas
Mode strictEnsemble d'options du compilateur (strictNullChecks, noImplicitAny…) qui interdit les approximations ; activé par défaut dans les projets Angular récents.

Ensemble d'options du compilateur (strictNullChecks, noImplicitAny…) qui interdit les approximations ; activé par défaut dans les projets Angular récents.

json
{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true } }
DécorateursFonctions préfixées par @ qui ajoutent des métadonnées à une classe ou un membre ; Angular s'en sert pour déclarer composants, directives, services et inputs.

Fonctions préfixées par @ qui ajoutent des métadonnées à une classe ou un membre ; Angular s'en sert pour déclarer composants, directives, services et inputs.

typescript
@Component({ selector: 'app-x', template: '' })   // métadonnées lues par le compilateur Angular
export class XComponent {}

📋 Synthèse express — Angular

🆕 Nouveautés Angular 17 → 22

v17
Nov 2023
  • Control flow @if/@for
  • @defer
  • Signals stables
v18
Mai 2024
  • Control flow et @defer stables
  • Zoneless expérimental
v19
Nov 2024
  • Standalone par défaut
  • input()/output()/model() stables
  • linkedSignal, resource
v20
Mai 2025
  • effect, linkedSignal, toSignal stables
  • Zoneless stable (20.2)
v21
Nov 2025
  • Zoneless par défaut
  • Signal Forms (expérimental)
  • Vitest par défaut
v22
Juin 2026 🆕
  • OnPush par défaut
  • Signal Forms stables
  • httpResource stable, @Service
🚦 Nouveau Control Flow (v17)
<!-- @if / @for / @switch --> @if (user) { <p>{{user.name}}</p> } @else { <p>Non connecté</p> } @for (item of items; track item.id) { <li>{{item.name}}</li> } @empty { <li>Aucun résultat</li> }
⚡ Signals (v16+, stable v17)
import { signal, computed, effect } from '@angular/core'; count = signal(0); doubled = computed(() => this.count() * 2); constructor() { effect(() => { console.log('count:', this.count()); }); } increment() { this.count.update(c => c + 1); }
🏝 Standalone Components (v14+)
@Component({ selector: 'app-root', standalone: true, imports: [CommonModule, RouterOutlet], template: `<router-outlet />` }) export class AppComponent {}
⚙️ Defer / Lazy (v17)
<!-- Chargement différé --> @defer (on viewport) { <heavy-component /> } @placeholder { <p>Chargement...</p> }

🔗 Data Binding — Les 4 types

TypeSyntaxeDirectionCas d'usage
Interpolation{{ titre }}Component → TemplateAfficher une valeur
Property Binding[disabled]="loading"Component → TemplatePropriété DOM, Input
Event Binding(click)="save()"Template → ComponentÉvénements utilisateur
Two-Way Binding[(ngModel)]="name"BidirectionnelFormulaires template-driven

🔄 Lifecycle Hooks — Ordre d'exécution

// Ordre garanti par Angular constructor() // Injection DI uniquement — pas de logique ngOnChanges(changes) // Chaque fois qu'un @Input change (avant OnInit) ngOnInit() // Init après premier rendu des inputs ← utiliser ici ngDoCheck() // Après chaque cycle de détection ngAfterContentInit() // Après ng-content projeté ngAfterViewInit() // Après rendu des children ngOnDestroy() // Nettoyage : unsubscribe, clearInterval ← critique
⚠️ ngOnDestroy obligatoire si souscription à un Observable, un interval, ou un event listener global. Sinon memory leak !
Avec les Signals (Angular 17+) et takeUntilDestroyed() de @angular/core/rxjs-interop, la gestion du cycle de vie est automatique.

📡 @Input / @Output — Communication parent-enfant

// Composant enfant @Component({ selector: 'app-card' }) export class CardComponent { @Input({ required: true }) user!: User; // Input requis Angular 16+ @Output() selected = new EventEmitter<User>(); select() { this.selected.emit(this.user); } } // Template parent <app-card [user]="currentUser" (selected)="onUserSelected($event)" />

📋 Reactive Forms

// Service-driven, typé, testable import { FormBuilder, Validators } from '@angular/forms'; form = this.fb.group({ name: ['', [Validators.required, Validators.minLength(2)]], email: ['', [Validators.required, Validators.email]], age: [null, [Validators.min(18)]] }); onSubmit() { if (this.form.invalid) return; const data = this.form.getRawValue(); // valeurs typées this.service.save(data).subscribe(); } // Template <input formControlName="email"> <span *ngIf="form.get('email')?.hasError('email')">Email invalide</span>

🔌 Intercepteurs HTTP

// Intercepteur fonctionnel (Angular 17+) export const authInterceptor: HttpInterceptorFn = (req, next) => { const token = inject(AuthService).getToken(); if (token) { const authReq = req.clone({ setHeaders: { 'Authorization': `Bearer ${token}` } }); return next(authReq); } return next(req); }; // Enregistrement dans bootstrapApplication provideHttpClient(withInterceptors([authInterceptor]))

⚖️ Observable vs Promise

CritèreObservable (RxJS)Promise
ValeursMultiple (flux)Une seule
Annulation✅ unsubscribe()❌ non natif
Lazy✅ (subscribe déclenche)❌ démarre immédiatement
Opérateurs✅ map, filter, switchMap…❌ limité (.then, .catch)
Cas d'usageHTTP, events, WebSocket, formulairesOpération async unique

🔄 RxJS — Opérateurs essentiels

OpérateurComportementCas d'usage
switchMapAnnule l'observable précédentAutocomplete, dernière requête gagne
mergeMapParallélise tous les observablesUpload multiple simultané
concatMapSéquentiel, attend la finOrdre garanti (save séquentiel)
exhaustMapIgnore les nouvelles émissions si en coursSubmit bouton (évite double-submit)
combineLatestÉmet quand tous ont émis au moins 1 foisCombiner plusieurs filtres
forkJoinAttend la fin de tousChargement initial multiple
shareReplay(1)Partage + rejoue la dernière valeurCache HTTP dans service
takeUntilDestroyedUnsubscribe auto (v16+)Gestion cycle de vie

⚡ Performance Angular

🎯 OnPush Change Detection

Par défaut Angular vérifie tout le composant tree. OnPush ne vérifie que si Input change (référence) ou Observable émet.

@Component({ changeDetection: ChangeDetectionStrategy.OnPush })
🔑 TrackBy / track

Sans trackBy, Angular re-rend tous les items sur chaque changement. Avec track item.id, seuls les items modifiés sont re-rendus.

🔷 TypeScript — Types essentiels

// Utility Types courants type Partial<T> // toutes props optionnelles type Required<T> // toutes props requises type Readonly<T> // toutes props readonly type Pick<T, K> // sous-ensemble de propriétés type Omit<T, K> // exclure des propriétés type Record<K, V> // dictionnaire typé // Discriminated Union type Result<T> = | { success: true; data: T } | { success: false; error: string }; // Générics contraints function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }
⚡ Quiz Rapide — Angular & TS
1. Quel opérateur RxJS utiliser pour une barre de recherche autocomplete ?
A
mergeMap
B
concatMap
C
switchMap
D
exhaustMap
✅ C. switchMap. Il annule l'appel HTTP précédent si l'utilisateur tape à nouveau. Ainsi seule la dernière recherche est conservée. Souvent combiné avec debounceTime(300) et distinctUntilChanged().
2. Que sont les Signals Angular et quel avantage offrent-ils ?
A
Des observables RxJS enrichis
B
Des valeurs réactives primitives permettant un change detection granulaire sans Zone.js
C
Des événements DOM custom
D
Des intercepteurs HTTP améliorés
✅ B. Les Signals sont des conteneurs de valeurs réactives. Angular sait exactement quels composants dépendent d'un signal et ne les met à jour que ceux-là. Cela permet d'envisager un Angular sans Zone.js (Zoneless).
3. Différence entre interface et type en TypeScript ?
A
interface = runtime, type = compile-time seulement
B
Aucune différence fonctionnelle
C
interface peut être étendue et déclarée plusieurs fois (merging), type permet unions/intersections/mapped types
D
type est obsolète en TS 5+
✅ C. interface supporte la déclaration multiple (declaration merging) et extends plus naturellement les classes. type est plus puissant pour les unions (A | B), intersections (A & B) et mapped types. En pratique, interface pour les objets/classes, type pour tout le reste.
4. Dans quel hook de cycle de vie doit-on initialiser les données d'un composant Angular ?
A
constructor()
B
ngOnChanges()
C
ngOnInit()
D
ngAfterViewInit()
✅ C. ngOnInit(). Le constructeur sert uniquement à l'injection DI. ngOnInit est appelé après l'initialisation des @Input, c'est le bon endroit pour charger les données, souscrire aux services, etc. ngAfterViewInit sert quand on a besoin d'accéder au DOM (ViewChild).
5. Quelle est la différence entre Reactive Forms et Template-Driven Forms en Angular ?
A
Les Reactive Forms ne supportent pas la validation
B
Reactive Forms : contrôle dans le TypeScript (typé, testable). Template-Driven : logique dans le template (ngModel, moins testable)
C
Template-Driven est recommandé pour les projets complexes
D
Il n'y a pas de différence fonctionnelle
✅ B. Reactive Forms (FormBuilder, FormGroup) : logique dans le composant TypeScript, entièrement testables, typage fort, meilleur pour les formulaires complexes et dynamiques. Template-Driven (ngModel) : plus simple mais logique dans le HTML, moins testable, adapté aux petits formulaires.
Microservices & Architecture
SAGA, CQRS, Event Sourcing, Circuit Breaker, Hexagonal Architecture
📖 Cours complet — 54 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/54 maîtrisées

1Styles d'architecture7

MonolitheApplication déployée comme un seul artefact (un JAR, une base) qui contient toutes les fonctionnalités. Simple à développer, tester et déployer au départ.

Application déployée comme un seul artefact (un JAR, une base) qui contient toutes les fonctionnalités. Simple à développer, tester et déployer au départ.

Le monolithe n'est pas un défaut : transactions ACID locales, appels en mémoire (nanosecondes), un seul pipeline. Il devient problématique quand il est mal structuré (« big ball of mud ») ou quand des équipes nombreuses se bloquent mutuellement sur le même code et le même déploiement.

Monolithe modulaireUn seul déployable, mais découpé en modules métier aux frontières strictes (API publiques, pas d'accès direct aux tables des autres). Souvent le meilleur point de départ.

Un seul déployable, mais découpé en modules métier aux frontières strictes (API publiques, pas d'accès direct aux tables des autres). Souvent le meilleur point de départ.

On obtient l'essentiel de la clarté des microservices sans leur complexité opérationnelle, et l'on peut extraire un module en service plus tard. Spring Modulith vérifie ces frontières par des tests.

java
// Test qui échoue si un module accède aux classes internes d'un autre
class ArchitectureTest {
    @Test void verifierModules() { ApplicationModules.of(BanqueApplication.class).verify(); }
}
MicroserviceService autonome, centré sur une capacité métier, avec sa propre base de données, déployable et scalable indépendamment, qui communique par le réseau (HTTP, messages).

Service autonome, centré sur une capacité métier, avec sa propre base de données, déployable et scalable indépendamment, qui communique par le réseau (HTTP, messages).

Bénéfices : déploiements indépendants, équipes autonomes, scalabilité ciblée, choix technologiques locaux. Coûts : réseau non fiable, cohérence à terme, observabilité distribuée, tests plus complexes, infrastructure (Kubernetes, CI/CD par service).

🎯 En entretienRéponse d'entretien attendue d'un tech lead : « je commence en monolithe modulaire et je découpe quand un besoin réel d'autonomie ou de scalabilité le justifie ».
Bounded Context (DDD)Frontière à l'intérieur de laquelle un modèle métier et son vocabulaire ont un sens précis ; c'est la bonne unité pour découper des microservices.

Frontière à l'intérieur de laquelle un modèle métier et son vocabulaire ont un sens précis ; c'est la bonne unité pour découper des microservices.

Le mot « Client » n'a pas le même sens pour le service KYC (identité, pièces justificatives) que pour le service Crédit (scoring, capacité de remboursement). Chaque contexte a son propre modèle ; ils communiquent par des contrats explicites.

Database per serviceChaque microservice possède sa base et est le seul à y accéder ; les autres passent par son API ou ses événements.

Chaque microservice possède sa base et est le seul à y accéder ; les autres passent par son API ou ses événements.

Une base partagée recrée un couplage fort (un changement de schéma casse plusieurs services). Le prix : plus de jointure SQL entre services, il faut composer les données via API ou répliquer par événements.

Loi de Conway« Les organisations produisent des systèmes dont la structure reproduit leurs structures de communication. » Le découpage technique suit le découpage des équipes.

« Les organisations produisent des systèmes dont la structure reproduit leurs structures de communication. » Le découpage technique suit le découpage des équipes.

Conséquence pratique (« Inverse Conway Maneuver ») : on organise les équipes selon les domaines métier que l'on veut voir apparaître dans l'architecture.

Strangler Fig (migration progressive)Migrer un système legacy pièce par pièce : une façade (gateway) route certaines URL vers le nouveau service, le reste vers l'ancien, jusqu'à extinction de l'ancien.

Migrer un système legacy pièce par pièce : une façade (gateway) route certaines URL vers le nouveau service, le reste vers l'ancien, jusqu'à extinction de l'ancien.

yaml
routes:
  - id: virements-nouveau
    uri: http://virements-service:8080
    predicates: [ Path=/api/virements/** ]     # déjà migré
  - id: legacy
    uri: http://core-banking-legacy:8080
    predicates: [ Path=/** ]                    # tout le reste

2Communication entre services6

Communication synchrone vs asynchroneSynchrone : l'appelant attend la réponse (REST, gRPC) ; asynchrone : il publie un message ou un événement et continue (Kafka, RabbitMQ).

Synchrone : l'appelant attend la réponse (REST, gRPC) ; asynchrone : il publie un message ou un événement et continue (Kafka, RabbitMQ).

Synchrone (REST/gRPC)Asynchrone (messages)
Couplage temporelFort (l'autre doit être disponible)Faible
Réponse immédiateOuiNon (eventual)
Gestion de chargePics transmisLissés par la file
DébogageSimpleTraçage distribué nécessaire
API GatewayPoint d'entrée unique devant les microservices : routage, authentification, rate limiting, CORS, agrégation, terminaison TLS.

Point d'entrée unique devant les microservices : routage, authentification, rate limiting, CORS, agrégation, terminaison TLS.

Le front Angular parle à une seule URL ; la gateway redirige vers le bon service. Outils : Spring Cloud Gateway, Kong, NGINX, AWS API Gateway.

⚠️ PiègeNe pas y mettre de logique métier : elle deviendrait un nouveau monolithe et un point unique de défaillance.
Service DiscoveryMécanisme qui permet à un service de trouver l'adresse réseau des instances d'un autre service, qui changent en permanence (scaling, redémarrages).

Mécanisme qui permet à un service de trouver l'adresse réseau des instances d'un autre service, qui changent en permanence (scaling, redémarrages).

Sur Kubernetes, c'est natif : un Service k8s a un nom DNS stable (http://comptes-service:8080). Hors Kubernetes : Eureka, Consul.

Configuration centralisée (Config Server)Externaliser la configuration de tous les services dans un dépôt central (Git, Vault, ConfigMaps) pour la modifier sans reconstruire les images.

Externaliser la configuration de tous les services dans un dépôt central (Git, Vault, ConfigMaps) pour la modifier sans reconstruire les images.

yaml
spring:
  config:
    import: "configserver:http://config:8888"   # Spring Cloud Config
Service mesh (Istio, Linkerd)Couche d'infrastructure (un proxy « sidecar » à côté de chaque service) qui gère le trafic réseau : mTLS, retries, timeouts, routage canary, métriques — sans toucher au code.

Couche d'infrastructure (un proxy « sidecar » à côté de chaque service) qui gère le trafic réseau : mTLS, retries, timeouts, routage canary, métriques — sans toucher au code.

Utile à grande échelle (dizaines de services) ; ajoute de la complexité et de la latence pour une petite équipe.

Les illusions de l'informatique distribuéeHuit hypothèses fausses que l'on fait souvent : le réseau est fiable, la latence est nulle, la bande passante infinie, le réseau sûr, la topologie fixe, un seul administrateur, transport gratuit, réseau homogène.

Huit hypothèses fausses que l'on fait souvent : le réseau est fiable, la latence est nulle, la bande passante infinie, le réseau sûr, la topologie fixe, un seul administrateur, transport gratuit, réseau homogène.

Chaque appel réseau peut échouer, être lent ou réussir sans que l'appelant le sache : d'où timeouts, retries, idempotence et circuit breakers.

3Données distribuées11

Transaction distribuéeOpération qui modifie des données dans plusieurs services ou bases et devrait être « tout ou rien ». Sans base commune, il n'y a plus de transaction ACID unique.

Opération qui modifie des données dans plusieurs services ou bases et devrait être « tout ou rien ». Sans base commune, il n'y a plus de transaction ACID unique.

Exemple : commande → réservation de stock (service Stock) → paiement (service Paiement) → expédition. Solutions : SAGA (cohérence à terme) plutôt que 2PC.

2PC (Two-Phase Commit)Protocole où un coordinateur demande à tous les participants de se préparer (phase 1) puis de valider (phase 2). Garantit l'atomicité mais bloque les ressources et dépend du coordinateur.

Protocole où un coordinateur demande à tous les participants de se préparer (phase 1) puis de valider (phase 2). Garantit l'atomicité mais bloque les ressources et dépend du coordinateur.

Peu adapté aux microservices : verrous longs, faible disponibilité, peu supporté par les brokers et bases NoSQL. On lui préfère les SAGA.

Cohérence à terme (eventual consistency)Les données des différents services finissent par être cohérentes, mais pas instantanément : pendant un court laps de temps, deux vues peuvent diverger.

Les données des différents services finissent par être cohérentes, mais pas instantanément : pendant un court laps de temps, deux vues peuvent diverger.

L'interface doit l'assumer : « Virement en cours de traitement » plutôt qu'un solde faux. On le paie en simplicité, on le gagne en disponibilité et en découplage (théorème CAP).

SAGASuite de transactions locales, une par service ; si une étape échoue, on exécute des transactions de compensation pour annuler les étapes précédentes.

Suite de transactions locales, une par service ; si une étape échoue, on exécute des transactions de compensation pour annuler les étapes précédentes.

text
Commande créée → Stock réservé → Paiement débité → ✅ Commande confirmée
                               ↘ Paiement refusé → compensation : libérer le stock → Commande annulée
🎯 En entretienDeux styles : Chorégraphie (événements) et Orchestration (coordinateur central).
Chorégraphie (SAGA)Style de SAGA sans chef : chaque service réagit aux événements des autres et publie les siens. Couplage faible, mais flux global difficile à visualiser et à déboguer.

Style de SAGA sans chef : chaque service réagit aux événements des autres et publie les siens. Couplage faible, mais flux global difficile à visualiser et à déboguer.

java
@KafkaListener(topics = "stock.reserve")
public void onStockReserve(StockReserve evt) {
    var resultat = paiementService.debiter(evt.commandeId(), evt.montant());
    kafka.send(resultat.ok() ? "paiement.accepte" : "paiement.refuse", evt.commandeId(), resultat);
}
Orchestration (SAGA)Style de SAGA où un orchestrateur central envoie les commandes à chaque service, attend les réponses et déclenche les compensations. Flux explicite et traçable, au prix d'un composant central.

Style de SAGA où un orchestrateur central envoie les commandes à chaque service, attend les réponses et déclenche les compensations. Flux explicite et traçable, au prix d'un composant central.

Outils : un service dédié avec une machine à états, Camunda/Zeebe (BPMN), Temporal.

java
public void executer(Commande c) {
    stock.reserver(c);
    try { paiement.debiter(c); }
    catch (PaiementRefuseException e) { stock.liberer(c); commande.annuler(c); return; }
    commande.confirmer(c);
}
Transaction de compensationOpération métier qui annule l'effet d'une étape déjà validée (rembourser, libérer un stock) ; ce n'est pas un rollback technique, l'étape a bien eu lieu.

Opération métier qui annule l'effet d'une étape déjà validée (rembourser, libérer un stock) ; ce n'est pas un rollback technique, l'étape a bien eu lieu.

⚠️ PiègeUne compensation peut elle-même échouer : elle doit être idempotente et relancée jusqu'au succès.
Outbox pattern (Transactional Outbox)Écrire l'événement à publier dans une table « outbox » dans la même transaction que la donnée métier ; un relais le publie ensuite sur Kafka. Évite de modifier la base sans publier (ou l'inverse).

Écrire l'événement à publier dans une table « outbox » dans la même transaction que la donnée métier ; un relais le publie ensuite sur Kafka. Évite de modifier la base sans publier (ou l'inverse).

Le problème : repo.save() puis kafka.send() n'est pas atomique. Si Kafka est indisponible après le commit, l'événement est perdu. Avec l'outbox, les deux écritures sont dans la même transaction SQL ; un processus (ou Debezium par CDC) lit la table et publie.

java
@Transactional
public void creerVirement(Virement v) {
    virementRepo.save(v);
    outboxRepo.save(new OutboxEvent("virement.cree", v.id(), json(v)));   // même transaction
}
// Relais : @Scheduled qui lit les lignes non publiées, envoie à Kafka, marque "publié"
🎯 En entretienLa publication est « au moins une fois » : les consommateurs doivent être idempotents.
CQRSSéparer le modèle d'écriture (commandes, règles métier) du modèle de lecture (requêtes, vues optimisées), éventuellement avec des bases différentes synchronisées par événements.

Séparer le modèle d'écriture (commandes, règles métier) du modèle de lecture (requêtes, vues optimisées), éventuellement avec des bases différentes synchronisées par événements.

Utile quand les besoins de lecture (tableaux de bord, recherches multicritères) divergent fortement de l'écriture. Prix : deux modèles, synchronisation, cohérence à terme.

java
// Écriture : agrégat avec règles métier
commandHandler.handle(new EffectuerVirement(source, dest, montant));
// Lecture : vue dénormalisée, alimentée par les événements, interrogée directement
List<ReleveLigne> releve = releveQueryRepo.findByCompteOrderByDateDesc(compte);
Event SourcingStocker la suite des événements (CompteOuvert, Crédité, Débité…) au lieu de l'état courant ; l'état est reconstruit en rejouant les événements.

Stocker la suite des événements (CompteOuvert, Crédité, Débité…) au lieu de l'état courant ; l'état est reconstruit en rejouant les événements.

Avantages : historique complet et audit naturel, possibilité de reconstruire n'importe quel état passé, nouvelles projections à partir de l'historique. Inconvénients : complexité, évolution des schémas d'événements, besoin de snapshots.

java
BigDecimal solde = evenements.stream().map(e -> switch (e) {
    case Credite c -> c.montant();
    case Debite d  -> d.montant().negate();
    default        -> BigDecimal.ZERO;
}).reduce(BigDecimal.ZERO, BigDecimal::add);
Événement de domaineFait métier passé et immuable, nommé au passé (VirementEffectué), publié pour que d'autres parties du système réagissent.

Fait métier passé et immuable, nommé au passé (VirementEffectué), publié pour que d'autres parties du système réagissent.

java
public record VirementEffectue(UUID id, String source, String dest, BigDecimal montant, Instant date) {}

4Résilience12

Panne en cascadeUn service lent ou en panne bloque les threads de ses appelants, qui deviennent lents à leur tour, jusqu'à faire tomber tout le système.

Un service lent ou en panne bloque les threads de ses appelants, qui deviennent lents à leur tour, jusqu'à faire tomber tout le système.

Remèdes : timeouts courts, circuit breaker, bulkhead, dégradation gracieuse.

TimeoutDurée maximale d'attente d'un appel réseau ; sans timeout, un service lent peut bloquer indéfiniment les threads de l'appelant.

Durée maximale d'attente d'un appel réseau ; sans timeout, un service lent peut bloquer indéfiniment les threads de l'appelant.

java
var factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(Duration.ofSeconds(2));
factory.setReadTimeout(Duration.ofSeconds(3));
RestClient client = RestClient.builder().requestFactory(factory).build();
🎯 En entretienLe timeout de l'appelant doit être plus court que celui de son propre appelant (budget de temps décroissant).
Retry et backoff exponentielRéessayer automatiquement un appel en échec transitoire, en espaçant les tentatives de plus en plus (100 ms, 200 ms, 400 ms…) pour ne pas surcharger le service qui récupère.

Réessayer automatiquement un appel en échec transitoire, en espaçant les tentatives de plus en plus (100 ms, 200 ms, 400 ms…) pour ne pas surcharger le service qui récupère.

Ne réessayer que les erreurs transitoires (timeout, 503) et les opérations idempotentes. Limiter le nombre de tentatives (3).

yaml
resilience4j.retry.instances.operateur:
  max-attempts: 3
  wait-duration: 200ms
  enable-exponential-backoff: true
  exponential-backoff-multiplier: 2
  retry-exceptions: [ java.io.IOException, java.util.concurrent.TimeoutException ]
JitterPetite part aléatoire ajoutée aux délais (retries, TTL de cache) pour que des milliers de clients ne réessaient pas tous au même instant (effet « troupeau »).

Petite part aléatoire ajoutée aux délais (retries, TTL de cache) pour que des milliers de clients ne réessaient pas tous au même instant (effet « troupeau »).

text
délai = base × 2^tentative + aléatoire(0..100 ms)
Circuit BreakerDisjoncteur logiciel : après trop d'échecs, il « s'ouvre » et rejette immédiatement les appels vers le service défaillant pendant un temps, puis teste prudemment son rétablissement.

Disjoncteur logiciel : après trop d'échecs, il « s'ouvre » et rejette immédiatement les appels vers le service défaillant pendant un temps, puis teste prudemment son rétablissement.

États : Closed (normal, échecs comptés) → Open (seuil dépassé, appels rejetés directement, fallback) → Half-Open (après un délai, quelques appels de test ; succès → Closed, échec → Open).

java
@CircuitBreaker(name = "operateur", fallbackMethod = "statutInconnu")
public StatutPaiement statut(String ref) { return operateurClient.statut(ref); }

private StatutPaiement statutInconnu(String ref, Throwable t) {
    return StatutPaiement.A_VERIFIER;     // dégradation gracieuse
}
yaml
resilience4j.circuitbreaker.instances.operateur:
  sliding-window-size: 20
  failure-rate-threshold: 50          # % d'échecs pour ouvrir
  wait-duration-in-open-state: 30s
  permitted-number-of-calls-in-half-open-state: 3
États Closed / Open / Half-OpenLes trois états d'un circuit breaker : fermé (le courant passe, appels normaux), ouvert (appels coupés), semi-ouvert (quelques appels de test).

Les trois états d'un circuit breaker : fermé (le courant passe, appels normaux), ouvert (appels coupés), semi-ouvert (quelques appels de test).

🎯 En entretienImage d'entretien : comme un disjoncteur électrique, « fermé » veut dire que le courant passe.
Resilience4jBibliothèque Java légère de résilience : CircuitBreaker, Retry, RateLimiter, Bulkhead, TimeLimiter, intégrée à Spring Boot par annotations et propriétés.

Bibliothèque Java légère de résilience : CircuitBreaker, Retry, RateLimiter, Bulkhead, TimeLimiter, intégrée à Spring Boot par annotations et propriétés.

xml
<dependency>
  <groupId>io.github.resilience4j</groupId>
  <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
Fallback et dégradation gracieuseRéponse de repli quand une dépendance échoue (valeur en cache, valeur par défaut, fonctionnalité masquée) pour que le service reste utilisable.

Réponse de repli quand une dépendance échoue (valeur en cache, valeur par défaut, fonctionnalité masquée) pour que le service reste utilisable.

Exemple : si le service de recommandations tombe, la page d'accueil s'affiche sans le bloc recommandations plutôt qu'avec une erreur 500.

Bulkhead (cloisonnement)Isoler les ressources (pools de threads, connexions) par dépendance pour qu'un service lent n'épuise pas toutes les ressources de l'appelant, comme les cloisons étanches d'un navire.

Isoler les ressources (pools de threads, connexions) par dépendance pour qu'un service lent n'épuise pas toutes les ressources de l'appelant, comme les cloisons étanches d'un navire.

java
@Bulkhead(name = "rapports", type = Bulkhead.Type.SEMAPHORE)   // max N appels simultanés
public Rapport generer() { … }
Rate limiter (limitation de débit)Limiter le nombre de requêtes acceptées par unité de temps (par client, par IP, par API key) pour protéger un service et assurer l'équité.

Limiter le nombre de requêtes acceptées par unité de temps (par client, par IP, par API key) pour protéger un service et assurer l'équité.

Algorithmes : token bucket, fenêtre glissante. Réponse au dépassement : 429 Too Many Requests avec Retry-After.

yaml
resilience4j.ratelimiter.instances.otp:
  limit-for-period: 5          # 5 envois d'OTP…
  limit-refresh-period: 1m     # …par minute
IdempotenceUne opération est idempotente si l'exécuter plusieurs fois produit le même effet qu'une seule fois. Indispensable dès qu'il y a des retries ou une livraison « au moins une fois ».

Une opération est idempotente si l'exécuter plusieurs fois produit le même effet qu'une seule fois. Indispensable dès qu'il y a des retries ou une livraison « au moins une fois ».

SET solde = 1000 est idempotent ; solde = solde - 100 ne l'est pas. Pour rendre un traitement idempotent : clé d'idempotence, contrainte d'unicité en base, table des messages déjà traités.

java
@Transactional
public void traiter(PaiementRecu evt) {
    if (dejaTraiteRepo.existsById(evt.id())) return;         // doublon ignoré
    compteService.crediter(evt.compte(), evt.montant());
    dejaTraiteRepo.save(new MessageTraite(evt.id()));        // + contrainte UNIQUE
}
Idempotency-KeyEn-tête HTTP contenant un identifiant unique généré par le client pour une opération ; le serveur mémorise la réponse associée et la renvoie telle quelle si la requête est rejouée.

En-tête HTTP contenant un identifiant unique généré par le client pour une opération ; le serveur mémorise la réponse associée et la renvoie telle quelle si la requête est rejouée.

http
POST /api/v1/paiements
Idempotency-Key: 7c1f6c3e-2b8d-4f6a-9a51-0f2d6e1b9c44

{"compte": "SN01…", "montant": 25000}
java
@PostMapping("/paiements")
ResponseEntity<PaiementDto> payer(@RequestHeader("Idempotency-Key") UUID cle, @Valid @RequestBody PaiementRequest r) {
    return idempotence.executerUneFois(cle, () -> service.payer(r));  // stocke clé + réponse (Redis/DB)
}

5Architecture du code10

Architecture hexagonale (Ports & Adapters)Le domaine métier est au centre et ne dépend d'aucune technologie ; il définit des ports (interfaces) que des adaptateurs (HTTP, JPA, Kafka) implémentent. Toutes les dépendances pointent vers l'intérieur.

Le domaine métier est au centre et ne dépend d'aucune technologie ; il définit des ports (interfaces) que des adaptateurs (HTTP, JPA, Kafka) implémentent. Toutes les dépendances pointent vers l'intérieur.

On peut tester le métier sans base ni serveur, et changer de technologie (PostgreSQL → MongoDB, REST → Kafka) en ne remplaçant qu'un adaptateur.

text
[REST Controller] ─► (port entrant: EffectuerVirement) ─► [DOMAINE] ─► (port sortant: ComptePort) ◄─ [JpaCompteAdapter]
PortInterface définie par le domaine : port entrant = ce que l'application offre (cas d'usage) ; port sortant = ce dont le domaine a besoin (persistance, notifications).

Interface définie par le domaine : port entrant = ce que l'application offre (cas d'usage) ; port sortant = ce dont le domaine a besoin (persistance, notifications).

java
public interface EffectuerVirement { VirementId executer(DemandeVirement d); }   // port entrant
public interface ComptePort { Compte charger(Iban iban); void sauver(Compte c); } // port sortant
Adapter (adaptateur)Implémentation technique d'un port : contrôleur REST (adaptateur entrant), repository JPA ou client Kafka (adaptateur sortant).

Implémentation technique d'un port : contrôleur REST (adaptateur entrant), repository JPA ou client Kafka (adaptateur sortant).

java
@Component
class JpaCompteAdapter implements ComptePort {
    private final CompteJpaRepository jpa;
    private final CompteMapper mapper;
    public Compte charger(Iban iban) { return mapper.toDomain(jpa.findByIban(iban.valeur()).orElseThrow()); }
    public void sauver(Compte c) { jpa.save(mapper.toEntity(c)); }
}
Couche DomaineCœur métier : entités, value objects, règles et services du domaine, en Java pur (sans annotation Spring ni JPA idéalement).

Cœur métier : entités, value objects, règles et services du domaine, en Java pur (sans annotation Spring ni JPA idéalement).

Couche Application (use cases)Orchestre un cas d'usage : charge les objets via les ports, appelle le domaine, gère la transaction, publie les événements. Aucune règle métier propre.

Orchestre un cas d'usage : charge les objets via les ports, appelle le domaine, gère la transaction, publie les événements. Aucune règle métier propre.

java
@Service @Transactional
class EffectuerVirementService implements EffectuerVirement {
    public VirementId executer(DemandeVirement d) {
        Compte source = comptes.charger(d.source());
        Compte dest = comptes.charger(d.destination());
        Virement v = source.virerVers(dest, d.montant());   // règle métier dans le domaine
        comptes.sauver(source); comptes.sauver(dest);
        return v.id();
    }
}
Couche InfrastructureTout ce qui est technique : contrôleurs REST, repositories JPA, consommateurs Kafka, clients HTTP, configuration. Elle dépend du domaine, jamais l'inverse.

Tout ce qui est technique : contrôleurs REST, repositories JPA, consommateurs Kafka, clients HTTP, configuration. Elle dépend du domaine, jamais l'inverse.

Entité (DDD)Objet métier défini par son identité, qui persiste dans le temps même si ses attributs changent (un Compte reste le même compte quand son solde change).

Objet métier défini par son identité, qui persiste dans le temps même si ses attributs changent (un Compte reste le même compte quand son solde change).

Value ObjectObjet défini uniquement par ses valeurs, immuable et sans identité (Montant, Iban, Adresse) ; deux value objects égaux sont interchangeables. Il porte ses propres validations.

Objet défini uniquement par ses valeurs, immuable et sans identité (Montant, Iban, Adresse) ; deux value objects égaux sont interchangeables. Il porte ses propres validations.

java
public record Montant(BigDecimal valeur, String devise) {
    public Montant {
        if (valeur.signum() < 0) throw new IllegalArgumentException("Montant négatif");
        valeur = valeur.setScale(2, RoundingMode.HALF_EVEN);
    }
    public Montant plus(Montant o) { verifierDevise(o); return new Montant(valeur.add(o.valeur), devise); }
}
Domain ServiceRègle métier qui ne relève naturellement d'aucune entité (ex. calcul de frais entre deux comptes de banques différentes) ; sans état, en Java pur.

Règle métier qui ne relève naturellement d'aucune entité (ex. calcul de frais entre deux comptes de banques différentes) ; sans état, en Java pur.

Règle de dépendance (architecture en couches)Les dépendances vont toujours dans un seul sens : Controller → Service → Repository. Une couche basse ne connaît jamais une couche haute.

Les dépendances vont toujours dans un seul sens : Controller → Service → Repository. Une couche basse ne connaît jamais une couche haute.

⚠️ PiègeUn repository qui appelle un service, ou une entité qui dépend d'un contrôleur, crée des cycles et rend les tests et l'évolution très difficiles.

6SOLID & qualité de conception8

SOLIDCinq principes de conception orientée objet (Robert C. Martin) : Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion.

Cinq principes de conception orientée objet (Robert C. Martin) : Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion.

S — Single Responsibility PrincipleUne classe ne doit avoir qu'une seule raison de changer, c'est-à-dire répondre à un seul acteur ou besoin métier.

Une classe ne doit avoir qu'une seule raison de changer, c'est-à-dire répondre à un seul acteur ou besoin métier.

java
// ❌ calcule, persiste, envoie l'email et génère le PDF
class FactureService { … }
// ✅ responsabilités séparées
class CalculFacture { … }   class FactureRepository { … }
class EnvoiFacture { … }    class GenerateurPdf { … }
O — Open/Closed PrincipleOuvert à l'extension, fermé à la modification : on ajoute un comportement en ajoutant du code (nouvelle implémentation), pas en modifiant du code existant et testé.

Ouvert à l'extension, fermé à la modification : on ajoute un comportement en ajoutant du code (nouvelle implémentation), pas en modifiant du code existant et testé.

java
interface RegleFrais { boolean concerne(Operation o); BigDecimal frais(Operation o); }
@Component class FraisVirementInterne implements RegleFrais { … }
@Component class FraisMobileMoney implements RegleFrais { … }   // nouveau cas = nouvelle classe

BigDecimal frais(Operation o) {               // ce code ne change plus
    return regles.stream().filter(r -> r.concerne(o)).findFirst().map(r -> r.frais(o)).orElse(BigDecimal.ZERO);
}
L — Liskov Substitution PrincipleUne sous-classe doit pouvoir remplacer sa classe mère sans casser le comportement attendu par l'appelant (mêmes contrats, pas d'exception surprise).

Une sous-classe doit pouvoir remplacer sa classe mère sans casser le comportement attendu par l'appelant (mêmes contrats, pas d'exception surprise).

java
class Compte { void debiter(BigDecimal m) { … } }
class CompteBloque extends Compte {
    @Override void debiter(BigDecimal m) { throw new UnsupportedOperationException(); } // ❌ viole LSP
}
// ✅ modéliser "bloqué" comme un état (statut) plutôt que comme une sous-classe
I — Interface Segregation PrinciplePlusieurs petites interfaces spécifiques valent mieux qu'une grosse interface générale : un client ne doit pas dépendre de méthodes qu'il n'utilise pas.

Plusieurs petites interfaces spécifiques valent mieux qu'une grosse interface générale : un client ne doit pas dépendre de méthodes qu'il n'utilise pas.

java
// ❌ interface CompteOperations { debiter(); crediter(); exporterPdf(); calculerInterets(); envoyerReleve(); }
interface Debitable { void debiter(Montant m); }
interface Exportable { byte[] exporterPdf(); }
D — Dependency Inversion PrincipleLes modules de haut niveau (métier) ne dépendent pas des modules de bas niveau (techniques) : les deux dépendent d'abstractions. Spring réalise l'injection, le principe dit dans quel sens vont les dépendances.

Les modules de haut niveau (métier) ne dépendent pas des modules de bas niveau (techniques) : les deux dépendent d'abstractions. Spring réalise l'injection, le principe dit dans quel sens vont les dépendances.

java
class VirementService {
    private final Notificateur notificateur;          // abstraction
    VirementService(Notificateur n) { this.notificateur = n; }
    // ❌ private final SmsOrangeClient sms = new SmsOrangeClient();
}
God classClasse « qui fait tout » (des milliers de lignes, des dizaines de dépendances) : symptôme d'une violation du SRP, difficile à tester et à faire évoluer.

Classe « qui fait tout » (des milliers de lignes, des dizaines de dépendances) : symptôme d'une violation du SRP, difficile à tester et à faire évoluer.

Couplage et cohésionCouplage : degré de dépendance entre modules (à minimiser). Cohésion : degré auquel les éléments d'un module servent un même but (à maximiser). Objectif : faible couplage, forte cohésion.

Couplage : degré de dépendance entre modules (à minimiser). Cohésion : degré auquel les éléments d'un module servent un même but (à maximiser). Objectif : faible couplage, forte cohésion.

📋 Synthèse express — Microservices

🏛 Patterns essentiels

⚡ SAGA Pattern

Gestion de transactions distribuées sans 2PC. Deux variantes :

  • Choreography — chaque service émet des événements
  • Orchestration — un orchestrateur central coordonne
📊 CQRS

Command Query Responsibility Segregation : séparer lectures (Query) et écritures (Command). Permet d'optimiser chaque côté indépendamment. Souvent couplé à Event Sourcing.

📝 Event Sourcing

Stocker les événements plutôt que l'état courant. L'état est reconstitué en rejouant les événements. Audit trail complet. Replay possible.

🔌 Circuit Breaker

3 états : Closed (normal) → Open (erreurs dépassent seuil) → Half-Open (test). Resilience4j en Spring. Évite la propagation de pannes en cascade.

🏗 Architecture Hexagonale (Ports & Adapters)

Principe : Le domaine métier est au centre. Les adaptateurs (HTTP, DB, Messaging) sont des plug-ins. Dépendances toujours vers l'intérieur. Permet de tester le domaine sans infrastructure.
CoucheRôleExemples
DomainLogique métier pureEntités, Value Objects, Domain Services
ApplicationOrchestration cas d'usageUse Cases, Application Services, Ports
InfrastructureAdaptateurs techniquesJPA Repos, REST Controllers, Kafka Consumers

📐 SOLID — Rappel

PrincipeDéfinitionViolation typique
Single ResponsibilityUne classe = une seule raison de changerGod class qui fait tout
Open/ClosedOuvert à l'extension, fermé à la modificationModifier la classe au lieu d'étendre
Liskov SubstitutionSous-classe remplaçable par sa classe mèreOverride qui casse le contrat
Interface SegregationPlusieurs interfaces spécialisées > une grosseInterface avec 20 méthodes
Dependency InversionDépendre d'abstractions, pas d'implémentationsnew ConcreteService() dans le code

⚖️ Monolithe vs Microservices

CritèreMonolithe modulaireMicroservices
DéploiementUn seul artefactIndépendant par service
ScalabilitéTout ou rienPar service (ciblée)
Complexité opérationnelleFaibleÉlevée (k8s, service mesh, observabilité)
Cohérence des donnéesACID natif (1 DB)Éventuelle (SAGA, CQRS)
Latence inter-servicesAppel local (nanosec)Réseau (ms) + timeouts
ÉquipePetite/moyenne, 1 techGrandes équipes autonomes (Conway's Law)
Quand choisirDébut de projet, équipe <10Besoin réel d'autonomie/scale/orga

🔁 Idempotence & Resilience

🔁 Idempotence

Une opération est idempotente si elle peut être répétée sans changer le résultat. Critique pour les paiements et les APIs REST. Implémenter via un idempotency-key header.

// Header idempotency-key généré côté client POST /api/payments HTTP/1.1 Idempotency-Key: uuid-unique-per-operation // Si rejeu → même réponse, pas de double débit
⏱ Timeouts & Retry

Toujours définir un timeout sur les appels réseau. Retry avec backoff exponentiel + jitter. Éviter les retries illimités → utiliser un circuit breaker.

  • Timeout : 2-5s pour les APIs synchonres
  • Retry : max 3 fois avec backoff
  • Fallback : valeur par défaut ou dégradé gracieux
⚡ Quiz Rapide — Microservices
1. Quelle est la différence entre SAGA Choreography et Orchestration ?
A
Choreography = synchrone, Orchestration = asynchrone
B
Choreography = chaque service réagit à des événements, Orchestration = un coordinateur central dirige
C
Ce sont deux noms pour le même pattern
D
Choreography utilise une DB partagée, Orchestration des événements
✅ B. Choreography : couplage faible, chaque service émet/consomme des événements. Orchestration : un saga orchestrator envoie des commandes et attend des réponses. Choreography = plus scalable mais debug difficile.
2. Les 3 états d'un Circuit Breaker (Resilience4j) ?
A
On / Off / Pending
B
Active / Inactive / Test
C
Closed / Open / Half-Open
D
Running / Stopped / Recovering
✅ C. Closed = fonctionnement normal. Open = seuil d'erreurs dépassé, toutes les requêtes sont rejetées. Half-Open = teste si le service a récupéré en laissant passer quelques requêtes.
Bases de données
PostgreSQL, MongoDB, Redis, Elasticsearch — optimisation et patterns
📖 Cours complet — 62 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/62 maîtrisées

1Transactions & intégrité8

Transaction (SQL)Ensemble d'opérations exécuté comme une unité : soit tout est validé (COMMIT), soit tout est annulé (ROLLBACK).

Ensemble d'opérations exécuté comme une unité : soit tout est validé (COMMIT), soit tout est annulé (ROLLBACK).

sql
BEGIN;
UPDATE compte SET solde = solde - 50000 WHERE id = 1;
UPDATE compte SET solde = solde + 50000 WHERE id = 2;
INSERT INTO mouvement(compte_id, montant, type) VALUES (1, 50000, 'DEBIT'), (2, 50000, 'CREDIT');
COMMIT;      -- ou ROLLBACK; en cas d'erreur : aucun des trois changements n'est conservé
ACIDLes quatre garanties d'une transaction relationnelle : Atomicité (tout ou rien), Cohérence (contraintes respectées), Isolation (pas d'interférence entre transactions concurrentes), Durabilité (validé = conservé, même après un crash).

Les quatre garanties d'une transaction relationnelle : Atomicité (tout ou rien), Cohérence (contraintes respectées), Isolation (pas d'interférence entre transactions concurrentes), Durabilité (validé = conservé, même après un crash).

  • Atomicité : un virement ne peut pas débiter sans créditer.
  • Cohérence : la transaction amène la base d'un état valide à un autre (clés étrangères, CHECK solde >= 0, unicité).
  • Isolation : deux virements simultanés ne voient pas les états intermédiaires l'un de l'autre, selon le niveau d'isolation.
  • Durabilité : une fois le COMMIT confirmé, la donnée est écrite dans le journal (WAL) et survit à une panne.
🎯 En entretienEn banque, ACID est le socle : c'est l'argument principal pour garder les écritures financières dans une base relationnelle.
Niveaux d'isolationRéglage du compromis entre cohérence et performance pour les transactions concurrentes : READ UNCOMMITTED, READ COMMITTED (défaut PostgreSQL), REPEATABLE READ, SERIALIZABLE.

Réglage du compromis entre cohérence et performance pour les transactions concurrentes : READ UNCOMMITTED, READ COMMITTED (défaut PostgreSQL), REPEATABLE READ, SERIALIZABLE.

NiveauLecture saleLecture non répétableLecture fantôme
READ UNCOMMITTEDpossible*possiblepossible
READ COMMITTED❌possiblepossible
REPEATABLE READ❌❌possible*
SERIALIZABLE❌❌❌

*PostgreSQL est plus strict que la norme : pas de lecture sale même en READ UNCOMMITTED, pas de fantôme en REPEATABLE READ.

java
@Transactional(isolation = Isolation.SERIALIZABLE)   // à réserver aux cas critiques : risque d'échecs à relancer
public void cloturerJournee() { … }
Anomalies de concurrenceLecture sale : lire une donnée non validée. Lecture non répétable : relire une ligne et trouver une autre valeur. Fantôme : relancer une requête et trouver de nouvelles lignes. Mise à jour perdue : deux écritures concurrentes, la seconde écrase la première.

Lecture sale : lire une donnée non validée. Lecture non répétable : relire une ligne et trouver une autre valeur. Fantôme : relancer une requête et trouver de nouvelles lignes. Mise à jour perdue : deux écritures concurrentes, la seconde écrase la première.

text
T1 lit solde = 100          T2 lit solde = 100
T1 écrit 100 - 30 = 70      T2 écrit 100 - 50 = 50   → solde final 50 : le débit de 30 est perdu
🎯 En entretienContre la mise à jour perdue : UPDATE … SET solde = solde - 30 atomique, verrou optimiste (@Version) ou pessimiste (FOR UPDATE).
Verrouillage optimiste (@Version)On ne verrouille pas : chaque ligne a un numéro de version ; à l'UPDATE, on vérifie qu'il n'a pas changé. Si oui, l'écriture échoue (conflit) et on relance ou on informe l'utilisateur.

On ne verrouille pas : chaque ligne a un numéro de version ; à l'UPDATE, on vérifie qu'il n'a pas changé. Si oui, l'écriture échoue (conflit) et on relance ou on informe l'utilisateur.

Idéal quand les conflits sont rares (édition d'un dossier client). Pas de verrou tenu, donc bonne scalabilité.

java
@Entity class Dossier { @Version private Long version; … }
// Hibernate génère : UPDATE dossier SET …, version = 6 WHERE id = 42 AND version = 5
// 0 ligne modifiée → ObjectOptimisticLockingFailureException (→ HTTP 409 Conflict)
Verrouillage pessimiste (SELECT … FOR UPDATE)On verrouille la ligne dès la lecture : les autres transactions qui veulent la modifier attendent la fin de la transaction. Adapté aux conflits fréquents (solde d'un compte très sollicité).

On verrouille la ligne dès la lecture : les autres transactions qui veulent la modifier attendent la fin de la transaction. Adapté aux conflits fréquents (solde d'un compte très sollicité).

java
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT c FROM Compte c WHERE c.id = :id")
Optional<Compte> findForUpdate(@Param("id") Long id);   // SELECT … FOR UPDATE
⚠️ PiègeToujours verrouiller dans le même ordre (ex. id croissant) pour éviter les deadlocks entre deux virements croisés.
Contraintes d'intégritéRègles garanties par la base elle-même : clé primaire (identifiant unique), clé étrangère (référence valide), UNIQUE, NOT NULL, CHECK.

Règles garanties par la base elle-même : clé primaire (identifiant unique), clé étrangère (référence valide), UNIQUE, NOT NULL, CHECK.

sql
CREATE TABLE compte (
  id         BIGSERIAL PRIMARY KEY,
  client_id  BIGINT NOT NULL REFERENCES client(id),
  iban       VARCHAR(34) NOT NULL UNIQUE,
  solde      NUMERIC(15,2) NOT NULL CHECK (solde >= 0),
  devise     CHAR(3) NOT NULL DEFAULT 'XOF'
);
🎯 En entretienLa base est le dernier rempart : même si le code a un bug, la contrainte refuse la donnée incohérente.
NormalisationOrganiser les tables pour éliminer la redondance (chaque information stockée une seule fois). La dénormalisation duplique volontairement des données pour accélérer la lecture.

Organiser les tables pour éliminer la redondance (chaque information stockée une seule fois). La dénormalisation duplique volontairement des données pour accélérer la lecture.

1NF : valeurs atomiques ; 2NF : pas de dépendance partielle à la clé ; 3NF : pas de dépendance entre colonnes non-clés. Exemple : on ne recopie pas l'adresse du client dans chaque commande, on référence client_id.

2Requêtes SQL12

SELECT et projectionSELECT choisit les colonnes à renvoyer (la projection) ; ne demander que les colonnes utiles réduit les I/O, le réseau et permet les index couvrants.

SELECT choisit les colonnes à renvoyer (la projection) ; ne demander que les colonnes utiles réduit les I/O, le réseau et permet les index couvrants.

sql
-- ❌ ramène 40 colonnes dont des textes volumineux
SELECT * FROM transaction WHERE compte_id = 42;
-- ✅
SELECT id, date_operation, montant, libelle FROM transaction WHERE compte_id = 42;
Jointure (JOIN) : vue d'ensembleUne jointure combine les lignes de deux tables selon une condition (souvent clé étrangère = clé primaire) ; son type décide du sort des lignes sans correspondance.

Une jointure combine les lignes de deux tables selon une condition (souvent clé étrangère = clé primaire) ; son type décide du sort des lignes sans correspondance.

TypeLignes gardées
INNER JOINSeulement celles qui correspondent des deux côtés
LEFT JOINToutes celles de gauche (+ NULL à droite si pas de correspondance)
RIGHT JOINToutes celles de droite (+ NULL à gauche)
FULL OUTER JOINToutes celles des deux tables
CROSS JOINProduit cartésien (chaque ligne × chaque ligne)
🎯 En entretienToujours indexer les colonnes de jointure (clés étrangères) : sinon chaque jointure parcourt toute la table.
INNER JOINRenvoie uniquement les lignes qui ont une correspondance dans les deux tables.

Renvoie uniquement les lignes qui ont une correspondance dans les deux tables.

sql
SELECT c.nom, co.iban
FROM client c
INNER JOIN compte co ON co.client_id = c.id;   -- les clients sans compte disparaissent
LEFT JOINRenvoie toutes les lignes de la table de gauche, avec les colonnes de droite remplies si une correspondance existe, NULL sinon.

Renvoie toutes les lignes de la table de gauche, avec les colonnes de droite remplies si une correspondance existe, NULL sinon.

sql
SELECT c.id, c.nom, COUNT(co.id) AS nombre_commandes
FROM client c
LEFT JOIN commande co ON co.client_id = c.id
GROUP BY c.id, c.nom;                -- les clients sans commande apparaissent avec 0

-- Trouver les clients SANS commande
SELECT c.* FROM client c LEFT JOIN commande co ON co.client_id = c.id WHERE co.id IS NULL;
⚠️ PiègeCOUNT(*) compterait 1 pour un client sans commande (la ligne avec NULL) : utiliser COUNT(co.id).
RIGHT JOINSymétrique du LEFT JOIN : toutes les lignes de la table de droite, NULL à gauche si pas de correspondance. On l'écrit rarement (on inverse les tables et on fait un LEFT JOIN).

Symétrique du LEFT JOIN : toutes les lignes de la table de droite, NULL à gauche si pas de correspondance. On l'écrit rarement (on inverse les tables et on fait un LEFT JOIN).

FULL OUTER JOINToutes les lignes des deux tables, appariées quand c'est possible, complétées par des NULL sinon. Utile pour la réconciliation (comparer deux sources).

Toutes les lignes des deux tables, appariées quand c'est possible, complétées par des NULL sinon. Utile pour la réconciliation (comparer deux sources).

sql
-- Rapprochement : opérations banque vs relevé opérateur mobile money
SELECT b.reference, b.montant AS montant_banque, o.montant AS montant_operateur
FROM operations_banque b
FULL OUTER JOIN releve_operateur o ON o.reference = b.reference
WHERE b.reference IS NULL OR o.reference IS NULL OR b.montant <> o.montant;   -- écarts
GROUP BY et fonctions d'agrégationGROUP BY regroupe les lignes qui partagent une valeur ; les fonctions d'agrégation (COUNT, SUM, AVG, MIN, MAX) calculent un résultat par groupe.

GROUP BY regroupe les lignes qui partagent une valeur ; les fonctions d'agrégation (COUNT, SUM, AVG, MIN, MAX) calculent un résultat par groupe.

sql
SELECT agence, COUNT(*) AS nb_comptes, SUM(solde) AS encours, AVG(solde) AS solde_moyen
FROM compte
GROUP BY agence
ORDER BY encours DESC;
⚠️ PiègeToute colonne du SELECT qui n'est pas agrégée doit figurer dans le GROUP BY.
WHERE vs HAVINGWHERE filtre les lignes avant le regroupement ; HAVING filtre les groupes après le calcul des agrégats.

WHERE filtre les lignes avant le regroupement ; HAVING filtre les groupes après le calcul des agrégats.

Ordre logique d'exécution : FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. C'est pourquoi WHERE ne peut pas utiliser COUNT(*).

sql
SELECT client_id, COUNT(*) AS nb_virements
FROM virement
WHERE date_operation >= CURRENT_DATE - 30   -- lignes : 30 derniers jours
GROUP BY client_id
HAVING COUNT(*) > 50;                        -- groupes : clients très actifs (alerte AML)
ORDER BY et LIMITORDER BY trie le résultat ; LIMIT restreint le nombre de lignes renvoyées et OFFSET en saute un certain nombre.

ORDER BY trie le résultat ; LIMIT restreint le nombre de lignes renvoyées et OFFSET en saute un certain nombre.

sql
SELECT * FROM transaction WHERE compte_id = 42 ORDER BY date_operation DESC LIMIT 20 OFFSET 40;  -- page 3
⚠️ PiègeSans ORDER BY, l'ordre des lignes n'est pas garanti : une pagination sans tri renvoie des doublons ou des trous.
Sous-requête et CTE (WITH)Une sous-requête est une requête imbriquée ; une CTE (WITH nom AS (...)) lui donne un nom pour rendre une requête complexe lisible, et peut être récursive.

Une sous-requête est une requête imbriquée ; une CTE (WITH nom AS (...)) lui donne un nom pour rendre une requête complexe lisible, et peut être récursive.

sql
WITH mouvements_mois AS (
  SELECT compte_id, SUM(montant) AS total
  FROM mouvement
  WHERE date_operation >= date_trunc('month', CURRENT_DATE)
  GROUP BY compte_id
)
SELECT c.iban, m.total
FROM compte c JOIN mouvements_mois m ON m.compte_id = c.id
WHERE m.total > 10000000;
Fonctions de fenêtrage (window functions)Calculs sur un ensemble de lignes liées à la ligne courante (classement, cumul, comparaison avec la précédente) sans réduire le nombre de lignes, via OVER (PARTITION BY … ORDER BY …).

Calculs sur un ensemble de lignes liées à la ligne courante (classement, cumul, comparaison avec la précédente) sans réduire le nombre de lignes, via OVER (PARTITION BY … ORDER BY …).

sql
SELECT compte_id, date_operation, montant,
       SUM(montant) OVER (PARTITION BY compte_id ORDER BY date_operation) AS solde_cumule,
       ROW_NUMBER() OVER (PARTITION BY compte_id ORDER BY montant DESC)   AS rang
FROM mouvement;
Requête préparéeRequête dont la structure SQL est envoyée séparément des valeurs (paramètres ?) : la base ne peut jamais interpréter une valeur comme du code SQL. Protection de base contre l'injection SQL.

Requête dont la structure SQL est envoyée séparément des valeurs (paramètres ?) : la base ne peut jamais interpréter une valeur comme du code SQL. Protection de base contre l'injection SQL.

java
// ❌ concaténation : "'; DROP TABLE users; --" devient du SQL
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
// ✅ paramètre lié
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
ps.setString(1, name);
// ✅ JPA : @Query("… WHERE u.name = :name") avec @Param, ou les requêtes dérivées

3Index & performance16

IndexStructure de données annexe qui permet de retrouver des lignes sans parcourir toute la table, comme l'index d'un livre. Accélère les lectures, ralentit un peu les écritures et occupe de l'espace.

Structure de données annexe qui permet de retrouver des lignes sans parcourir toute la table, comme l'index d'un livre. Accélère les lectures, ralentit un peu les écritures et occupe de l'espace.

sql
CREATE INDEX idx_transaction_compte_date ON transaction (compte_id, date_operation DESC);
CREATE INDEX CONCURRENTLY idx_client_email ON client (email);   -- PostgreSQL : sans bloquer les écritures
🎯 En entretienIndexer les colonnes des WHERE, JOIN et ORDER BY fréquents, et les clés étrangères (PostgreSQL ne les indexe pas automatiquement).
Index B-TreeIndex par défaut : arbre équilibré trié qui gère égalité, plages (<, >, BETWEEN), tri et préfixes (LIKE 'abc%'). Couvre la grande majorité des besoins.

Index par défaut : arbre équilibré trié qui gère égalité, plages (<, >, BETWEEN), tri et préfixes (LIKE 'abc%'). Couvre la grande majorité des besoins.

Index HashIndex basé sur une fonction de hachage, utilisable uniquement pour l'égalité (=). Rarement préférable au B-Tree en PostgreSQL.

Index basé sur une fonction de hachage, utilisable uniquement pour l'égalité (=). Rarement préférable au B-Tree en PostgreSQL.

Index GINIndex inversé : pour chaque élément (clé JSON, mot, élément de tableau), la liste des lignes qui le contiennent. Idéal pour JSONB, recherche plein texte et tableaux.

Index inversé : pour chaque élément (clé JSON, mot, élément de tableau), la liste des lignes qui le contiennent. Idéal pour JSONB, recherche plein texte et tableaux.

sql
CREATE INDEX idx_client_meta ON client USING GIN (metadonnees);
SELECT * FROM client WHERE metadonnees @> '{"segment": "PME"}';   -- utilise l'index GIN
Index GiSTIndex généraliste pour les données géométriques et les plages : géolocalisation (PostGIS), intervalles de dates, recherche de proximité.

Index généraliste pour les données géométriques et les plages : géolocalisation (PostGIS), intervalles de dates, recherche de proximité.

sql
CREATE INDEX idx_agence_position ON agence USING GIST (position);
SELECT nom FROM agence ORDER BY position <-> ST_MakePoint(-17.4677, 14.7167) LIMIT 3;  -- 3 agences les plus proches de Dakar
Index BRINIndex minuscule qui stocke le min/max par bloc de pages ; très efficace sur de très grosses tables dont la colonne suit l'ordre physique d'insertion (horodatages de logs, transactions).

Index minuscule qui stocke le min/max par bloc de pages ; très efficace sur de très grosses tables dont la colonne suit l'ordre physique d'insertion (horodatages de logs, transactions).

sql
CREATE INDEX idx_audit_date ON journal_audit USING BRIN (cree_le);
Index partielIndex qui ne couvre qu'un sous-ensemble de lignes (clause WHERE) : plus petit et plus rapide quand on interroge toujours ce sous-ensemble.

Index qui ne couvre qu'un sous-ensemble de lignes (clause WHERE) : plus petit et plus rapide quand on interroge toujours ce sous-ensemble.

sql
CREATE INDEX idx_virement_en_attente ON virement (date_creation) WHERE statut = 'EN_ATTENTE';
Index composite et ordre des colonnesIndex sur plusieurs colonnes ; il sert aux requêtes qui filtrent sur ses premières colonnes (règle du préfixe gauche). L'ordre des colonnes compte.

Index sur plusieurs colonnes ; il sert aux requêtes qui filtrent sur ses premières colonnes (règle du préfixe gauche). L'ordre des colonnes compte.

sql
CREATE INDEX idx_tx ON transaction (compte_id, date_operation);
-- ✅ WHERE compte_id = 42
-- ✅ WHERE compte_id = 42 AND date_operation > '2026-01-01'
-- ❌ WHERE date_operation > '2026-01-01'   (pas de compte_id : index peu utile)
Index couvrant (INCLUDE) et Index Only ScanIndex qui contient toutes les colonnes demandées par la requête : la base répond sans lire la table (Index Only Scan).

Index qui contient toutes les colonnes demandées par la requête : la base répond sans lire la table (Index Only Scan).

sql
CREATE INDEX idx_tx_couvrant ON transaction (compte_id, date_operation) INCLUDE (montant);
SELECT date_operation, montant FROM transaction WHERE compte_id = 42;   -- Index Only Scan
EXPLAIN ANALYZEAffiche le plan d'exécution choisi par la base et, avec ANALYZE, exécute réellement la requête pour donner temps et nombre de lignes réels à chaque étape.

Affiche le plan d'exécution choisi par la base et, avec ANALYZE, exécute réellement la requête pour donner temps et nombre de lignes réels à chaque étape.

sql
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM transaction WHERE compte_id = 42 AND date_operation > now() - interval '30 days';
-- Index Scan using idx_tx on transaction (cost=0.43..85.20 rows=120) (actual time=0.05..0.31 rows=118)
-- Execution Time: 0.42 ms
🎯 En entretienLire : le type de scan, l'écart entre rows estimé et réel (statistiques fausses ?), et les nœuds les plus coûteux.
⚠️ PiègeEXPLAIN ANALYZE exécute vraiment la requête : sur un UPDATE/DELETE, l'envelopper dans BEGIN; … ROLLBACK;.
Seq Scan, Index Scan, Bitmap Heap ScanStratégies de lecture : Seq Scan lit toute la table ; Index Scan suit l'index ligne par ligne ; Bitmap Heap Scan collecte via l'index les pages à lire puis les lit en bloc (bon compromis pour beaucoup de lignes ou plusieurs conditions).

Stratégies de lecture : Seq Scan lit toute la table ; Index Scan suit l'index ligne par ligne ; Bitmap Heap Scan collecte via l'index les pages à lire puis les lit en bloc (bon compromis pour beaucoup de lignes ou plusieurs conditions).

Un Seq Scan n'est pas toujours mauvais : sur une petite table ou si la requête ramène une grande partie des lignes, c'est le plus rapide. Il devient suspect sur une grande table filtrée sur peu de lignes.

Cardinalité et sélectivitéCardinalité : nombre de valeurs distinctes d'une colonne. Sélectivité : proportion de lignes retenues par un filtre. Un index est utile sur un filtre très sélectif (peu de lignes).

Cardinalité : nombre de valeurs distinctes d'une colonne. Sélectivité : proportion de lignes retenues par un filtre. Un index est utile sur un filtre très sélectif (peu de lignes).

Un index sur sexe (2 valeurs) sert rarement ; un index sur email (unique) est très sélectif.

Statistiques : ANALYZE et pg_statsANALYZE collecte des statistiques (distribution des valeurs) que l'optimiseur utilise pour estimer les coûts ; pg_stats permet de les consulter.

ANALYZE collecte des statistiques (distribution des valeurs) que l'optimiseur utilise pour estimer les coûts ; pg_stats permet de les consulter.

sql
ANALYZE transaction;
SELECT attname, n_distinct, most_common_vals FROM pg_stats WHERE tablename = 'transaction';
MVCC et VACUUMPostgreSQL ne modifie pas une ligne en place : il crée une nouvelle version (MVCC), ce qui permet aux lecteurs de ne pas bloquer les écrivains. VACUUM récupère l'espace des anciennes versions.

PostgreSQL ne modifie pas une ligne en place : il crée une nouvelle version (MVCC), ce qui permet aux lecteurs de ne pas bloquer les écrivains. VACUUM récupère l'espace des anciennes versions.

L'autovacuum s'en charge normalement. Sur une table très mise à jour, un autovacuum en retard entraîne du « bloat » (table gonflée, requêtes plus lentes).

sql
VACUUM (ANALYZE, VERBOSE) transaction;
SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;
Pagination : OFFSET vs keysetOFFSET relit et jette toutes les lignes précédentes (lent sur les pages lointaines, instable si des lignes arrivent). La pagination keyset repart de la dernière valeur vue : coût constant.

OFFSET relit et jette toutes les lignes précédentes (lent sur les pages lointaines, instable si des lignes arrivent). La pagination keyset repart de la dernière valeur vue : coût constant.

sql
-- OFFSET : page 5000 = 100 000 lignes lues puis ignorées
SELECT * FROM transaction ORDER BY id DESC LIMIT 20 OFFSET 100000;
-- Keyset : on transmet le dernier id reçu
SELECT * FROM transaction WHERE id < :dernier_id ORDER BY id DESC LIMIT 20;
Pool de connexions (HikariCP)Ensemble de connexions base ouvertes à l'avance et réutilisées (ouvrir une connexion coûte des dizaines de ms). HikariCP est le pool par défaut de Spring Boot (10 connexions par défaut).

Ensemble de connexions base ouvertes à l'avance et réutilisées (ouvrir une connexion coûte des dizaines de ms). HikariCP est le pool par défaut de Spring Boot (10 connexions par défaut).

yaml
spring.datasource.hikari:
  maximum-pool-size: 20
  connection-timeout: 3000      # ms d'attente max pour obtenir une connexion
  leak-detection-threshold: 20000
⚠️ PiègePlus de connexions n'est pas toujours mieux : PostgreSQL souffre au-delà de quelques centaines au total (tous pods confondus).

4PostgreSQL4

PostgreSQLBase relationnelle open source robuste et très complète : ACID, MVCC, JSONB, index avancés, CTE, fonctions fenêtres, extensions (PostGIS, pg_trgm).

Base relationnelle open source robuste et très complète : ACID, MVCC, JSONB, index avancés, CTE, fonctions fenêtres, extensions (PostGIS, pg_trgm).

JSONBType PostgreSQL qui stocke du JSON sous forme binaire indexable (GIN) ; permet de mêler données relationnelles et attributs flexibles.

Type PostgreSQL qui stocke du JSON sous forme binaire indexable (GIN) ; permet de mêler données relationnelles et attributs flexibles.

sql
ALTER TABLE client ADD COLUMN metadonnees JSONB DEFAULT '{}';
UPDATE client SET metadonnees = metadonnees || '{"canal": "USSD"}' WHERE id = 42;
SELECT metadonnees->>'canal' FROM client WHERE metadonnees @> '{"segment": "PME"}';
⚠️ PiègeNe pas tout mettre en JSONB : les champs interrogés et contraints (montant, statut) restent des colonnes typées.
Partitionnement de tableDécouper une très grosse table en sous-tables (par date, par région) : les requêtes ne lisent que les partitions concernées et l'archivage devient un simple DETACH.

Découper une très grosse table en sous-tables (par date, par région) : les requêtes ne lisent que les partitions concernées et l'archivage devient un simple DETACH.

sql
CREATE TABLE transaction (id BIGINT, date_operation DATE, montant NUMERIC(15,2))
PARTITION BY RANGE (date_operation);
CREATE TABLE transaction_2026_10 PARTITION OF transaction
  FOR VALUES FROM ('2026-10-01') TO ('2026-11-01');
Réplication et réplicas de lectureCopie continue de la base principale vers une ou plusieurs répliques ; on y envoie les lectures lourdes (rapports) pour soulager le primaire, et elles servent de secours en cas de panne.

Copie continue de la base principale vers une ou plusieurs répliques ; on y envoie les lectures lourdes (rapports) pour soulager le primaire, et elles servent de secours en cas de panne.

⚠️ PiègeLa réplication est souvent asynchrone : une donnée tout juste écrite peut ne pas encore être visible sur le réplica.

5MongoDB & NoSQL7

MongoDBBase NoSQL orientée documents : les données sont stockées en documents BSON (proches du JSON) dans des collections, sans schéma imposé par la base.

Base NoSQL orientée documents : les données sont stockées en documents BSON (proches du JSON) dans des collections, sans schéma imposé par la base.

Adaptée aux données hétérogènes ou évolutives (catalogues, contenus, logs métier, profils). Pour des écritures financières fortement relationnelles, PostgreSQL reste préféré.

javascript
db.clients.insertOne({
  nom: "Awa Ndiaye",
  telephones: ["+221770000000"],
  adresse: { ville: "Dakar", quartier: "Plateau" }
});
Document et collectionUn document est un enregistrement JSON/BSON (avec un _id unique) ; une collection regroupe des documents, comme une table regroupe des lignes, mais sans structure imposée.

Un document est un enregistrement JSON/BSON (avec un _id unique) ; une collection regroupe des documents, comme une table regroupe des lignes, mais sans structure imposée.

Embedding vs referencingModélisation MongoDB : imbriquer les données lues ensemble dans le même document (embedding), ou stocker une référence vers un autre document (referencing) quand elles sont volumineuses, partagées ou à croissance illimitée.

Modélisation MongoDB : imbriquer les données lues ensemble dans le même document (embedding), ou stocker une référence vers un autre document (referencing) quand elles sont volumineuses, partagées ou à croissance illimitée.

javascript
// Embedding : adresse lue toujours avec le client
{ _id: 1, nom: "Awa", adresse: { ville: "Dakar" } }
// Referencing : des milliers de transactions → collection séparée
{ _id: 9001, clientId: 1, montant: 25000 }
⚠️ PiègeUn document est limité à 16 Mo : ne pas imbriquer une liste qui grossit sans fin.
Aggregation PipelineSuite d'étapes ($match, $group, $sort, $project, $lookup…) que traversent les documents, chaque étape transformant le résultat de la précédente ; l'équivalent MongoDB du GROUP BY/JOIN.

Suite d'étapes ($match, $group, $sort, $project, $lookup…) que traversent les documents, chaque étape transformant le résultat de la précédente ; l'équivalent MongoDB du GROUP BY/JOIN.

javascript
db.orders.aggregate([
  { $match: { status: "ACTIVE", createdAt: { $gte: ISODate("2026-01-01") } } },
  { $group: { _id: "$customerId", total: { $sum: "$amount" }, count: { $sum: 1 } } },
  { $sort: { total: -1 } },
  { $limit: 10 }
]);
Étapes $match, $group, $sort, $limit, $lookup$match filtre (comme WHERE), $group regroupe et agrège, $sort trie, $limit coupe, $project choisit les champs, $lookup joint une autre collection.

$match filtre (comme WHERE), $group regroupe et agrège, $sort trie, $limit coupe, $project choisit les champs, $lookup joint une autre collection.

🎯 En entretienPlacer $match le plus tôt possible : il peut utiliser un index et réduit le volume pour les étapes suivantes.
Index MongoDBComme en SQL, sans index une requête parcourt toute la collection (COLLSCAN) ; on crée des index simples, composés, uniques, TTL ou texte.

Comme en SQL, sans index une requête parcourt toute la collection (COLLSCAN) ; on crée des index simples, composés, uniques, TTL ou texte.

javascript
db.transactions.createIndex({ clientId: 1, date: -1 });
db.sessions.createIndex({ creeLe: 1 }, { expireAfterSeconds: 3600 });   // TTL : suppression auto
db.transactions.find({ clientId: 1 }).sort({ date: -1 }).explain("executionStats");
Théorème CAPEn cas de partition réseau, un système distribué doit choisir entre Cohérence (tout le monde voit la même donnée) et Disponibilité (tout le monde reçoit une réponse) ; la tolérance au partitionnement n'est pas négociable.

En cas de partition réseau, un système distribué doit choisir entre Cohérence (tout le monde voit la même donnée) et Disponibilité (tout le monde reçoit une réponse) ; la tolérance au partitionnement n'est pas négociable.

Un système de paiement favorise la cohérence (refuser plutôt que débiter deux fois) ; un fil d'actualité favorise la disponibilité.

6Redis & cache13

RedisBase clé-valeur en mémoire, ultra-rapide (sub-milliseconde), avec structures de données riches ; utilisée comme cache, store de sessions, compteur, verrou distribué, file légère.

Base clé-valeur en mémoire, ultra-rapide (sub-milliseconde), avec structures de données riches ; utilisée comme cache, store de sessions, compteur, verrou distribué, file légère.

bash
SET taux:EUR "655.957" EX 3600     # valeur avec expiration 1 h
GET taux:EUR
INCR compteur:otp:+221770000000
TTL (Time To Live)Durée de vie d'une clé : passé ce délai, elle est supprimée automatiquement. Indispensable pour qu'un cache ne serve pas éternellement des données périmées.

Durée de vie d'une clé : passé ce délai, elle est supprimée automatiquement. Indispensable pour qu'un cache ne serve pas éternellement des données périmées.

bash
EXPIRE session:abc 1800
TTL session:abc        # secondes restantes
Structures Redis : String, Hash, List, SetString : valeur simple ou compteur. Hash : objet à champs (user:123 → nom, email). List : liste ordonnée (file FIFO/LIFO). Set : ensemble sans doublons.

String : valeur simple ou compteur. Hash : objet à champs (user:123 → nom, email). List : liste ordonnée (file FIFO/LIFO). Set : ensemble sans doublons.

bash
HSET client:42 nom "Awa" ville "Dakar"
HGET client:42 ville
LPUSH file:notifications "msg1"     # puis BRPOP pour consommer
SADD agences:ouvertes "Plateau" "Medina"
SISMEMBER agences:ouvertes "Plateau"
Sorted Set (ZSET)Ensemble dont chaque élément a un score, maintenu trié : classements, files de priorité, fenêtres glissantes pour le rate limiting.

Ensemble dont chaque élément a un score, maintenu trié : classements, files de priorité, fenêtres glissantes pour le rate limiting.

bash
ZADD classement:agences 1250000000 "Plateau" 980000000 "Medina"
ZREVRANGE classement:agences 0 9 WITHSCORES          # top 10
# Rate limiting fenêtre glissante : un élément par requête, score = timestamp
ZADD rl:client:42 1760440000123 "req-uuid"
ZREMRANGEBYSCORE rl:client:42 0 1760439940123        # purge > 60 s
ZCARD rl:client:42                                   # requêtes dans la dernière minute
Redis StreamsJournal d'événements en append-only dans Redis, avec groupes de consommateurs et accusés de réception : une alternative légère à Kafka pour des volumes modérés.

Journal d'événements en append-only dans Redis, avec groupes de consommateurs et accusés de réception : une alternative légère à Kafka pour des volumes modérés.

bash
XADD evenements * type "VIREMENT" montant "25000"
XREADGROUP GROUP notif c1 COUNT 10 STREAMS evenements >
XACK evenements notif 1760440000123-0
Cache-Aside (lazy loading)L'application lit d'abord le cache ; en cas d'absence (miss), elle lit la base, puis écrit le résultat dans le cache. C'est le pattern le plus courant (ce que fait @Cacheable).

L'application lit d'abord le cache ; en cas d'absence (miss), elle lit la base, puis écrit le résultat dans le cache. C'est le pattern le plus courant (ce que fait @Cacheable).

java
public Taux taux(String devise) {
    Taux t = redis.opsForValue().get("taux:" + devise);
    if (t != null) return t;                                    // hit
    t = repo.findByDevise(devise);                              // miss → base
    redis.opsForValue().set("taux:" + devise, t, Duration.ofMinutes(30));
    return t;
}
Read-ThroughLe cache lui-même charge la donnée depuis la base en cas de miss (via un « loader »), l'application ne parle qu'au cache.

Le cache lui-même charge la donnée depuis la base en cas de miss (via un « loader »), l'application ne parle qu'au cache.

java
LoadingCache<String, Taux> cache = Caffeine.newBuilder()
        .expireAfterWrite(Duration.ofMinutes(30))
        .build(devise -> repo.findByDevise(devise));   // loader appelé en cas de miss
Write-ThroughChaque écriture met à jour le cache et la base de façon synchrone : le cache est toujours à jour, au prix d'une écriture un peu plus lente.

Chaque écriture met à jour le cache et la base de façon synchrone : le cache est toujours à jour, au prix d'une écriture un peu plus lente.

Write-Behind (Write-Back)L'écriture va d'abord dans le cache, et la base est mise à jour plus tard, en asynchrone et par lots : très rapide, mais risque de perte si le cache tombe avant la synchronisation.

L'écriture va d'abord dans le cache, et la base est mise à jour plus tard, en asynchrone et par lots : très rapide, mais risque de perte si le cache tombe avant la synchronisation.

⚠️ PiègeÀ éviter pour des données financières : une écriture acquittée au client pourrait ne jamais atteindre la base.
Cache Stampede (thundering herd)Quand une clé très demandée expire, des centaines de requêtes ratent le cache en même temps et frappent toutes la base, qui peut s'effondrer.

Quand une clé très demandée expire, des centaines de requêtes ratent le cache en même temps et frappent toutes la base, qui peut s'effondrer.

Remèdes : un seul recalcul grâce à un verrou (SETNX), TTL avec jitter pour étaler les expirations, rafraîchissement anticipé avant expiration, ou servir la valeur périmée pendant le recalcul.

SETNX et verrou distribuéSET cle valeur NX PX 5000 n'écrit la clé que si elle n'existe pas : le premier qui réussit obtient le verrou, avec une expiration de sécurité. Sert à coordonner plusieurs instances.

SET cle valeur NX PX 5000 n'écrit la clé que si elle n'existe pas : le premier qui réussit obtient le verrou, avec une expiration de sécurité. Sert à coordonner plusieurs instances.

bash
SET lock:recalcul:taux "pod-7" NX PX 5000    # OK → j'ai le verrou ; (nil) → quelqu'un d'autre l'a
java
RLock lock = redisson.getLock("lock:cloture:" + date);    // Redisson gère renouvellement et libération
if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
    try { cloturerJournee(date); } finally { lock.unlock(); }
}
⚠️ PiègeLibérer uniquement son propre verrou (comparer la valeur) ; et un verrou Redis n'est pas une garantie absolue en cas de panne : pour l'argent, s'appuyer aussi sur les contraintes de la base.
Invalidation de cacheSupprimer ou mettre à jour une entrée de cache quand la donnée source change ; « il n'y a que deux choses difficiles en informatique : l'invalidation de cache et nommer les choses ».

Supprimer ou mettre à jour une entrée de cache quand la donnée source change ; « il n'y a que deux choses difficiles en informatique : l'invalidation de cache et nommer les choses ».

Stratégies : TTL court, suppression explicite après écriture (@CacheEvict), événement de changement diffusé à toutes les instances.

Politique d'éviction (LRU, LFU)Règle qui choisit quelles clés supprimer quand la mémoire est pleine : LRU (moins récemment utilisée), LFU (moins fréquemment utilisée), TTL le plus proche…

Règle qui choisit quelles clés supprimer quand la mémoire est pleine : LRU (moins récemment utilisée), LFU (moins fréquemment utilisée), TTL le plus proche…

bash
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru

7Elasticsearch2

Elasticsearch et index inverséMoteur de recherche et d'analyse distribué basé sur Lucene ; son index inversé associe chaque mot aux documents qui le contiennent, ce qui rend la recherche plein texte très rapide.

Moteur de recherche et d'analyse distribué basé sur Lucene ; son index inversé associe chaque mot aux documents qui le contiennent, ce qui rend la recherche plein texte très rapide.

Usage : recherche de clients par nom approximatif, recherche dans les logs (pile ELK), tableaux de bord analytiques. Ce n'est pas une base transactionnelle de référence : on y réplique les données depuis la base principale.

json
GET /clients/_search
{ "query": { "match": { "nom": { "query": "ndiay", "fuzziness": "AUTO" } } } }
Analyseur et recherche plein texteL'analyseur découpe le texte en termes (tokenisation), met en minuscules, retire accents et mots vides, applique la racinisation ; la même analyse est faite sur la requête pour pouvoir comparer.

L'analyseur découpe le texte en termes (tokenisation), met en minuscules, retire accents et mots vides, applique la racinisation ; la même analyse est faite sur la requête pour pouvoir comparer.

json
PUT /clients
{ "mappings": { "properties": {
    "nom":  { "type": "text", "analyzer": "french" },
    "iban": { "type": "keyword" } } } }
🎯 En entretientext = analysé (recherche floue) ; keyword = valeur exacte (filtres, tri, agrégations).

📋 Synthèse express — BDD

🗃️ SQL — Fondamentaux

🔒 ACID
  • Atomicité — tout ou rien
  • Cohérence — respecte les contraintes
  • Isolation — transactions invisibles entre elles
  • Durabilité — committed = persisté même après crash
🔗 JOINs
  • INNER JOIN — lignes matchant des deux côtés
  • LEFT JOIN — toutes les lignes de gauche + match si existant
  • RIGHT JOIN — toutes les lignes de droite
  • FULL OUTER JOIN — toutes les lignes des deux tables
WHERE vs HAVING

WHERE filtre les lignes avant l'agrégation (GROUP BY).
HAVING filtre les groupes après l'agrégation.

SELECT dept, COUNT(*) AS nb FROM employees WHERE salary > 30000 -- filtre lignes GROUP BY dept HAVING COUNT(*) > 5; -- filtre groupes
-- Exemple JOIN + agrégation SELECT c.id, c.nom, COUNT(co.id) AS nombre_commandes FROM client c LEFT JOIN commande co ON co.client_id = c.id GROUP BY c.id, c.nom ORDER BY nombre_commandes DESC; -- Anti-injection : TOUJOURS requêtes préparées -- ❌ String name = "'; DROP TABLE users; --" -- ✅ preparedStatement.setString(1, name)

📊 Optimisation SQL — 6 étapes

#ÉtapeOutils
1Mesurer avec le plan d'exécutionEXPLAIN ANALYZE
2Identifier Seq Scan sur grandes tablesSeq Scan → créer index
3Éviter SELECT * si inutileProjections ciblées
4Paginer les listesLIMIT/OFFSET ou keyset pagination
5Corriger le N+1 JPAJOIN FETCH, @EntityGraph
6Vérifier la cardinalité des indexpg_stats, ANALYZE

🐘 PostgreSQL

Type d'indexUsage
B-Tree (défaut)Equality, range, ORDER BY — 90% des cas
HashEquality only, plus rapide pour =
GINJSONB, full-text search, arrays
GiSTGéospatial (PostGIS), formes géométriques
BRINColonnes corrélées à leur position physique (timestamps séquentiels)
Partial indexIndex sur sous-ensemble : WHERE status = 'ACTIVE'
EXPLAIN ANALYZE : Toujours utiliser pour diagnostiquer. Seq Scan = pas d'index utilisé. Index Scan = O(log n). Bitmap Heap Scan = compromis pour multiple conditions.

🍃 MongoDB

// Aggregation Pipeline db.orders.aggregate([ { $match: { status: "ACTIVE", createdAt: { $gte: new Date("2024-01-01") } }}, { $group: { _id: "$customerId", total: { $sum: "$amount" }, count: { $sum: 1 } }}, { $sort: { total: -1 }}, { $limit: 10 } ]);

⚡ Redis — Structures & Patterns

🗃 Structures Redis
  • String — cache simple, compteurs
  • Hash — objets (user:123 → {name, email})
  • List — queues, LIFO/FIFO
  • Set — tags, unicité
  • Sorted Set — leaderboards, rate limiting
  • Stream — event log, alternative Kafka léger
🔄 Cache Patterns
  • Cache-Aside — app gère miss/hit manuellement
  • Write-Through — écriture sync en cache+DB
  • Write-Behind — écriture async en DB
  • Read-Through — cache charge depuis DB si miss

Attention au Cache Stampede (thundering herd) sur expiration.

⚡ Quiz Rapide — Bases de données
1. Quel type d'index PostgreSQL est optimal pour une recherche sur colonne JSONB ?
A
B-Tree
B
Hash
C
GIN
D
BRIN
✅ C. GIN (Generalized Inverted Index). Conçu pour JSONB, full-text search et arrays. Indexe chaque clé/valeur dans le document JSON. B-Tree ne supporte pas les opérateurs JSONB @> ou ?.
2. Quel pattern Redis utiliser pour éviter qu'un seul service surcharge la DB après expiration du cache ?
A
Cache-Aside simple
B
Lock distribué (SETNX) + TTL aléatoire (jitter)
C
Write-Behind
D
Read-Through uniquement
✅ B. Le Cache Stampede = N services frappent la DB simultanément après expiration. Solutions : SETNX comme lock distribué (1 seul recharge), TTL avec jitter (expiration aléatoire), ou pre-warming avant expiration.
3. Quelle est la différence entre WHERE et HAVING en SQL ?
A
WHERE filtre après GROUP BY, HAVING filtre avant
B
WHERE filtre les lignes avant agrégation, HAVING filtre les groupes après agrégation
C
Ils sont interchangeables
D
HAVING filtre uniquement les colonnes texte
✅ B. WHERE s'exécute avant GROUP BY et filtre les lignes individuelles. HAVING s'exécute après GROUP BY et filtre les groupes agrégés. Exemple : HAVING COUNT(*) > 5 ne peut pas s'écrire avec WHERE car COUNT() n'existe pas encore à ce stade.
Communication Angular ↔ Spring Boot
Flux complet : formulaire → API → DB → réponse
📖 Cours complet — 15 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/15 maîtrisées

1Le contrat entre le front et l'API9

Architecture SPA + API RESTLe front Angular (Single Page Application) est servi une fois en fichiers statiques puis échange uniquement des données JSON avec l'API Spring Boot ; le serveur ne génère plus de HTML.

Le front Angular (Single Page Application) est servi une fois en fichiers statiques puis échange uniquement des données JSON avec l'API Spring Boot ; le serveur ne génère plus de HTML.

Les deux applications sont déployées et versionnées séparément. Conséquences : CORS (domaines différents), authentification par token, contrat d'API à maintenir (OpenAPI), gestion des erreurs côté client.

text
Navigateur ─► https://app.banque.sn (Angular, NGINX/CDN)
           └─► https://api.banque.sn/api/v1/... (Spring Boot, JSON)
Sérialisation JSON (Jackson ↔ TypeScript)Spring transforme les objets Java en JSON (Jackson) ; Angular reçoit ce JSON et le type avec une interface TypeScript. Les deux doivent correspondre champ par champ.

Spring transforme les objets Java en JSON (Jackson) ; Angular reçoit ce JSON et le type avec une interface TypeScript. Les deux doivent correspondre champ par champ.

TypeScript ne vérifie rien à l'exécution : si Java renomme nomComplet en nom, Angular affichera undefined sans erreur. D'où l'intérêt d'un contrat OpenAPI et de clients générés.

java
public record ClientDto(Long id, String nom, String email, LocalDate dateNaissance) {}
typescript
export interface ClientDto { id: number; nom: string; email: string; dateNaissance: string; } // ISO "1990-05-12"
Dates et fuseaux horaires dans le JSONÉchanger les dates en texte ISO-8601 : LocalDate → "2026-10-14", Instant → "2026-10-14T16:33:00Z" (UTC). Le front les convertit pour l'affichage dans le fuseau de l'utilisateur.

Échanger les dates en texte ISO-8601 : LocalDate → "2026-10-14", Instant → "2026-10-14T16:33:00Z" (UTC). Le front les convertit pour l'affichage dans le fuseau de l'utilisateur.

java
public record OperationDto(Instant horodatage, LocalDate dateValeur) {}
// {"horodatage":"2026-10-14T16:33:00Z","dateValeur":"2026-10-15"}
typescript
const d = new Date(op.horodatage);           // instant précis
{{ op.horodatage | date:'dd/MM/yyyy HH:mm' }}
⚠️ Piègenew Date("2026-10-15") est interprété en UTC minuit : selon le fuseau du navigateur, la date affichée peut reculer d'un jour. Garder les dates sans heure comme chaînes ou utiliser une bibliothèque de dates.
Montants : BigDecimal ↔ JSONLes montants sont des BigDecimal côté Java ; en JSON, les envoyer comme chaîne ("15000.50") ou en entier d'unités mineures évite les erreurs d'arrondi des nombres flottants JavaScript.

Les montants sont des BigDecimal côté Java ; en JSON, les envoyer comme chaîne ("15000.50") ou en entier d'unités mineures évite les erreurs d'arrondi des nombres flottants JavaScript.

java
public record SoldeDto(@JsonFormat(shape = JsonFormat.Shape.STRING) BigDecimal solde, String devise) {}
// {"solde":"1250000.75","devise":"XOF"}
typescript
0.1 + 0.2;   // 0.30000000000000004 en JavaScript → ne jamais calculer de l'argent en number côté front
🎯 En entretienCôté front : afficher, ne pas recalculer. Les calculs financiers se font côté serveur.
Bearer token (en-tête Authorization)Le front envoie le jeton d'accès dans l'en-tête Authorization: Bearer <token> de chaque requête ; « bearer » signifie que quiconque possède le jeton est considéré comme authentifié.

Le front envoie le jeton d'accès dans l'en-tête Authorization: Bearer <token> de chaque requête ; « bearer » signifie que quiconque possède le jeton est considéré comme authentifié.

typescript
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).accessToken();
  if (!token || !req.url.startsWith(environment.apiUrl)) return next(req);   // jamais vers un domaine tiers
  return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};
⚠️ PiègeNe jamais ajouter le token aux requêtes vers d'autres domaines (CDN, API tierces) : il fuirait.
Requête preflight CORS (OPTIONS)Avant une requête « non simple » (JSON, en-tête Authorization, PUT/DELETE) vers un autre domaine, le navigateur envoie une requête OPTIONS pour demander l'autorisation ; si la réponse ne contient pas les bons en-têtes CORS, la vraie requête n'est jamais envoyée.

Avant une requête « non simple » (JSON, en-tête Authorization, PUT/DELETE) vers un autre domaine, le navigateur envoie une requête OPTIONS pour demander l'autorisation ; si la réponse ne contient pas les bons en-têtes CORS, la vraie requête n'est jamais envoyée.

http
OPTIONS /api/v1/virements
Origin: https://app.banque.sn
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

HTTP/1.1 200
Access-Control-Allow-Origin: https://app.banque.sn
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: authorization, content-type
🎯 En entretienSi le preflight est bloqué par Spring Security (401), configurer CORS dans la SecurityFilterChain (http.cors(...)) pour qu'il soit traité avant l'authentification.
Proxy de développement Angular (proxy.conf.json)En développement, ng serve peut relayer les appels /api vers Spring (localhost:8080) : le navigateur ne voit qu'une seule origine, donc pas de problème CORS en local.

En développement, ng serve peut relayer les appels /api vers Spring (localhost:8080) : le navigateur ne voit qu'une seule origine, donc pas de problème CORS en local.

json
{
  "/api": { "target": "http://localhost:8080", "secure": false, "changeOrigin": true }
}
bash
ng serve --proxy-config proxy.conf.json
OpenAPI / Swagger (springdoc)OpenAPI est un format standard qui décrit une API REST (endpoints, paramètres, schémas, codes de retour) ; springdoc le génère depuis le code Spring et fournit l'interface Swagger UI.

OpenAPI est un format standard qui décrit une API REST (endpoints, paramètres, schémas, codes de retour) ; springdoc le génère depuis le code Spring et fournit l'interface Swagger UI.

xml
<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
java
@Operation(summary = "Créer un client", responses = {
    @ApiResponse(responseCode = "201", description = "Client créé"),
    @ApiResponse(responseCode = "409", description = "Email déjà utilisé") })
@PostMapping
ResponseEntity<ClientDto> creer(@Valid @RequestBody CreateClientRequest req) { … }
// Swagger UI : /swagger-ui.html — spec : /v3/api-docs
Génération du client Angular depuis OpenAPIÀ partir de la spec OpenAPI, un générateur produit les interfaces TypeScript et les services HttpClient : le front ne peut plus diverger du back sans erreur de compilation.

À partir de la spec OpenAPI, un générateur produit les interfaces TypeScript et les services HttpClient : le front ne peut plus diverger du back sans erreur de compilation.

bash
npx @openapitools/openapi-generator-cli generate \
  -i http://localhost:8080/v3/api-docs -g typescript-angular -o src/app/api

2Erreurs, fichiers et temps réel6

Codes de statut HTTPNombre à 3 chiffres qui résume le résultat : 2xx succès, 3xx redirection, 4xx erreur du client, 5xx erreur du serveur.

Nombre à 3 chiffres qui résume le résultat : 2xx succès, 3xx redirection, 4xx erreur du client, 5xx erreur du serveur.

CodeSignificationExemple
200 OKSuccès avec corpsGET d'un compte
201 CreatedRessource créée (+ Location)POST d'un virement
204 No ContentSuccès sans corpsDELETE
400 Bad RequestRequête invalideValidation @Valid échouée
401 UnauthorizedNon authentifiéToken absent ou expiré
403 ForbiddenAuthentifié mais non autoriséClient qui lit le compte d'un autre
404 Not FoundRessource inexistanteCompte introuvable
409 ConflictConflit d'étatEmail déjà utilisé, version obsolète
422 Unprocessable ContentRègle métier violéeSolde insuffisant
429 Too Many RequestsLimite de débit atteinteTrop d'OTP demandés
500 Internal Server ErrorBug serveurException non gérée
503 Service UnavailableIndisponible temporairementMaintenance, surcharge
401 vs 403401 : « je ne sais pas qui tu es » (pas de token, token invalide ou expiré) → se reconnecter. 403 : « je sais qui tu es, mais tu n'as pas le droit » → message d'accès refusé.

401 : « je ne sais pas qui tu es » (pas de token, token invalide ou expiré) → se reconnecter. 403 : « je sais qui tu es, mais tu n'as pas le droit » → message d'accès refusé.

typescript
catchError((e: HttpErrorResponse) => {
  if (e.status === 401) { auth.rafraichirOuReconnecter(); }
  else if (e.status === 403) { toast.erreur("Vous n'avez pas les droits pour cette action"); }
  return throwError(() => e);
})
HttpErrorResponse et gestion d'erreurs côté AngularObjet reçu par Angular quand une requête échoue : status, message, et error (le corps renvoyé par Spring, par exemple un ProblemDetail).

Objet reçu par Angular quand une requête échoue : status, message, et error (le corps renvoyé par Spring, par exemple un ProblemDetail).

typescript
this.api.virer(demande).subscribe({
  next: () => this.toast.succes('Virement effectué'),
  error: (e: HttpErrorResponse) => {
    const pb = e.error as { title?: string; detail?: string };
    this.toast.erreur(pb?.detail ?? 'Erreur inattendue, réessayez plus tard');
  },
});
🎯 En entretienFormat d'erreur unique côté Spring (ProblemDetail) = un seul code de gestion côté Angular.
Upload de fichier (multipart)Envoi de fichier en multipart/form-data : Angular construit un FormData, Spring le reçoit en MultipartFile. Taille et type doivent être vérifiés côté serveur.

Envoi de fichier en multipart/form-data : Angular construit un FormData, Spring le reçoit en MultipartFile. Taille et type doivent être vérifiés côté serveur.

typescript
const fd = new FormData();
fd.append('fichier', file);
fd.append('type', 'PIECE_IDENTITE');
this.http.post('/api/v1/kyc/documents', fd, { reportProgress: true, observe: 'events' });
java
@PostMapping(path = "/kyc/documents", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
DocumentDto deposer(@RequestPart("fichier") MultipartFile fichier, @RequestPart("type") String type) {
    if (!List.of("application/pdf", "image/jpeg", "image/png").contains(fichier.getContentType()))
        throw new FormatNonAutoriseException();
    return service.stocker(fichier, type);
}
properties
spring.servlet.multipart.max-file-size=5MB
Téléchargement de fichier (Blob)Spring renvoie des octets avec Content-Type et Content-Disposition ; Angular les reçoit en Blob (responseType: 'blob') et propose l'enregistrement.

Spring renvoie des octets avec Content-Type et Content-Disposition ; Angular les reçoit en Blob (responseType: 'blob') et propose l'enregistrement.

java
@GetMapping("/releves/{mois}.pdf")
ResponseEntity<byte[]> releve(@PathVariable YearMonth mois) {
    return ResponseEntity.ok()
        .contentType(MediaType.APPLICATION_PDF)
        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=releve-" + mois + ".pdf")
        .body(service.pdf(mois));
}
typescript
this.http.get(`/api/v1/releves/${mois}.pdf`, { responseType: 'blob' }).subscribe(blob => {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href = url; a.download = `releve-${mois}.pdf`; a.click();
  URL.revokeObjectURL(url);
});
Temps réel : Server-Sent Events et WebSocketSSE : flux unidirectionnel serveur → navigateur sur HTTP (notifications, statut d'un paiement). WebSocket : canal bidirectionnel permanent (chat, cotations). Alternative simple : le polling.

SSE : flux unidirectionnel serveur → navigateur sur HTTP (notifications, statut d'un paiement). WebSocket : canal bidirectionnel permanent (chat, cotations). Alternative simple : le polling.

java
@GetMapping(path = "/paiements/{id}/statut", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
SseEmitter suivre(@PathVariable String id) { return suiviService.abonner(id); }
typescript
const source = new EventSource(`/api/v1/paiements/${id}/statut`);
source.onmessage = e => this.statut.set(JSON.parse(e.data));
⚠️ PiègeEventSource ne permet pas d'ajouter un en-tête Authorization : il faut un cookie, un jeton à usage unique dans l'URL, ou une bibliothèque SSE basée sur fetch.

📋 Synthèse express — Angular↔Spring

🔄 Flux en 8 étapes

Parcours complet d'une requête fullstack :
#CoucheRôle
1Formulaire Angular (Reactive Form)Saisie utilisateur + validation UX côté client
2Composant → Service AngularAppel HttpClient avec le DTO sérialisé en JSON
3Intercepteur HTTP AngularAjout du header Authorization: Bearer <token>
4Spring Security (filtre)Authentification JWT + autorisation (rôles/scopes)
5Controller Spring (@RestController)Validation @Valid du DTO + mapping HTTP
6Service Spring (@Service @Transactional)Logique métier, règles, transaction
7Repository JPA (@Repository)Requêtes DB, persistance Entity
8Réponse DTO → AngularCode HTTP + JSON → Angular met à jour le template
⚠️ La validation front améliore l'UX mais ne remplace JAMAIS la validation backend. Un attaquant peut bypasser Angular. Toujours valider côté Spring (@Valid + @RestControllerAdvice).

📦 Exemple concret — Création d'un client

// Angular Service @Injectable({ providedIn: 'root' }) export class ClientService { constructor(private http: HttpClient) {} create(data: CreateClientRequest): Observable<ClientDto> { return this.http.post<ClientDto>('/api/v1/clients', data); } } // Spring Controller @PostMapping @ResponseStatus(HttpStatus.CREATED) public ClientDto create(@Valid @RequestBody CreateClientRequest req) { return service.create(req); // retourne le DTO (jamais l'Entity) }

🔧 Points d'attention clés

🌍 CORS côté Spring

Configurer @CrossOrigin ou une CorsConfigurationSource globale pour autoriser les requêtes depuis http://localhost:4200 en dev et le domaine de prod.

📝 Contrats JSON alignés

Les DTOs TypeScript et les records/DTO Java doivent correspondre champ par champ. Documenter avec OpenAPI/Swagger (@Operation, @Schema). Utiliser une spec OpenAPI partagée si possible.

🚨 Gestion des erreurs HTTP

Angular : intercepter les erreurs dans le service avec catchError. Gérer 401 (redirect login), 403 (accès refusé), 404, 409 (conflit), 500. Afficher des messages utilisateur adaptés.

🔐 Sécurité Angular

Ne jamais stocker les secrets dans le bundle Angular. Guards Angular = UX seulement, pas sécurité réelle. La vraie sécurité est côté Spring Security. Environments Angular pour les URLs.

Messaging — Kafka, RabbitMQ, SQS
Partitions, consumer groups, garanties de livraison
📖 Cours complet — 48 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/48 maîtrisées

1Fondamentaux de la messagerie4

Message brokerIntermédiaire qui reçoit des messages de producteurs, les stocke et les distribue à des consommateurs ; il découple les services dans le temps (l'un peut être arrêté) et en charge (il absorbe les pics).

Intermédiaire qui reçoit des messages de producteurs, les stocke et les distribue à des consommateurs ; il découple les services dans le temps (l'un peut être arrêté) et en charge (il absorbe les pics).

Exemples : Apache Kafka (journal d'événements distribué), RabbitMQ (routage de messages AMQP), AWS SQS/SNS (service managé).

text
Service Virement ──publie──► [Broker] ──distribue──► Service Notification
                                     └────────────► Service Anti-fraude
Point-à-point (queue) vs Publish/SubscribePoint-à-point : chaque message est traité par un seul consommateur (répartition de travail). Pub/Sub : chaque message est reçu par tous les abonnés intéressés (diffusion d'événements).

Point-à-point : chaque message est traité par un seul consommateur (répartition de travail). Pub/Sub : chaque message est reçu par tous les abonnés intéressés (diffusion d'événements).

Avec Kafka, les deux se font avec le même topic : consommateurs du même groupe → répartition ; groupes différents → chacun reçoit tout.

Événement, commande et messageUn événement annonce un fait passé (« VirementEffectué ») sans savoir qui l'écoute ; une commande demande une action à un destinataire précis (« EnvoyerSms ») ; les deux voyagent sous forme de messages.

Un événement annonce un fait passé (« VirementEffectué ») sans savoir qui l'écoute ; une commande demande une action à un destinataire précis (« EnvoyerSms ») ; les deux voyagent sous forme de messages.

java
public record VirementEffectue(UUID virementId, String compte, BigDecimal montant, Instant date) {}  // événement
public record EnvoyerSms(String telephone, String texte) {}                                         // commande
Backpressure (contre-pression)Capacité d'un consommateur à signaler qu'il est saturé pour que le producteur ralentisse, au lieu d'accumuler des messages en mémoire jusqu'à la panne.

Capacité d'un consommateur à signaler qu'il est saturé pour que le producteur ralentisse, au lieu d'accumuler des messages en mémoire jusqu'à la panne.

Avec Kafka, la contre-pression est naturelle : le consommateur *tire* (poll) les messages à son rythme et le retard se mesure par le Consumer lag. Avec RabbitMQ, on la règle par le Prefetch.

2Apache Kafka23

Apache KafkaPlateforme distribuée de streaming d'événements : un journal (log) append-only, partitionné et répliqué, où les messages sont conservés selon une durée de rétention et peuvent être relus.

Plateforme distribuée de streaming d'événements : un journal (log) append-only, partitionné et répliqué, où les messages sont conservés selon une durée de rétention et peuvent être relus.

Différence clé avec une file classique : lire un message ne le supprime pas. Chaque groupe de consommateurs garde sa position (offset), on peut donc rejouer l'historique ou ajouter un nouveau consommateur qui relit tout.

🎯 En entretienDepuis Kafka 4.0 (2025), ZooKeeper a disparu : le cluster gère ses métadonnées lui-même avec KRaft.
TopicCatégorie nommée de messages (ex. virements.effectues) dans laquelle les producteurs écrivent et les consommateurs lisent ; physiquement découpé en partitions.

Catégorie nommée de messages (ex. virements.effectues) dans laquelle les producteurs écrivent et les consommateurs lisent ; physiquement découpé en partitions.

bash
kafka-topics.sh --create --topic virements.effectues --partitions 12 --replication-factor 3 \
  --bootstrap-server kafka:9092
PartitionSous-ensemble ordonné et immuable d'un topic ; unité de parallélisme et d'ordre. L'ordre des messages n'est garanti qu'à l'intérieur d'une partition.

Sous-ensemble ordonné et immuable d'un topic ; unité de parallélisme et d'ordre. L'ordre des messages n'est garanti qu'à l'intérieur d'une partition.

Plus de partitions = plus de consommateurs possibles en parallèle dans un groupe. On peut en ajouter mais pas en retirer, et l'ajout change la répartition des clés.

text
Topic virements (3 partitions)
P0: [m0][m3][m6] →
P1: [m1][m4][m7] →
P2: [m2][m5][m8] →
OffsetNuméro séquentiel d'un message dans sa partition ; le consommateur enregistre (commit) l'offset du dernier message traité pour reprendre au bon endroit après un redémarrage.

Numéro séquentiel d'un message dans sa partition ; le consommateur enregistre (commit) l'offset du dernier message traité pour reprendre au bon endroit après un redémarrage.

Broker et cluster KafkaUn broker est un serveur Kafka qui stocke des partitions et répond aux clients ; un cluster regroupe plusieurs brokers pour la capacité et la tolérance aux pannes.

Un broker est un serveur Kafka qui stocke des partitions et répond aux clients ; un cluster regroupe plusieurs brokers pour la capacité et la tolérance aux pannes.

Réplication, leader et ISRChaque partition est copiée sur plusieurs brokers (replication factor, souvent 3) : un leader reçoit écritures et lectures, les followers se synchronisent ; l'ISR (In-Sync Replicas) est la liste des répliques à jour.

Chaque partition est copiée sur plusieurs brokers (replication factor, souvent 3) : un leader reçoit écritures et lectures, les followers se synchronisent ; l'ISR (In-Sync Replicas) est la liste des répliques à jour.

Si le broker leader tombe, une réplique de l'ISR devient leader. Avec acks=all et min.insync.replicas=2, un message acquitté existe sur au moins 2 brokers.

KRaft (fin de ZooKeeper)Mode de Kafka où les métadonnées du cluster sont gérées par Kafka lui-même via un protocole de consensus (Raft) ; seul mode disponible depuis Kafka 4.0, ZooKeeper n'est plus nécessaire.

Mode de Kafka où les métadonnées du cluster sont gérées par Kafka lui-même via un protocole de consensus (Raft) ; seul mode disponible depuis Kafka 4.0, ZooKeeper n'est plus nécessaire.

ProducerClient qui publie des messages dans un topic ; il choisit la partition (via la clé), regroupe les messages par lots et attend les acquittements selon acks.

Client qui publie des messages dans un topic ; il choisit la partition (via la clé), regroupe les messages par lots et attend les acquittements selon acks.

java
@Service
public class VirementPublisher {
    private final KafkaTemplate<String, VirementEffectue> kafka;
    public VirementPublisher(KafkaTemplate<String, VirementEffectue> kafka) { this.kafka = kafka; }
    public void publier(VirementEffectue evt) {
        kafka.send("virements.effectues", evt.compte(), evt)        // clé = compte → même partition
             .whenComplete((res, ex) -> { if (ex != null) log.error("Publication échouée", ex); });
    }
}
Clé de message et ordreLa clé (ex. numéro de compte) détermine la partition par hachage : tous les messages d'une même clé vont dans la même partition et sont donc traités dans l'ordre.

La clé (ex. numéro de compte) détermine la partition par hachage : tous les messages d'une même clé vont dans la même partition et sont donc traités dans l'ordre.

⚠️ PiègeUne clé trop concentrée (un seul gros client) crée une partition « chaude » ; une clé null répartit les messages sans garantie d'ordre.
acks (acquittements du producteur)Niveau de confirmation attendu : acks=0 (aucune, rapide mais pertes possibles), acks=1 (le leader a écrit), acks=all (toutes les répliques de l'ISR ont écrit : le plus sûr, défaut depuis Kafka 3.0).

Niveau de confirmation attendu : acks=0 (aucune, rapide mais pertes possibles), acks=1 (le leader a écrit), acks=all (toutes les répliques de l'ISR ont écrit : le plus sûr, défaut depuis Kafka 3.0).

properties
spring.kafka.producer.acks=all
spring.kafka.producer.properties.min.insync.replicas=2   # côté topic/broker en pratique
Producteur idempotentAvec enable.idempotence=true (défaut depuis Kafka 3.0), le broker élimine les doublons créés par les relances du producteur grâce à un identifiant de producteur et des numéros de séquence.

Avec enable.idempotence=true (défaut depuis Kafka 3.0), le broker élimine les doublons créés par les relances du producteur grâce à un identifiant de producteur et des numéros de séquence.

Cela évite les doublons dus au réseau entre producteur et broker, mais pas les doublons métier (même événement publié deux fois par l'application) ni ceux côté consommateur.

ConsumerClient qui lit les messages d'un ou plusieurs topics en boucle (poll), les traite, puis enregistre son offset.

Client qui lit les messages d'un ou plusieurs topics en boucle (poll), les traite, puis enregistre son offset.

java
@KafkaListener(topics = "virements.effectues", groupId = "notification-service")
public void onVirement(VirementEffectue evt) {
    notificationService.envoyer(evt);
}
Consumer groupEnsemble de consommateurs partageant un group.id : chaque partition est attribuée à un seul membre du groupe, ce qui répartit la charge. Chaque groupe lit le topic indépendamment.

Ensemble de consommateurs partageant un group.id : chaque partition est attribuée à un seul membre du groupe, ce qui répartit la charge. Chaque groupe lit le topic indépendamment.

text
Topic 4 partitions, groupe "notif" avec 2 instances → 2 partitions chacune
groupe "notif" avec 6 instances → 4 actives, 2 inactives (pas de partition libre)
groupe "antifraude" → reçoit aussi TOUS les messages, indépendamment
🎯 En entretienParallélisme max d'un groupe = nombre de partitions du topic.
RebalancingRedistribution des partitions entre les membres d'un groupe quand un consommateur arrive, part ou ne répond plus ; la consommation est brièvement perturbée.

Redistribution des partitions entre les membres d'un groupe quand un consommateur arrive, part ou ne répond plus ; la consommation est brièvement perturbée.

Un traitement trop long (au-delà de max.poll.interval.ms) fait croire au coordinateur que le consommateur est mort → rebalance → le message est retraité par un autre : doublons. Le protocole de rebalance incrémental (Kafka 4) réduit ces interruptions.

Commit d'offset (automatique vs manuel)Enregistrer la position de lecture : automatiquement à intervalles (simple, mais risque de perte ou doublon) ou manuellement après traitement réussi (au moins une fois).

Enregistrer la position de lecture : automatiquement à intervalles (simple, mais risque de perte ou doublon) ou manuellement après traitement réussi (au moins une fois).

properties
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.listener.ack-mode=record     # Spring commit après chaque message traité sans exception
auto-offset-reset (earliest / latest)Où commence un groupe qui n'a encore aucun offset enregistré : earliest = au début du topic (tout relire), latest = seulement les nouveaux messages.

Où commence un groupe qui n'a encore aucun offset enregistré : earliest = au début du topic (tout relire), latest = seulement les nouveaux messages.

properties
spring.kafka.consumer.auto-offset-reset=earliest
Consumer lagÉcart entre le dernier message écrit dans une partition et le dernier offset traité par le groupe : c'est le retard du consommateur, la métrique n°1 à surveiller.

Écart entre le dernier message écrit dans une partition et le dernier offset traité par le groupe : c'est le retard du consommateur, la métrique n°1 à surveiller.

bash
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group notification-service
# TOPIC               PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG
# virements.effectues 0          15230           15890           660
Rétention et log compactionLes messages sont conservés pendant une durée ou une taille donnée (retention.ms), puis supprimés ; la compaction garde seulement le dernier message de chaque clé (état courant).

Les messages sont conservés pendant une durée ou une taille donnée (retention.ms), puis supprimés ; la compaction garde seulement le dernier message de chaque clé (état courant).

bash
kafka-configs.sh --alter --entity-type topics --entity-name clients.etat \
  --add-config cleanup.policy=compact --bootstrap-server kafka:9092
Transactions Kafka (exactly-once)Un producteur transactionnel écrit dans plusieurs partitions et commit les offsets consommés de façon atomique ; les consommateurs en read_committed ne voient que les transactions validées.

Un producteur transactionnel écrit dans plusieurs partitions et commit les offsets consommés de façon atomique ; les consommateurs en read_committed ne voient que les transactions validées.

properties
spring.kafka.producer.transaction-id-prefix=virement-tx-
spring.kafka.consumer.isolation-level=read_committed
⚠️ PiègeL'exactly-once Kafka couvre le cycle lire-Kafka → écrire-Kafka. Dès qu'il y a un effet externe (base, SMS, API), il faut un consommateur idempotent.
Schema Registry et AvroLe Schema Registry stocke les schémas (Avro, Protobuf, JSON Schema) des messages et vérifie leur compatibilité à chaque évolution, pour qu'un producteur ne casse pas les consommateurs.

Le Schema Registry stocke les schémas (Avro, Protobuf, JSON Schema) des messages et vérifie leur compatibilité à chaque évolution, pour qu'un producteur ne casse pas les consommateurs.

json
{ "type": "record", "name": "VirementEffectue",
  "fields": [ {"name": "id", "type": "string"},
              {"name": "montant", "type": {"type": "bytes", "logicalType": "decimal", "precision": 15, "scale": 2}},
              {"name": "canal", "type": ["null", "string"], "default": null} ] }
🎯 En entretienRègle de compatibilité ascendante : on ajoute des champs optionnels avec valeur par défaut, on ne renomme ni ne supprime.
KafkaTemplate et @KafkaListener (Spring Kafka)KafkaTemplate envoie des messages, @KafkaListener transforme une méthode en consommateur ; Spring Boot configure tout à partir des propriétés spring.kafka.*.

KafkaTemplate envoie des messages, @KafkaListener transforme une méthode en consommateur ; Spring Boot configure tout à partir des propriétés spring.kafka.*.

properties
spring.kafka.bootstrap-servers=kafka:9092
spring.kafka.consumer.group-id=order-service
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.producer.acks=all
spring.kafka.producer.properties.enable.idempotence=true
spring.kafka.listener.concurrency=3          # 3 threads consommateurs par instance
Dead Letter Topic et retries non bloquantsUn message qui échoue après N tentatives est déplacé dans un topic « poubelle » (DLT) pour analyse, au lieu de bloquer la partition. @RetryableTopic ajoute des topics de retry avec délais croissants.

Un message qui échoue après N tentatives est déplacé dans un topic « poubelle » (DLT) pour analyse, au lieu de bloquer la partition. @RetryableTopic ajoute des topics de retry avec délais croissants.

java
@RetryableTopic(attempts = "4", backoff = @Backoff(delay = 1000, multiplier = 2),
                exclude = ValidationException.class)          // pas de retry pour une donnée invalide
@KafkaListener(topics = "paiements.recus")
public void traiter(PaiementRecu p) { service.crediter(p); }

@DltHandler
public void dlt(PaiementRecu p, @Header(KafkaHeaders.EXCEPTION_MESSAGE) String erreur) {
    alerteService.signaler(p, erreur);                       // supervision + rejeu manuel
}
Kafka StreamsBibliothèque Java de traitement de flux au-dessus de Kafka : filtrer, agréger, joindre des topics en continu (ex. total des retraits par carte sur 10 minutes pour la détection de fraude).

Bibliothèque Java de traitement de flux au-dessus de Kafka : filtrer, agréger, joindre des topics en continu (ex. total des retraits par carte sur 10 minutes pour la détection de fraude).

java
builder.stream("retraits", Consumed.with(Serdes.String(), retraitSerde))
       .groupByKey()
       .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(10)))
       .aggregate(() -> BigDecimal.ZERO, (carte, r, total) -> total.add(r.montant()), Materialized.with(...))
       .toStream()
       .filter((carte, total) -> total.compareTo(SEUIL) > 0)
       .to("alertes.fraude");

3Garanties de livraison6

Garanties de livraison (vue d'ensemble)Promesse d'un système de messagerie sur le nombre de fois qu'un message est traité : au plus une fois (pertes possibles), au moins une fois (doublons possibles) ou exactement une fois.

Promesse d'un système de messagerie sur le nombre de fois qu'un message est traité : au plus une fois (pertes possibles), au moins une fois (doublons possibles) ou exactement une fois.

Tout dépend du moment où l'on enregistre la progression (offset, ACK) par rapport au traitement : avant → at-most-once, après → at-least-once. En pratique : at-least-once + consommateur idempotent.

At-most-onceChaque message est livré au plus une fois : jamais de doublon, mais des pertes possibles (offset commité avant le traitement). Acceptable pour des métriques non critiques.

Chaque message est livré au plus une fois : jamais de doublon, mais des pertes possibles (offset commité avant le traitement). Acceptable pour des métriques non critiques.

At-least-onceChaque message est livré au moins une fois : jamais de perte, mais des doublons possibles (traitement fait, puis crash avant le commit). Garantie la plus courante ; impose des consommateurs idempotents.

Chaque message est livré au moins une fois : jamais de perte, mais des doublons possibles (traitement fait, puis crash avant le commit). Garantie la plus courante ; impose des consommateurs idempotents.

text
1. poll → message 42
2. traitement OK (SMS envoyé)
3. 💥 crash avant commit de l'offset
4. redémarrage → message 42 relu → SMS envoyé une 2e fois si le consommateur n'est pas idempotent
Exactly-onceChaque message produit exactement un effet. Possible de bout en bout dans Kafka (producteur idempotent + transactions) ; avec des systèmes externes, on l'obtient par at-least-once + idempotence.

Chaque message produit exactement un effet. Possible de bout en bout dans Kafka (producteur idempotent + transactions) ; avec des systèmes externes, on l'obtient par at-least-once + idempotence.

Consommateur idempotentConsommateur qui détecte et ignore les messages déjà traités (identifiant unique mémorisé en base dans la même transaction que l'effet), rendant les doublons inoffensifs.

Consommateur qui détecte et ignore les messages déjà traités (identifiant unique mémorisé en base dans la même transaction que l'effet), rendant les doublons inoffensifs.

java
@KafkaListener(topics = "paiements.recus")
@Transactional
public void traiter(PaiementRecu p) {
    try { messagesTraites.save(new MessageTraite(p.id())); }           // PK = id du message
    catch (DataIntegrityViolationException doublon) { return; }        // déjà traité
    comptes.crediter(p.compte(), p.montant());
}
Message poisonMessage qui fait échouer le consommateur à chaque tentative (format invalide, donnée incohérente) ; sans DLT, il bloque indéfiniment la partition.

Message qui fait échouer le consommateur à chaque tentative (format invalide, donnée incohérente) ; sans DLT, il bloque indéfiniment la partition.

🎯 En entretienDistinguer erreurs transitoires (réessayer) et erreurs définitives (envoyer directement en DLT).

4RabbitMQ12

RabbitMQ et AMQPBroker de messages basé sur le protocole AMQP : les producteurs publient vers un exchange, qui route les messages vers des queues selon des règles ; un message consommé et acquitté est supprimé.

Broker de messages basé sur le protocole AMQP : les producteurs publient vers un exchange, qui route les messages vers des queues selon des règles ; un message consommé et acquitté est supprimé.

Points forts : routage riche, priorités, TTL par message, faible latence pour des files de tâches. Kafka est préféré pour un gros débit, la relecture et la rétention longue.

ExchangePoint d'entrée des messages dans RabbitMQ : il ne stocke rien, il route chaque message vers zéro, une ou plusieurs queues selon son type et les bindings.

Point d'entrée des messages dans RabbitMQ : il ne stocke rien, il route chaque message vers zéro, une ou plusieurs queues selon son type et les bindings.

Queue (RabbitMQ)File qui stocke les messages jusqu'à ce qu'un consommateur les traite et les acquitte ; « durable » pour survivre à un redémarrage, idéalement de type « quorum » pour la réplication.

File qui stocke les messages jusqu'à ce qu'un consommateur les traite et les acquitte ; « durable » pour survivre à un redémarrage, idéalement de type « quorum » pour la réplication.

java
@Bean Queue notifications() { return QueueBuilder.durable("notifications").quorum().build(); }
Binding et routing keyUn binding relie un exchange à une queue avec un motif ; la routing key est l'étiquette posée par le producteur sur le message, comparée à ce motif.

Un binding relie un exchange à une queue avec un motif ; la routing key est l'étiquette posée par le producteur sur le message, comparée à ce motif.

java
@Bean Binding b(Queue notifications, TopicExchange evenements) {
    return BindingBuilder.bind(notifications).to(evenements).with("virement.*");
}
rabbitTemplate.convertAndSend("evenements", "virement.effectue", evt);   // routing key
Direct exchangeRoute vers les queues dont la clé de binding est exactement égale à la routing key : du point-à-point ciblé.

Route vers les queues dont la clé de binding est exactement égale à la routing key : du point-à-point ciblé.

Fanout exchangeCopie chaque message dans toutes les queues liées, en ignorant la routing key : diffusion générale (broadcast).

Copie chaque message dans toutes les queues liées, en ignorant la routing key : diffusion générale (broadcast).

Topic exchangeRoute selon un motif sur la routing key découpée par des points : * remplace exactement un mot, # zéro ou plusieurs mots.

Route selon un motif sur la routing key découpée par des points : * remplace exactement un mot, # zéro ou plusieurs mots.

text
binding "virement.*"      reçoit virement.effectue, virement.rejete
binding "*.rejete"        reçoit virement.rejete, paiement.rejete
binding "agence.dakar.#"  reçoit agence.dakar, agence.dakar.plateau.retrait
Headers exchangeRoute selon les en-têtes du message (ex. canal=USSD et pays=SN) au lieu de la routing key ; utile pour des règles multi-critères.

Route selon les en-têtes du message (ex. canal=USSD et pays=SN) au lieu de la routing key ; utile pour des règles multi-critères.

ACK, NACK et requeueLe consommateur confirme le traitement (ACK) pour que RabbitMQ supprime le message ; NACK signale un échec, avec ou sans remise en file (requeue).

Le consommateur confirme le traitement (ACK) pour que RabbitMQ supprime le message ; NACK signale un échec, avec ou sans remise en file (requeue).

⚠️ PiègeNACK avec requeue sur une erreur définitive = boucle infinie. Envoyer plutôt vers une Dead Letter Exchange.
Prefetch (QoS)Nombre maximum de messages non acquittés qu'un consommateur peut recevoir à la fois ; règle l'équilibrage de charge et la contre-pression.

Nombre maximum de messages non acquittés qu'un consommateur peut recevoir à la fois ; règle l'équilibrage de charge et la contre-pression.

properties
spring.rabbitmq.listener.simple.prefetch=10
Dead Letter Exchange (DLX)Exchange où RabbitMQ renvoie les messages rejetés, expirés (TTL) ou en surnombre ; on y branche une queue d'analyse et de rejeu.

Exchange où RabbitMQ renvoie les messages rejetés, expirés (TTL) ou en surnombre ; on y branche une queue d'analyse et de rejeu.

java
QueueBuilder.durable("paiements").deadLetterExchange("paiements.dlx").build();
Kafka vs RabbitMQKafka : journal distribué, rétention, relecture, très gros débit, ordre par partition. RabbitMQ : routage flexible, files de tâches, acquittement par message, suppression après lecture.

Kafka : journal distribué, rétention, relecture, très gros débit, ordre par partition. RabbitMQ : routage flexible, files de tâches, acquittement par message, suppression après lecture.

CritèreKafkaRabbitMQ
ModèleJournal (log) conservéFile, message supprimé après ACK
Relecture✅ (offsets)❌
DébitTrès élevéÉlevé
RoutagePar topic/partitionRiche (direct, topic, headers)
Cas typiqueEvent streaming, CDC, auditTâches asynchrones, RPC, notifications

5Services managés AWS3

Amazon SQS (Standard vs FIFO)File de messages entièrement gérée par AWS. Standard : débit quasi illimité, ordre non garanti, livraison au moins une fois. FIFO : ordre garanti et déduplication, débit limité.

File de messages entièrement gérée par AWS. Standard : débit quasi illimité, ordre non garanti, livraison au moins une fois. FIFO : ordre garanti et déduplication, débit limité.

java
@SqsListener("paiements-a-traiter")          // Spring Cloud AWS
public void traiter(PaiementMessage m) { … }
Visibility timeout (SQS)Délai pendant lequel un message lu est caché aux autres consommateurs ; s'il n'est pas supprimé avant la fin (traitement trop long ou échec), il redevient visible et sera retraité.

Délai pendant lequel un message lu est caché aux autres consommateurs ; s'il n'est pas supprimé avant la fin (traitement trop long ou échec), il redevient visible et sera retraité.

🎯 En entretienRégler le timeout au-dessus du temps de traitement maximal, et configurer une redrive policy vers une DLQ après N réceptions.
Amazon SNS (fan-out)Service pub/sub d'AWS : un message publié sur un topic SNS est poussé à tous les abonnés (files SQS, Lambda, HTTP, SMS). SNS + plusieurs SQS = diffusion fiable vers plusieurs services.

Service pub/sub d'AWS : un message publié sur un topic SNS est poussé à tous les abonnés (files SQS, Lambda, HTTP, SMS). SNS + plusieurs SQS = diffusion fiable vers plusieurs services.

📋 Synthèse express — Messaging

📨 Kafka — Concepts clés

🔀 Partitions & Consumer Groups

Un topic est divisé en partitions. Chaque partition = 1 seul consumer par groupe. Parallélisme = nombre de partitions. Ordre garanti par partition.

📬 Garanties de livraison
  • At-most-once — peut perdre des messages
  • At-least-once — peut dupliquer (défaut)
  • Exactly-once — idempotent producer + transactions
⚙️ Config Spring Kafka
spring.kafka.consumer.group-id=order-service spring.kafka.consumer.auto-offset-reset=earliest spring.kafka.producer.acks=all spring.kafka.producer.enable-idempotence=true

🐇 RabbitMQ — Types d'Exchange

ExchangeRoutageCas d'usage
DirectRouting key exactePoint-to-point
FanoutBroadcast vers toutes les queues liéesNotifications globales
TopicPattern *.orange.* ou lazy.#Pub/Sub filtré
HeadersHeaders du messageRoutage complexe
⚡ Quiz Rapide — Messaging
1. Dans Kafka, combien de consumers peuvent lire simultanément la même partition ?
A
Illimité
B
1 seul consumer par groupe de consumers
C
2 maximum
D
Dépend du nombre de brokers
✅ B. Dans un consumer group, chaque partition est assignée à exactement 1 consumer. Si vous avez plus de consumers que de partitions, les consumers supplémentaires sont inactifs. Pour augmenter le parallélisme = augmenter le nombre de partitions.
Sécurité applicative
OAuth2, JWT, Keycloak, Vault — approche secure-by-design
📖 Cours complet — 55 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/55 maîtrisées

1Identité & contrôle d'accès6

Authentification vs autorisationAuthentification : prouver qui on est (mot de passe, OTP, token). Autorisation : décider ce qu'on a le droit de faire une fois identifié (rôles, propriété de la ressource).

Authentification : prouver qui on est (mot de passe, OTP, token). Autorisation : décider ce qu'on a le droit de faire une fois identifié (rôles, propriété de la ressource).

En HTTP : un échec d'authentification donne 401, un échec d'autorisation donne 403. Les deux doivent être vérifiés côté serveur, jamais seulement dans Angular.

java
.authorizeHttpRequests(a -> a
    .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")      // autorisation par rôle
    .anyRequest().authenticated())                              // authentification requise
Principe du moindre privilègeChaque utilisateur, service ou compte technique ne reçoit que les droits strictement nécessaires à sa tâche, et pas plus longtemps que nécessaire.

Chaque utilisateur, service ou compte technique ne reçoit que les droits strictement nécessaires à sa tâche, et pas plus longtemps que nécessaire.

Exemples : utilisateur de base de l'application sans droit DROP ; token de service limité au scope paiements:lecture ; conteneur exécuté en non-root ; accès production temporaire et tracé.

RBAC (contrôle d'accès par rôle)Les droits sont attachés à des rôles (CLIENT, CONSEILLER, ADMIN) et les utilisateurs reçoivent des rôles. Simple à administrer, mais insuffisant pour « ce client ne voit que SES comptes ».

Les droits sont attachés à des rôles (CLIENT, CONSEILLER, ADMIN) et les utilisateurs reçoivent des rôles. Simple à administrer, mais insuffisant pour « ce client ne voit que SES comptes ».

java
@PreAuthorize("hasRole('CONSEILLER')")
public List<ClientDto> portefeuille() { … }
Contrôle d'accès par ressource (ABAC) et IDORVérifier non seulement le rôle mais aussi que la ressource demandée appartient à l'utilisateur. Sans ce contrôle : IDOR (Insecure Direct Object Reference), première faille du classement OWASP.

Vérifier non seulement le rôle mais aussi que la ressource demandée appartient à l'utilisateur. Sans ce contrôle : IDOR (Insecure Direct Object Reference), première faille du classement OWASP.

java
// ❌ IDOR : un client change l'id dans l'URL et lit le compte d'un autre
@GetMapping("/comptes/{id}") CompteDto get(@PathVariable Long id) { return service.get(id); }

// ✅ la requête est filtrée par le propriétaire authentifié
@GetMapping("/comptes/{id}")
CompteDto get(@PathVariable Long id, @AuthenticationPrincipal Jwt jwt) {
    return service.getPourClient(id, jwt.getSubject());   // 404 si le compte n'est pas à lui
}
MFA et OTPAuthentification multifacteur : combiner quelque chose que l'on sait (mot de passe), possède (téléphone, OTP, clé FIDO2) ou est (biométrie). Exigée pour les opérations sensibles (virement, ajout de bénéficiaire).

Authentification multifacteur : combiner quelque chose que l'on sait (mot de passe), possède (téléphone, OTP, clé FIDO2) ou est (biométrie). Exigée pour les opérations sensibles (virement, ajout de bénéficiaire).

⚠️ PiègeL'OTP par SMS est vulnérable au SIM swap : préférer une application d'authentification ou une validation dans l'app mobile pour les montants élevés.

2OAuth2 & OpenID Connect13

OAuth2Protocole de délégation d'autorisation : une application obtient un jeton d'accès limité pour appeler une API au nom d'un utilisateur, sans jamais connaître son mot de passe.

Protocole de délégation d'autorisation : une application obtient un jeton d'accès limité pour appeler une API au nom d'un utilisateur, sans jamais connaître son mot de passe.

OAuth2 ne dit pas *qui* est l'utilisateur (c'est le rôle d'OpenID Connect) ; il dit *ce que l'application peut faire*. OAuth 2.1 consolide les bonnes pratiques : PKCE obligatoire, flux implicite et « password » supprimés.

Rôles OAuth2Resource Owner (l'utilisateur), Client (l'application Angular ou mobile), Authorization Server (Keycloak, qui émet les jetons), Resource Server (l'API Spring qui vérifie les jetons).

Resource Owner (l'utilisateur), Client (l'application Angular ou mobile), Authorization Server (Keycloak, qui émet les jetons), Resource Server (l'API Spring qui vérifie les jetons).

text
Utilisateur ─(se connecte)─► Keycloak (Authorization Server) ─(access token)─► Angular (Client)
Angular ─(Bearer token)─► API Spring (Resource Server) ─(vérifie signature via JWKS)
OpenID Connect (OIDC)Couche d'identité au-dessus d'OAuth2 : en plus de l'access token, le serveur renvoie un ID token (JWT) qui décrit l'utilisateur authentifié, et expose un endpoint userinfo.

Couche d'identité au-dessus d'OAuth2 : en plus de l'access token, le serveur renvoie un ID token (JWT) qui décrit l'utilisateur authentifié, et expose un endpoint userinfo.

Le scope openid déclenche OIDC. L'ID token est destiné au client (afficher le nom, savoir qui est connecté) ; l'access token est destiné à l'API.

Authorization Code FlowFlux OAuth2 recommandé : le navigateur est redirigé vers le serveur d'autorisation, l'utilisateur s'y authentifie, puis le client reçoit un code à usage unique qu'il échange contre les jetons.

Flux OAuth2 recommandé : le navigateur est redirigé vers le serveur d'autorisation, l'utilisateur s'y authentifie, puis le client reçoit un code à usage unique qu'il échange contre les jetons.

text
1. GET /authorize?response_type=code&client_id=app&redirect_uri=…&scope=openid&state=xyz&code_challenge=…
2. Utilisateur saisit ses identifiants CHEZ Keycloak (jamais dans l'application)
3. Redirection vers redirect_uri?code=abc&state=xyz
4. POST /token  code=abc + code_verifier   →  access_token, refresh_token, id_token
🎯 En entretienLe paramètre state protège contre la falsification de la redirection (CSRF du login) : il doit revenir identique.
PKCE (Proof Key for Code Exchange)Extension du flux Authorization Code pour les clients publics (SPA, mobile) qui ne peuvent pas garder de secret : le client prouve qu'il est bien celui qui a demandé le code.

Extension du flux Authorization Code pour les clients publics (SPA, mobile) qui ne peuvent pas garder de secret : le client prouve qu'il est bien celui qui a demandé le code.

Sans PKCE, un attaquant qui intercepte le code (application malveillante, logs) pourrait l'échanger contre un token. Avec PKCE, il lui manque le code_verifier, resté en mémoire chez le client légitime. Obligatoire en OAuth 2.1, pour tous les clients.

code_verifier et code_challengeLe client génère un secret aléatoire (code_verifier), envoie son empreinte SHA-256 (code_challenge) au début du flux, puis révèle le verifier lors de l'échange ; le serveur vérifie que les deux correspondent.

Le client génère un secret aléatoire (code_verifier), envoie son empreinte SHA-256 (code_challenge) au début du flux, puis révèle le verifier lors de l'échange ; le serveur vérifie que les deux correspondent.

typescript
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));   // 43 caractères
const challenge = base64url(new Uint8Array(
  await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))));
// /authorize?...&code_challenge=<challenge>&code_challenge_method=S256
// /token ...&code_verifier=<verifier>
🎯 En entretienEn pratique, une bibliothèque (angular-oauth2-oidc, keycloak-js) fait tout cela pour toi.
Client Credentials FlowFlux pour la communication service à service, sans utilisateur : le service s'authentifie avec son client_id et son client_secret et obtient un token pour ses propres droits.

Flux pour la communication service à service, sans utilisateur : le service s'authentifie avec son client_id et son client_secret et obtient un token pour ses propres droits.

bash
curl -X POST https://auth.banque.sn/realms/banque/protocol/openid-connect/token \
  -d grant_type=client_credentials -d client_id=service-releves -d client_secret=$SECRET
yaml
spring.security.oauth2.client.registration.keycloak:
  client-id: service-releves
  client-secret: ${KEYCLOAK_SECRET}
  authorization-grant-type: client_credentials
ScopePermission demandée par un client et accordée dans le token (ex. comptes:lecture, virements:ecriture) : elle limite ce que le jeton permet, indépendamment des rôles de l'utilisateur.

Permission demandée par un client et accordée dans le token (ex. comptes:lecture, virements:ecriture) : elle limite ce que le jeton permet, indépendamment des rôles de l'utilisateur.

java
.requestMatchers(HttpMethod.POST, "/api/v1/virements").hasAuthority("SCOPE_virements:ecriture")
Access token, refresh token et ID tokenAccess token : courte durée (5-15 min), envoyé à l'API. Refresh token : plus long, sert uniquement à obtenir un nouvel access token auprès du serveur d'autorisation. ID token : décrit l'utilisateur, pour le client.

Access token : courte durée (5-15 min), envoyé à l'API. Refresh token : plus long, sert uniquement à obtenir un nouvel access token auprès du serveur d'autorisation. ID token : décrit l'utilisateur, pour le client.

⚠️ PiègeNe jamais envoyer le refresh token à l'API ni l'ID token comme jeton d'accès.
Rotation des refresh tokensÀ chaque utilisation, le refresh token est remplacé par un nouveau et l'ancien invalidé ; si un ancien est réutilisé (vol probable), toute la session est révoquée.

À chaque utilisation, le refresh token est remplacé par un nouveau et l'ancien invalidé ; si un ancien est réutilisé (vol probable), toute la session est révoquée.

Flux implicite (déprécié) et OAuth 2.1L'ancien flux implicite renvoyait l'access token directement dans l'URL du navigateur (fuite par l'historique, les logs, le Referer). Il est abandonné au profit d'Authorization Code + PKCE.

L'ancien flux implicite renvoyait l'access token directement dans l'URL du navigateur (fuite par l'historique, les logs, le Referer). Il est abandonné au profit d'Authorization Code + PKCE.

Pattern BFF (Backend For Frontend)Un petit serveur côté front gère le flux OAuth2 et garde les tokens côté serveur ; le navigateur ne reçoit qu'un cookie de session HttpOnly. Pratique recommandée pour les SPA à forts enjeux de sécurité.

Un petit serveur côté front gère le flux OAuth2 et garde les tokens côté serveur ; le navigateur ne reçoit qu'un cookie de session HttpOnly. Pratique recommandée pour les SPA à forts enjeux de sécurité.

Avantage : aucun token accessible au JavaScript, donc un XSS ne peut pas le voler. Contrepartie : il faut gérer CSRF (cookie) et un composant de plus (Spring Cloud Gateway peut jouer ce rôle avec TokenRelay).

Stockage du token dans une SPAlocalStorage est lisible par tout script de la page (XSS = vol du token) ; la mémoire JavaScript est plus sûre mais perdue au rechargement ; le cookie HttpOnly (via BFF) est hors de portée du JavaScript.

localStorage est lisible par tout script de la page (XSS = vol du token) ; la mémoire JavaScript est plus sûre mais perdue au rechargement ; le cookie HttpOnly (via BFF) est hors de portée du JavaScript.

StockageRésiste au XSSPersistantRemarque
localStorage❌✅À éviter pour des tokens sensibles
Mémoire (variable)⚠️ partiel❌+ refresh silencieux
Cookie HttpOnly (BFF)✅✅Gérer CSRF et SameSite

3JWT8

JWT (JSON Web Token)Jeton compact et signé, formé de trois parties encodées en Base64url séparées par des points : header.payload.signature. Le serveur le vérifie sans consulter de base.

Jeton compact et signé, formé de trois parties encodées en Base64url séparées par des points : header.payload.signature. Le serveur le vérifie sans consulter de base.

text
eyJhbGciOiJSUzI1NiIsImtpZCI6ImsxIn0.eyJzdWIiOiI0MiIsImV4cCI6MTc2MDQ0Mjc4MH0.kQ8…
└────────── header ───────────┘ └──────────── payload ───────────┘ └ signature ┘
⚠️ PiègeUn JWT est signé, pas chiffré : n'importe qui peut lire son contenu (jwt.io). Aucune donnée sensible dans le payload.
Structure d'un JWT : header, payload, signatureHeader : algorithme et identifiant de clé (alg, kid). Payload : les claims (qui, pour qui, jusqu'à quand, quels droits). Signature : garantit que header et payload n'ont pas été modifiés.

Header : algorithme et identifiant de clé (alg, kid). Payload : les claims (qui, pour qui, jusqu'à quand, quels droits). Signature : garantit que header et payload n'ont pas été modifiés.

json
// header
{ "alg": "RS256", "typ": "JWT", "kid": "k1" }
// payload
{ "iss": "https://auth.banque.sn/realms/banque", "sub": "42", "aud": "api-banque",
  "exp": 1760442780, "iat": 1760441880, "jti": "a1b2c3", "scope": "comptes:lecture",
  "realm_access": { "roles": ["CLIENT"] } }
Claims standard (iss, sub, aud, exp, iat, jti)iss : émetteur ; sub : identifiant de l'utilisateur ; aud : destinataire prévu ; exp : expiration ; iat : date d'émission ; jti : identifiant unique du jeton (utile pour la révocation et l'anti-rejeu).

iss : émetteur ; sub : identifiant de l'utilisateur ; aud : destinataire prévu ; exp : expiration ; iat : date d'émission ; jti : identifiant unique du jeton (utile pour la révocation et l'anti-rejeu).

HS256 vs RS256HS256 : signature avec un secret partagé (qui peut vérifier peut aussi signer). RS256/ES256 : signature avec une clé privée, vérification avec la clé publique (les APIs ne peuvent pas forger de token).

HS256 : signature avec un secret partagé (qui peut vérifier peut aussi signer). RS256/ES256 : signature avec une clé privée, vérification avec la clé publique (les APIs ne peuvent pas forger de token).

En microservices, RS256 évite de distribuer un secret de signature à chaque service : l'API ne détient que la clé publique, publiée par Keycloak.

JWKS (JSON Web Key Set)Endpoint public qui publie les clés publiques du serveur d'autorisation ; l'API choisit la bonne clé grâce au kid du header, ce qui permet la rotation des clés sans redéploiement.

Endpoint public qui publie les clés publiques du serveur d'autorisation ; l'API choisit la bonne clé grâce au kid du header, ce qui permet la rotation des clés sans redéploiement.

text
GET https://auth.banque.sn/realms/banque/protocol/openid-connect/certs
{ "keys": [ { "kid": "k1", "kty": "RSA", "alg": "RS256", "n": "…", "e": "AQAB" } ] }
Valider un JWT côté APIVérifier : la signature (clé du JWKS), l'algorithme attendu (refuser none), l'expiration (exp), l'émetteur (iss), l'audience (aud), puis extraire rôles et scopes.

Vérifier : la signature (clé du JWKS), l'algorithme attendu (refuser none), l'expiration (exp), l'émetteur (iss), l'audience (aud), puis extraire rôles et scopes.

java
@Bean JwtDecoder jwtDecoder() {
    NimbusJwtDecoder d = JwtDecoders.fromIssuerLocation("https://auth.banque.sn/realms/banque");
    d.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
        JwtValidators.createDefaultWithIssuer("https://auth.banque.sn/realms/banque"),
        new JwtClaimValidator<List<String>>("aud", aud -> aud != null && aud.contains("api-banque"))));
    return d;
}
Révocation d'un JWTUn JWT reste valide jusqu'à son expiration même si l'utilisateur se déconnecte. Parades : durée de vie courte + refresh token révocable, liste noire des jti (Redis), ou introspection auprès du serveur pour les opérations sensibles.

Un JWT reste valide jusqu'à son expiration même si l'utilisateur se déconnecte. Parades : durée de vie courte + refresh token révocable, liste noire des jti (Redis), ou introspection auprès du serveur pour les opérations sensibles.

Base64url n'est pas du chiffrementBase64url est un simple encodage réversible (texte transportable dans une URL) ; décoder un payload de JWT ne demande aucune clé.

Base64url est un simple encodage réversible (texte transportable dans une URL) ; décoder un payload de JWT ne demande aucune clé.

bash
echo 'eyJzdWIiOiI0MiJ9' | base64 -d     # {"sub":"42"}

4Keycloak7

KeycloakServeur open source de gestion des identités et des accès (IAM) : authentification, SSO, OAuth2/OIDC, MFA, fédération LDAP/Active Directory, gestion des rôles et des clients.

Serveur open source de gestion des identités et des accès (IAM) : authentification, SSO, OAuth2/OIDC, MFA, fédération LDAP/Active Directory, gestion des rôles et des clients.

bash
docker run -p 8081:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:26.0 start-dev
RealmEspace d'identité isolé dans Keycloak (utilisateurs, clients, rôles, politiques propres). Un realm par organisation ou environnement ; le realm master sert uniquement à administrer Keycloak.

Espace d'identité isolé dans Keycloak (utilisateurs, clients, rôles, politiques propres). Un realm par organisation ou environnement ; le realm master sert uniquement à administrer Keycloak.

Client Keycloak : public vs confidentielUn client est une application enregistrée dans Keycloak. Public : ne peut pas garder de secret (SPA, mobile) → PKCE. Confidentiel : application serveur qui possède un client_secret.

Un client est une application enregistrée dans Keycloak. Public : ne peut pas garder de secret (SPA, mobile) → PKCE. Confidentiel : application serveur qui possède un client_secret.

Rôles realm et rôles clientRôles realm : globaux au realm (realm_access.roles dans le token). Rôles client : propres à une application (resource_access.<client>.roles). Spring doit être configuré pour les lire.

Rôles realm : globaux au realm (realm_access.roles dans le token). Rôles client : propres à une application (resource_access.<client>.roles). Spring doit être configuré pour les lire.

java
@Bean JwtAuthenticationConverter jwtAuthConverter() {
    var conv = new JwtAuthenticationConverter();
    conv.setJwtGrantedAuthoritiesConverter(jwt -> {
        Map<String, Object> realm = jwt.getClaim("realm_access");
        List<String> roles = realm == null ? List.of() : (List<String>) realm.get("roles");
        return roles.stream().map(r -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + r)).toList();
    });
    return conv;
}
Groupes KeycloakEnsemble d'utilisateurs qui hérite de rôles et d'attributs communs (ex. groupe « Agence Plateau ») : on gère les droits par groupe plutôt qu'utilisateur par utilisateur.

Ensemble d'utilisateurs qui hérite de rôles et d'attributs communs (ex. groupe « Agence Plateau ») : on gère les droits par groupe plutôt qu'utilisateur par utilisateur.

Protocol mapperRègle Keycloak qui ajoute ou transforme un claim dans les tokens (rôles, groupes, attribut agence_id, audience), configurée par client ou via un client scope.

Règle Keycloak qui ajoute ou transforme un claim dans les tokens (rôles, groupes, attribut agence_id, audience), configurée par client ou via un client scope.

Fédération d'identités (LDAP, Identity Brokering)Keycloak peut s'appuyer sur un annuaire existant (LDAP/AD) pour les employés, ou déléguer la connexion à un autre fournisseur (Google, autre IdP OIDC/SAML).

Keycloak peut s'appuyer sur un annuaire existant (LDAP/AD) pour les employés, ou déléguer la connexion à un autre fournisseur (Google, autre IdP OIDC/SAML).

5Cryptographie & secrets9

Hachage vs chiffrementHachage : transformation à sens unique (on ne retrouve pas l'original), pour vérifier un mot de passe ou l'intégrité. Chiffrement : réversible avec une clé, pour protéger une donnée qu'on devra relire.

Hachage : transformation à sens unique (on ne retrouve pas l'original), pour vérifier un mot de passe ou l'intégrité. Chiffrement : réversible avec une clé, pour protéger une donnée qu'on devra relire.

Mot de passe → hachage lent et salé (BCrypt, Argon2). Numéro de pièce d'identité stocké → chiffrement (AES-GCM) avec une clé gérée dans un KMS/Vault.

Sel (salt)Valeur aléatoire unique ajoutée à chaque mot de passe avant hachage : deux mots de passe identiques donnent des empreintes différentes et les tables précalculées (rainbow tables) deviennent inutiles.

Valeur aléatoire unique ajoutée à chaque mot de passe avant hachage : deux mots de passe identiques donnent des empreintes différentes et les tables précalculées (rainbow tables) deviennent inutiles.

Argon2Algorithme de hachage de mots de passe moderne (vainqueur de la Password Hashing Competition), coûteux en mémoire et en temps, ce qui freine les attaques par GPU ; recommandé par l'OWASP avec BCrypt.

Algorithme de hachage de mots de passe moderne (vainqueur de la Password Hashing Competition), coûteux en mémoire et en temps, ce qui freine les attaques par GPU ; recommandé par l'OWASP avec BCrypt.

java
PasswordEncoder enc = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
HTTPS / TLSTLS chiffre et authentifie la connexion entre client et serveur (HTTPS = HTTP sur TLS) : confidentialité, intégrité et preuve d'identité du serveur via son certificat.

TLS chiffre et authentifie la connexion entre client et serveur (HTTPS = HTTP sur TLS) : confidentialité, intégrité et preuve d'identité du serveur via son certificat.

🎯 En entretienEn production : TLS 1.2 minimum (1.3 recommandé), certificats renouvelés automatiquement (cert-manager, Let's Encrypt), redirection HTTP → HTTPS et en-tête HSTS.
mTLS (TLS mutuel)Les deux parties présentent un certificat : le serveur authentifie aussi le client. Utilisé entre microservices (souvent via un service mesh) et avec les partenaires bancaires.

Les deux parties présentent un certificat : le serveur authentifie aussi le client. Utilisé entre microservices (souvent via un service mesh) et avec les partenaires bancaires.

HSTS et en-têtes de sécuritéEn-têtes HTTP qui durcissent le navigateur : HSTS (toujours HTTPS), Content-Security-Policy, X-Content-Type-Options: nosniff, X-Frame-Options / frame-ancestors (anti-clickjacking), Referrer-Policy.

En-têtes HTTP qui durcissent le navigateur : HSTS (toujours HTTPS), Content-Security-Policy, X-Content-Type-Options: nosniff, X-Frame-Options / frame-ancestors (anti-clickjacking), Referrer-Policy.

java
http.headers(h -> h
    .httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000).includeSubDomains(true))
    .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'; frame-ancestors 'none'")));
Gestion des secrets (Vault)Les secrets (mots de passe de base, clés API, clés de signature) vivent dans un coffre (HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets chiffrés), jamais dans le code ni dans Git.

Les secrets (mots de passe de base, clés API, clés de signature) vivent dans un coffre (HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets chiffrés), jamais dans le code ni dans Git.

Vault apporte en plus des secrets dynamiques (identifiants de base créés à la demande, expirant automatiquement), l'audit des accès et la rotation.

yaml
spring:
  config:
    import: "vault://"          # Spring Cloud Vault : les secrets deviennent des propriétés
  cloud.vault:
    uri: https://vault.interne:8200
    authentication: KUBERNETES
Chiffrement au repos et en transitEn transit : les données circulant sur le réseau sont chiffrées (TLS). Au repos : les données stockées (disques, sauvegardes, colonnes sensibles) le sont aussi, avec des clés gérées hors de la base.

En transit : les données circulant sur le réseau sont chiffrées (TLS). Au repos : les données stockées (disques, sauvegardes, colonnes sensibles) le sont aussi, avec des clés gérées hors de la base.

Rotation des secretsRemplacer régulièrement (et immédiatement en cas de fuite) mots de passe, clés et certificats, de préférence automatiquement, en supportant temporairement l'ancienne et la nouvelle valeur.

Remplacer régulièrement (et immédiatement en cas de fuite) mots de passe, clés et certificats, de préférence automatiquement, en supportant temporairement l'ancienne et la nouvelle valeur.

6Vulnérabilités & défenses12

OWASP Top 10Classement de référence des risques de sécurité web les plus critiques, publié par l'OWASP : contrôle d'accès défaillant, défaillances cryptographiques, injection, conception non sécurisée, mauvaise configuration, composants vulnérables…

Classement de référence des risques de sécurité web les plus critiques, publié par l'OWASP : contrôle d'accès défaillant, défaillances cryptographiques, injection, conception non sécurisée, mauvaise configuration, composants vulnérables…

🎯 En entretienCiter en entretien les trois premiers et comment ton code s'en protège : contrôle d'accès par ressource, chiffrement et hachage, requêtes préparées + validation.
Injection SQLInsérer du code SQL dans une entrée utilisateur pour modifier la requête exécutée (lire, modifier ou supprimer des données). Prévention : requêtes préparées / paramètres liés, jamais de concaténation.

Insérer du code SQL dans une entrée utilisateur pour modifier la requête exécutée (lire, modifier ou supprimer des données). Prévention : requêtes préparées / paramètres liés, jamais de concaténation.

java
// ❌ ?nom=' OR '1'='1  → renvoie tous les clients
em.createQuery("SELECT c FROM Client c WHERE c.nom = '" + nom + "'");
// ✅
em.createQuery("SELECT c FROM Client c WHERE c.nom = :nom").setParameter("nom", nom);
⚠️ PiègeLes noms de colonnes de tri (ORDER BY + param) ne peuvent pas être paramétrés : les valider par liste blanche.
XSS (Cross-Site Scripting)Faire exécuter du JavaScript malveillant dans le navigateur d'une victime via une donnée affichée sans encodage. Stocké (en base), réfléchi (dans l'URL) ou DOM-based (manipulation côté client).

Faire exécuter du JavaScript malveillant dans le navigateur d'une victime via une donnée affichée sans encodage. Stocké (en base), réfléchi (dans l'URL) ou DOM-based (manipulation côté client).

Conséquences : vol de token en localStorage, actions à la place de l'utilisateur. Angular échappe automatiquement les interpolations ; le danger vient de innerHTML avec bypassSecurityTrustHtml, de bibliothèques tierces et de la manipulation directe du DOM.

html
<p>{{ commentaire }}</p>                          <!-- ✅ échappé : <script> s'affiche en texte -->
<div [innerHTML]="commentaire"></div>             <!-- ⚠️ assaini par Angular (scripts retirés) -->
typescript
this.sanitizer.bypassSecurityTrustHtml(saisie);  // ❌ désactive la protection : à proscrire sur une saisie
Encodage des sorties et assainissement (sanitization)Encoder une donnée selon le contexte où elle est affichée (HTML, attribut, URL, JavaScript) pour qu'elle reste du texte ; assainir = retirer les parties dangereuses d'un HTML qu'on doit afficher.

Encoder une donnée selon le contexte où elle est affichée (HTML, attribut, URL, JavaScript) pour qu'elle reste du texte ; assainir = retirer les parties dangereuses d'un HTML qu'on doit afficher.

Content Security Policy (CSP)En-tête HTTP qui indique au navigateur d'où il a le droit de charger scripts, styles, images et vers où se connecter ; un script injecté depuis une source non autorisée est bloqué. Deuxième ligne de défense contre le XSS.

En-tête HTTP qui indique au navigateur d'où il a le droit de charger scripts, styles, images et vers où se connecter ; un script injecté depuis une source non autorisée est bloqué. Deuxième ligne de défense contre le XSS.

text
Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self' https://api.banque.sn;
  img-src 'self' data:; frame-ancestors 'none'; object-src 'none'
SSRF (Server-Side Request Forgery)Amener le serveur à appeler une URL choisie par l'attaquant (ex. un champ « URL de webhook ») pour atteindre des services internes ou les métadonnées du cloud (169.254.169.254).

Amener le serveur à appeler une URL choisie par l'attaquant (ex. un champ « URL de webhook ») pour atteindre des services internes ou les métadonnées du cloud (169.254.169.254).

🎯 En entretienListe blanche des domaines autorisés, refus des IP privées, pas de suivi de redirection aveugle. Spring Boot 4.1 ajoute un InetAddressFilter pour aider.
Brute force, rate limiting et CAPTCHALimiter les tentatives de connexion ou d'OTP par compte et par IP, verrouiller temporairement, ajouter un CAPTCHA après plusieurs échecs, pour bloquer les attaques par force brute et le credential stuffing.

Limiter les tentatives de connexion ou d'OTP par compte et par IP, verrouiller temporairement, ajouter un CAPTCHA après plusieurs échecs, pour bloquer les attaques par force brute et le credential stuffing.

Validation des entrées (liste blanche)Toute donnée entrante est suspecte : vérifier type, format, longueur et plage côté serveur, de préférence par liste blanche (ce qui est autorisé) plutôt que liste noire.

Toute donnée entrante est suspecte : vérifier type, format, longueur et plage côté serveur, de préférence par liste blanche (ce qui est autorisé) plutôt que liste noire.

java
public record BeneficiaireRequest(
    @NotBlank @Size(max = 80) @Pattern(regexp = "[\\p{L} .'-]+") String nom,
    @NotBlank @Pattern(regexp = "SN\\d{2}[A-Z0-9]{20,30}") String iban) {}
Dépendances vulnérables (SCA)Analyse des bibliothèques tierces (Software Composition Analysis) pour détecter celles qui ont des vulnérabilités connues (CVE) ; intégrée au pipeline CI pour bloquer une dépendance critique.

Analyse des bibliothèques tierces (Software Composition Analysis) pour détecter celles qui ont des vulnérabilités connues (CVE) ; intégrée au pipeline CI pour bloquer une dépendance critique.

bash
./mvnw org.owasp:dependency-check-maven:check -DfailBuildOnCVSS=7
npm audit --audit-level=high
Détection de secrets dans GitOutils qui analysent les commits (pre-commit, CI) pour détecter mots de passe, clés API et tokens avant qu'ils n'atteignent le dépôt.

Outils qui analysent les commits (pre-commit, CI) pour détecter mots de passe, clés API et tokens avant qu'ils n'atteignent le dépôt.

⚠️ PiègeUn secret poussé dans Git doit être considéré comme compromis : le révoquer et le remplacer, le supprimer de l'historique ne suffit pas.
Journal d'audit de sécuritéTrace horodatée et non modifiable des actions sensibles (connexion, virement, changement de bénéficiaire, accès admin) : qui, quoi, quand, d'où, résultat. Indispensable en banque pour la conformité et les enquêtes.

Trace horodatée et non modifiable des actions sensibles (connexion, virement, changement de bénéficiaire, accès admin) : qui, quoi, quand, d'où, résultat. Indispensable en banque pour la conformité et les enquêtes.

java
auditService.tracer(Audit.of("VIREMENT_INITIE", jwt.getSubject(), ip, Map.of("montant", montant, "dest", ibanMasque)));
⚠️ PiègeNe jamais journaliser mots de passe, tokens, numéros de carte complets : masquer (SN01 **** **** 1234).
Défense en profondeur et secure by designConcevoir la sécurité dès le départ (modélisation des menaces, choix par défaut sûrs) et empiler plusieurs couches indépendantes (WAF, TLS, authentification, autorisation, validation, contraintes en base, journalisation) pour qu'une seule faille ne suffise pas.

Concevoir la sécurité dès le départ (modélisation des menaces, choix par défaut sûrs) et empiler plusieurs couches indépendantes (WAF, TLS, authentification, autorisation, validation, contraintes en base, journalisation) pour qu'une seule faille ne suffise pas.

📋 Synthèse express — Sécurité

🔐 OAuth2 — Flux PKCE (SPA/Mobile)

PKCE (Proof Key for Code Exchange) : Pour les apps publiques (SPA, mobile) sans client_secret. Le client génère un code_verifier (aléatoire) → code_challenge (SHA256) → envoyé avec la demande auth.
// Flux PKCE simplifié 1. Client génère: code_verifier (random 43-128 chars) 2. code_challenge = base64url(SHA256(code_verifier)) 3. GET /auth?response_type=code&code_challenge=...&code_challenge_method=S256 4. Authorization Code reçu → POST /token avec code_verifier 5. Auth Server vérifie: SHA256(code_verifier) == code_challenge

🎫 JWT — Structure & Bonnes pratiques

PartieContenu
Header{"alg":"RS256","typ":"JWT"}
PayloadClaims: iss, sub, exp, iat, roles, jti (unique ID)
SignatureHS256 (secret symétrique) ou RS256 (clé privée/publique)
⚠️ RS256 vs HS256 : RS256 recommandé en microservices. Chaque service vérifie avec la clé publique sans avoir besoin du secret. HS256 = secret partagé entre tous les services.

🔑 Keycloak — Concepts

🏰 Realms

Un realm = espace d'identité isolé. Multi-tenant = plusieurs realms. Realm master = admin uniquement.

📱 Clients

Public client (SPA/mobile, PKCE) vs Confidential client (server-side, client_secret). Chaque application = 1 client Keycloak.

🎭 Mappers

Ajoutent des claims au JWT (roles, groupes, attributs custom). Protocol mappers configurés par client ou realm.

🛡️ Checklist Sécurité — 12 points

#Point de contrôleNotes
1Authentification & autorisation côté serveurJamais uniquement côté client
2Validation de toutes les entrées@Valid, regex, longueur max
3Encodage des sortiesPrévenir XSS en HTML/JS
4Protection injection SQLRequêtes préparées TOUJOURS
5CORS strict et justifiéPas de wildcard * en prod
6CSRF selon contexteSi cookies de session
7Secrets hors du codeVault, variables d'env sécurisées
8HTTPS en productionTLS 1.2+ minimum
9Mots de passe hachés BCrypt/Argon2Jamais MD5, SHA1, SHA256
10Contrôle d'accès par rôle/ressourcePrincipe moindre privilège
11Limitation des tentativesRate limiting, CAPTCHA
12Dépendances à jourOWASP Dependency Check, Snyk

⚔️ Vulnérabilités courantes

💉 SQL Injection

Injection de code SQL via les inputs. Exemple : '; DROP TABLE users; --. Prévention : requêtes préparées (setParameter), jamais de concaténation directe d'input utilisateur dans une requête.

🕸️ XSS (Cross-Site Scripting)

Injection de scripts dans la page web. Stocké (en DB), Réfléchi (dans l'URL), DOM-based. Prévention : encoder les sorties HTML, Content Security Policy (CSP), Angular sanitize par défaut.

🔑 Gestion des secrets

Ne jamais committer de secrets en Git (API keys, passwords, JWT secrets). Utiliser : HashiCorp Vault, AWS Secrets Manager, variables d'env du CI/CD. Scanner avec git-secrets ou trufflehog.

⚡ Quiz Rapide — Sécurité
1. Pourquoi utiliser RS256 plutôt que HS256 pour les JWT en microservices ?
A
RS256 est plus rapide à calculer
B
Chaque service peut vérifier le token avec la clé publique sans connaître le secret de signature
C
HS256 n'est pas supporté par Keycloak
D
RS256 permet des tokens plus longs
✅ B. RS256 = asymétrique. L'auth server signe avec la clé privée. Chaque microservice vérifie avec la clé publique (exposée via JWKS endpoint). Aucun service n'a besoin du secret de signature → surface d'attaque réduite.
DevOps — Docker, Kubernetes, CI/CD
Conteneurisation, orchestration, pipelines, GitOps
📖 Cours complet — 59 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/59 maîtrisées

1Docker13

Conteneur vs machine virtuelleUn conteneur isole un processus et ses dépendances en partageant le noyau de l'OS hôte (léger, démarre en secondes) ; une machine virtuelle embarque un OS complet sur un hyperviseur (plus lourde, isolation plus forte).

Un conteneur isole un processus et ses dépendances en partageant le noyau de l'OS hôte (léger, démarre en secondes) ; une machine virtuelle embarque un OS complet sur un hyperviseur (plus lourde, isolation plus forte).

Le conteneur garantit que l'application tourne de la même façon sur le poste du développeur, en CI et en production : « ça marche chez moi » disparaît.

Image DockerModèle en lecture seule, composé de couches empilées, à partir duquel on lance des conteneurs ; identifiée par un nom, un tag et un digest (sha256).

Modèle en lecture seule, composé de couches empilées, à partir duquel on lance des conteneurs ; identifiée par un nom, un tag et un digest (sha256).

bash
docker build -t registry.banque.sn/virements:1.4.2 .
docker run -d -p 8080:8080 --name virements registry.banque.sn/virements:1.4.2
docker images
DockerfileFichier texte de recette pour construire une image : image de base (FROM), répertoire (WORKDIR), copie (COPY), commandes de build (RUN), port documenté (EXPOSE), commande de démarrage (ENTRYPOINT/CMD).

Fichier texte de recette pour construire une image : image de base (FROM), répertoire (WORKDIR), copie (COPY), commandes de build (RUN), port documenté (EXPOSE), commande de démarrage (ENTRYPOINT/CMD).

dockerfile
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]
Couches et cache de buildChaque instruction du Dockerfile crée une couche mise en cache ; si une couche change, toutes les suivantes sont reconstruites. On place donc ce qui change rarement (dépendances) avant ce qui change souvent (code).

Chaque instruction du Dockerfile crée une couche mise en cache ; si une couche change, toutes les suivantes sont reconstruites. On place donc ce qui change rarement (dépendances) avant ce qui change souvent (code).

dockerfile
COPY pom.xml .
RUN ./mvnw dependency:go-offline          # mis en cache tant que pom.xml ne change pas
COPY src ./src
RUN ./mvnw package -DskipTests            # seule cette étape se refait quand le code change
🎯 En entretienSpring Boot sait découper le JAR en couches (java -Djarmode=tools -jar app.jar extract --layers) : seules les classes de l'application changent d'une version à l'autre.
Multi-stage buildDockerfile en plusieurs étapes : une première image (JDK, Maven) compile, la dernière n'embarque que le résultat sur une image d'exécution légère. Image finale plus petite et plus sûre.

Dockerfile en plusieurs étapes : une première image (JDK, Maven) compile, la dernière n'embarque que le résultat sur une image d'exécution légère. Image finale plus petite et plus sûre.

dockerfile
# Étape 1 : build
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./mvnw -q package -DskipTests

# Étape 2 : exécution (sans JDK, sans Maven, sans sources)
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /app/target/*.jar app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Image de base : JDK vs JRE, alpine, distrolessPour exécuter, une image JRE suffit (pas de compilateur) ; alpine est très petite (musl libc), distroless ne contient même pas de shell : moins de taille, moins de vulnérabilités.

Pour exécuter, une image JRE suffit (pas de compilateur) ; alpine est très petite (musl libc), distroless ne contient même pas de shell : moins de taille, moins de vulnérabilités.

⚠️ PiègeDistroless complique le débogage (pas de shell) : utiliser kubectl debug avec un conteneur éphémère.
Utilisateur non-rootExécuter le processus avec un utilisateur sans privilèges (instruction USER) : si l'application est compromise, l'attaquant n'est pas root dans le conteneur.

Exécuter le processus avec un utilisateur sans privilèges (instruction USER) : si l'application est compromise, l'attaquant n'est pas root dans le conteneur.

yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
.dockerignoreListe des fichiers exclus du contexte de build (target/, node_modules/, .git/, .env) : builds plus rapides et pas de secret embarqué par erreur dans l'image.

Liste des fichiers exclus du contexte de build (target/, node_modules/, .git/, .env) : builds plus rapides et pas de secret embarqué par erreur dans l'image.

text
.git
target/
node_modules/
*.env
Registry d'imagesDépôt où l'on publie et récupère les images (Docker Hub, GitLab Registry, Harbor, AWS ECR) ; le cluster Kubernetes y tire les images à déployer.

Dépôt où l'on publie et récupère les images (Docker Hub, GitLab Registry, Harbor, AWS ECR) ; le cluster Kubernetes y tire les images à déployer.

bash
docker push registry.banque.sn/virements:1.4.2
Tag d'image et digestLe tag est une étiquette lisible (1.4.2, sha du commit) ; le digest (sha256:…) identifie un contenu exact et immuable.

Le tag est une étiquette lisible (1.4.2, sha du commit) ; le digest (sha256:…) identifie un contenu exact et immuable.

⚠️ PiègeNe jamais déployer :latest en production : on ne sait plus quelle version tourne et un rollback devient incertain. Taguer avec la version ou le hash du commit.
Docker ComposeOutil qui décrit et lance plusieurs conteneurs ensemble (application, PostgreSQL, Kafka, Keycloak) dans un fichier YAML : idéal pour l'environnement de développement local.

Outil qui décrit et lance plusieurs conteneurs ensemble (application, PostgreSQL, Kafka, Keycloak) dans un fichier YAML : idéal pour l'environnement de développement local.

yaml
services:
  postgres:
    image: postgres:17-alpine
    environment: { POSTGRES_DB: banque, POSTGRES_PASSWORD: dev }
    ports: ["5432:5432"]
  api:
    build: .
    depends_on: [postgres]
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/banque
    ports: ["8080:8080"]
🎯 En entretienSpring Boot sait démarrer automatiquement un compose.yaml en dev avec spring-boot-docker-compose.
VolumeStockage persistant monté dans un conteneur ; sans volume, les données écrites disparaissent quand le conteneur est supprimé.

Stockage persistant monté dans un conteneur ; sans volume, les données écrites disparaissent quand le conteneur est supprimé.

bash
docker run -v pgdata:/var/lib/postgresql/data postgres:17-alpine
Buildpacks et JibConstruire une image optimisée sans écrire de Dockerfile : Cloud Native Buildpacks (spring-boot:build-image) ou Jib (plugin Maven/Gradle, sans démon Docker).

Construire une image optimisée sans écrire de Dockerfile : Cloud Native Buildpacks (spring-boot:build-image) ou Jib (plugin Maven/Gradle, sans démon Docker).

bash
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=registry.banque.sn/virements:1.4.2

2Kubernetes18

Kubernetes (k8s)Orchestrateur de conteneurs : il déploie, redémarre, met à l'échelle et répartit les conteneurs sur un cluster de machines, en convergeant en permanence vers l'état déclaré dans des fichiers YAML.

Orchestrateur de conteneurs : il déploie, redémarre, met à l'échelle et répartit les conteneurs sur un cluster de machines, en convergeant en permanence vers l'état déclaré dans des fichiers YAML.

On décrit l'état voulu (« 3 répliques de virements:1.4.2 »), Kubernetes s'occupe d'y arriver et d'y rester (auto-réparation).

Cluster, nœuds et control planeUn cluster = un control plane (API server, scheduler, etcd qui stocke l'état) + des nœuds de travail où le kubelet lance les pods.

Un cluster = un control plane (API server, scheduler, etcd qui stocke l'état) + des nœuds de travail où le kubelet lance les pods.

PodPlus petite unité déployable : un ou plusieurs conteneurs qui partagent réseau (même IP) et volumes. Un pod est éphémère : il peut être recréé ailleurs avec une autre IP.

Plus petite unité déployable : un ou plusieurs conteneurs qui partagent réseau (même IP) et volumes. Un pod est éphémère : il peut être recréé ailleurs avec une autre IP.

bash
kubectl get pods -n banque
kubectl logs -f virements-7d9c8b6f4-x2kqp
kubectl describe pod virements-7d9c8b6f4-x2kqp     # événements : OOMKilled, ImagePullBackOff…
DeploymentObjet qui décrit l'application à exécuter (image, nombre de répliques, probes, ressources) et gère les mises à jour progressives et les rollbacks via des ReplicaSets.

Objet qui décrit l'application à exécuter (image, nombre de répliques, probes, ressources) et gère les mises à jour progressives et les rollbacks via des ReplicaSets.

yaml
apiVersion: apps/v1
kind: Deployment
metadata: { name: virements, namespace: banque }
spec:
  replicas: 3
  selector: { matchLabels: { app: virements } }
  template:
    metadata: { labels: { app: virements } }
    spec:
      containers:
        - name: app
          image: registry.banque.sn/virements:1.4.2
          ports: [{ containerPort: 8080 }]
          envFrom: [{ configMapRef: { name: virements-config } }, { secretRef: { name: virements-secrets } }]
ReplicaSetGarantit qu'un nombre donné de pods identiques tourne en permanence ; géré automatiquement par le Deployment (un ReplicaSet par version).

Garantit qu'un nombre donné de pods identiques tourne en permanence ; géré automatiquement par le Deployment (un ReplicaSet par version).

Service KubernetesAdresse réseau stable (nom DNS + IP virtuelle) devant un ensemble de pods choisis par labels ; il répartit le trafic entre eux. Types : ClusterIP (interne), NodePort, LoadBalancer (exposé).

Adresse réseau stable (nom DNS + IP virtuelle) devant un ensemble de pods choisis par labels ; il répartit le trafic entre eux. Types : ClusterIP (interne), NodePort, LoadBalancer (exposé).

yaml
apiVersion: v1
kind: Service
metadata: { name: virements-service }
spec:
  selector: { app: virements }
  ports: [{ port: 80, targetPort: 8080 }]
# Les autres services appellent : http://virements-service.banque.svc.cluster.local
IngressRègles de routage HTTP(S) depuis l'extérieur du cluster vers les Services (par domaine et chemin), avec terminaison TLS ; appliquées par un Ingress controller (NGINX, Traefik).

Règles de routage HTTP(S) depuis l'extérieur du cluster vers les Services (par domaine et chemin), avec terminaison TLS ; appliquées par un Ingress controller (NGINX, Traefik).

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: { name: api }
spec:
  tls: [{ hosts: [api.banque.sn], secretName: api-tls }]
  rules:
    - host: api.banque.sn
      http:
        paths:
          - path: /api/v1/virements
            pathType: Prefix
            backend: { service: { name: virements-service, port: { number: 80 } } }
🎯 En entretienLa Gateway API de Kubernetes est le successeur plus riche d'Ingress.
ConfigMapObjet qui stocke de la configuration non sensible (clé/valeur ou fichiers) injectée dans les pods en variables d'environnement ou fichiers montés.

Objet qui stocke de la configuration non sensible (clé/valeur ou fichiers) injectée dans les pods en variables d'environnement ou fichiers montés.

yaml
apiVersion: v1
kind: ConfigMap
metadata: { name: virements-config }
data:
  SPRING_PROFILES_ACTIVE: prod
  BANQUE_VIREMENT_PLAFOND_JOURNALIER: "5000000"
Secret KubernetesObjet destiné aux données sensibles (mots de passe, clés), injecté comme une ConfigMap ; ses valeurs sont seulement encodées en Base64, il faut activer le chiffrement d'etcd et restreindre l'accès (RBAC).

Objet destiné aux données sensibles (mots de passe, clés), injecté comme une ConfigMap ; ses valeurs sont seulement encodées en Base64, il faut activer le chiffrement d'etcd et restreindre l'accès (RBAC).

⚠️ PiègeUn Secret Kubernetes n'est pas chiffré par défaut : ne pas le committer en clair ; utiliser Sealed Secrets, External Secrets Operator ou Vault.
NamespaceEspace logique qui cloisonne les ressources d'un cluster (par équipe ou environnement) : noms, quotas et droits RBAC propres.

Espace logique qui cloisonne les ressources d'un cluster (par équipe ou environnement) : noms, quotas et droits RBAC propres.

Requests et limits (ressources)requests : ressources réservées, utilisées par le scheduler pour placer le pod. limits : plafond ; au-delà de la mémoire limite, le conteneur est tué (OOMKilled), au-delà du CPU il est ralenti.

requests : ressources réservées, utilisées par le scheduler pour placer le pod. limits : plafond ; au-delà de la mémoire limite, le conteneur est tué (OOMKilled), au-delà du CPU il est ralenti.

yaml
resources:
  requests: { cpu: 250m, memory: 512Mi }
  limits:   { memory: 1Gi }
🎯 En entretienPour une JVM : -XX:MaxRAMPercentage=75 afin que le heap tienne sous la limite mémoire du conteneur.
HPA (Horizontal Pod Autoscaler)Ajuste automatiquement le nombre de répliques selon une métrique (CPU, mémoire, ou métrique métier comme le lag Kafka).

Ajuste automatiquement le nombre de répliques selon une métrique (CPU, mémoire, ou métrique métier comme le lag Kafka).

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: virements }
spec:
  scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: virements }
  minReplicas: 2
  maxReplicas: 10
  metrics: [{ type: Resource, resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } } }]
livenessProbeSonde qui vérifie que l'application est vivante ; en cas d'échecs répétés, le kubelet tue et redémarre le conteneur. Sert à sortir d'un état bloqué (deadlock).

Sonde qui vérifie que l'application est vivante ; en cas d'échecs répétés, le kubelet tue et redémarre le conteneur. Sert à sortir d'un état bloqué (deadlock).

yaml
livenessProbe:
  httpGet: { path: /actuator/health/liveness, port: 8080 }
  periodSeconds: 10
  failureThreshold: 3
⚠️ PiègeNe pas inclure la base de données dans la liveness : si PostgreSQL tombe, tous les pods redémarreraient en boucle sans rien régler.
readinessProbeSonde qui indique si le pod peut recevoir du trafic ; en cas d'échec, il est retiré du Service (plus de requêtes) mais pas redémarré. Utile au démarrage, pendant un préchauffage ou une surcharge.

Sonde qui indique si le pod peut recevoir du trafic ; en cas d'échec, il est retiré du Service (plus de requêtes) mais pas redémarré. Utile au démarrage, pendant un préchauffage ou une surcharge.

yaml
readinessProbe:
  httpGet: { path: /actuator/health/readiness, port: 8080 }
  periodSeconds: 5
startupProbeSonde utilisée pendant le démarrage seulement : tant qu'elle n'a pas réussi, liveness et readiness ne sont pas évaluées. Évite de tuer une application lente à démarrer.

Sonde utilisée pendant le démarrage seulement : tant qu'elle n'a pas réussi, liveness et readiness ne sont pas évaluées. Évite de tuer une application lente à démarrer.

yaml
startupProbe:
  httpGet: { path: /actuator/health/liveness, port: 8080 }
  failureThreshold: 30
  periodSeconds: 5          # jusqu'à 150 s pour démarrer
Arrêt gracieux (graceful shutdown)À l'arrêt d'un pod, Kubernetes envoie SIGTERM ; l'application cesse d'accepter des requêtes, termine celles en cours, puis s'arrête avant le SIGKILL (30 s par défaut).

À l'arrêt d'un pod, Kubernetes envoie SIGTERM ; l'application cesse d'accepter des requêtes, termine celles en cours, puis s'arrête avant le SIGKILL (30 s par défaut).

properties
server.shutdown=graceful                       # défaut depuis Spring Boot 3.4
spring.lifecycle.timeout-per-shutdown-phase=20s
HelmGestionnaire de paquets pour Kubernetes : un chart regroupe des templates YAML paramétrés par un fichier values.yaml, versionnés et installables en une commande.

Gestionnaire de paquets pour Kubernetes : un chart regroupe des templates YAML paramétrés par un fichier values.yaml, versionnés et installables en une commande.

bash
helm upgrade --install virements ./charts/virements -n banque \
  -f values-prod.yaml --set image.tag=1.4.2
helm rollback virements 12
kubectl (commandes essentielles)Client en ligne de commande de Kubernetes pour inspecter et piloter le cluster.

Client en ligne de commande de Kubernetes pour inspecter et piloter le cluster.

bash
kubectl get deploy,pods,svc -n banque
kubectl rollout status deploy/virements
kubectl rollout undo deploy/virements             # rollback
kubectl top pods                                   # consommation CPU/mémoire
kubectl exec -it virements-xxx -- sh
kubectl port-forward svc/virements-service 8080:80

3Stratégies de déploiement8

Rolling UpdateRemplacement progressif des pods de l'ancienne version par la nouvelle, quelques-uns à la fois ; stratégie par défaut de Kubernetes. Pendant la transition, les deux versions coexistent.

Remplacement progressif des pods de l'ancienne version par la nouvelle, quelques-uns à la fois ; stratégie par défaut de Kubernetes. Pendant la transition, les deux versions coexistent.

yaml
strategy:
  type: RollingUpdate
  rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }   # jamais moins de capacité qu'avant
⚠️ PiègeLes deux versions tournent en même temps : l'API et le schéma de base doivent rester compatibles (voir Expand / Contract).
Blue/GreenDeux environnements complets : Blue (actuel) et Green (nouvelle version). On teste Green, puis on bascule tout le trafic d'un coup ; rollback instantané en rebasculant. Coûte le double de ressources.

Deux environnements complets : Blue (actuel) et Green (nouvelle version). On teste Green, puis on bascule tout le trafic d'un coup ; rollback instantané en rebasculant. Coûte le double de ressources.

CanaryEnvoyer d'abord une petite partie du trafic (5 %) vers la nouvelle version, surveiller erreurs et latence, puis augmenter progressivement (25 %, 50 %, 100 %) ou annuler.

Envoyer d'abord une petite partie du trafic (5 %) vers la nouvelle version, surveiller erreurs et latence, puis augmenter progressivement (25 %, 50 %, 100 %) ou annuler.

Nécessite un routage fin du trafic (Istio, NGINX, Argo Rollouts) et de bonnes métriques pour décider automatiquement.

Feature flagInterrupteur de configuration qui active ou désactive une fonctionnalité sans redéployer ; découple le déploiement du code de la mise à disposition aux utilisateurs.

Interrupteur de configuration qui active ou désactive une fonctionnalité sans redéployer ; découple le déploiement du code de la mise à disposition aux utilisateurs.

java
if (features.estActive("virement-instantane", client)) { return virementInstantane(d); }
return virementClassique(d);
RollbackRevenir rapidement à la version précédente quand un déploiement pose problème ; doit être préparé (images taguées, migrations compatibles) et testé.

Revenir rapidement à la version précédente quand un déploiement pose problème ; doit être préparé (images taguées, migrations compatibles) et testé.

Expand / Contract (migrations rétrocompatibles)Faire évoluer un schéma en plusieurs déploiements pour que l'ancienne et la nouvelle version fonctionnent ensemble : ajouter (expand), migrer, puis supprimer l'ancien (contract).

Faire évoluer un schéma en plusieurs déploiements pour que l'ancienne et la nouvelle version fonctionnent ensemble : ajouter (expand), migrer, puis supprimer l'ancien (contract).

text
v1 : colonne nom_complet
v2 : ajoute prenom + nom (nullable), écrit dans les trois colonnes      ← expand
     job de migration des données
v3 : ne lit plus nom_complet
v4 : DROP COLUMN nom_complet                                            ← contract
GitOpsL'état voulu de l'infrastructure et des déploiements est décrit dans Git ; un agent dans le cluster synchronise en continu le cluster sur ce dépôt. Déployer = merger, rollback = revert.

L'état voulu de l'infrastructure et des déploiements est décrit dans Git ; un agent dans le cluster synchronise en continu le cluster sur ce dépôt. Déployer = merger, rollback = revert.

Avantages : historique et audit de chaque changement, revue de code de l'infrastructure, pas d'accès direct à kubectl en production.

ArgoCDOutil GitOps pour Kubernetes : il compare en permanence les manifestes du dépôt Git à l'état du cluster, signale les écarts (drift) et les synchronise.

Outil GitOps pour Kubernetes : il compare en permanence les manifestes du dépôt Git à l'état du cluster, signale les écarts (drift) et les synchronise.

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: virements-prod }
spec:
  source: { repoURL: https://gitlab.banque.sn/infra/deploiements.git, path: virements/prod, targetRevision: main }
  destination: { server: https://kubernetes.default.svc, namespace: banque }
  syncPolicy: { automated: { prune: true, selfHeal: true } }

4CI/CD10

Intégration continue (CI)Chaque push déclenche automatiquement compilation, tests et analyses sur un serveur : les erreurs sont détectées en minutes, et la branche principale reste toujours dans un état livrable.

Chaque push déclenche automatiquement compilation, tests et analyses sur un serveur : les erreurs sont détectées en minutes, et la branche principale reste toujours dans un état livrable.

Livraison continue vs déploiement continuLivraison continue : chaque version validée est prête à être déployée, la mise en production reste une décision humaine (un clic). Déploiement continu : tout ce qui passe le pipeline part automatiquement en production.

Livraison continue : chaque version validée est prête à être déployée, la mise en production reste une décision humaine (un clic). Déploiement continu : tout ce qui passe le pipeline part automatiquement en production.

Pipeline et stagesSuite d'étapes automatisées (build → tests → qualité/sécurité → image → déploiement) ; si une étape échoue, la suite s'arrête.

Suite d'étapes automatisées (build → tests → qualité/sécurité → image → déploiement) ; si une étape échoue, la suite s'arrête.

yaml
# .gitlab-ci.yml
stages: [build, test, quality, package, deploy]

test:
  stage: test
  image: eclipse-temurin:21-jdk
  script: ["./mvnw -B verify"]
  artifacts: { reports: { junit: target/surefire-reports/*.xml } }

image:
  stage: package
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - trivy image --exit-code 1 --severity CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

deploy-prod:
  stage: deploy
  when: manual                       # livraison continue : validation humaine
  environment: production
  script: ["helm upgrade --install virements ./chart --set image.tag=$CI_COMMIT_SHORT_SHA"]
ArtefactProduit versionné d'un build (JAR, image Docker, bundle Angular) qu'on promeut d'environnement en environnement sans le reconstruire : ce qui a été testé est exactement ce qui est déployé.

Produit versionné d'un build (JAR, image Docker, bundle Angular) qu'on promeut d'environnement en environnement sans le reconstruire : ce qui a été testé est exactement ce qui est déployé.

SonarQube et quality gateSonarQube analyse le code sans l'exécuter (bugs, vulnérabilités, code smells, duplication, couverture) ; la quality gate est le seuil qui fait échouer le pipeline si le nouveau code ne respecte pas les critères.

SonarQube analyse le code sans l'exécuter (bugs, vulnérabilités, code smells, duplication, couverture) ; la quality gate est le seuil qui fait échouer le pipeline si le nouveau code ne respecte pas les critères.

bash
./mvnw verify sonar:sonar -Dsonar.host.url=$SONAR_URL -Dsonar.token=$SONAR_TOKEN
🎯 En entretienAppliquer la règle « clean as you code » : exigences sur le nouveau code, pour améliorer sans bloquer sur la dette historique.
Scan d'image (Trivy)Outil qui détecte les vulnérabilités connues (CVE) des paquets de l'OS et des bibliothèques d'une image, ainsi que les mauvaises configurations ; bloque le pipeline au-delà d'un seuil de criticité.

Outil qui détecte les vulnérabilités connues (CVE) des paquets de l'OS et des bibliothèques d'une image, ainsi que les mauvaises configurations ; bloque le pipeline au-delà d'un seuil de criticité.

GitLab CI et GitHub ActionsMoteurs de CI/CD intégrés aux forges Git : pipelines décrits en YAML dans le dépôt, exécutés par des runners.

Moteurs de CI/CD intégrés aux forges Git : pipelines décrits en YAML dans le dépôt, exécutés par des runners.

yaml
# .github/workflows/ci.yml
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 21, cache: maven }
      - run: ./mvnw -B verify
Variables CI protégées et masquéesLes secrets du pipeline (tokens de registry, Sonar, kubeconfig) sont stockés comme variables chiffrées, masquées dans les logs et réservées aux branches protégées.

Les secrets du pipeline (tokens de registry, Sonar, kubeconfig) sont stockés comme variables chiffrées, masquées dans les logs et réservées aux branches protégées.

⚠️ PiègeUn echo $SECRET ou un set -x dans un script peut quand même exposer un secret : vérifier les logs et limiter les droits des runners.
Versionnage sémantique (SemVer)Format MAJEUR.MINEUR.CORRECTIF : MAJEUR = changement incompatible, MINEUR = ajout rétrocompatible, CORRECTIF = correction de bug. Ex. 1.4.2 → 1.5.0 pour une nouvelle fonctionnalité.

Format MAJEUR.MINEUR.CORRECTIF : MAJEUR = changement incompatible, MINEUR = ajout rétrocompatible, CORRECTIF = correction de bug. Ex. 1.4.2 → 1.5.0 pour une nouvelle fonctionnalité.

Infrastructure as Code (Terraform)Décrire l'infrastructure (réseaux, bases managées, clusters) dans des fichiers versionnés et l'appliquer par outil (Terraform, Pulumi), plutôt que de cliquer dans une console.

Décrire l'infrastructure (réseaux, bases managées, clusters) dans des fichiers versionnés et l'appliquer par outil (Terraform, Pulumi), plutôt que de cliquer dans une console.

hcl
resource "aws_db_instance" "banque" {
  engine            = "postgres"
  engine_version    = "17"
  instance_class    = "db.m6g.large"
  allocated_storage = 100
  storage_encrypted = true
}

5Git10

git mergeIntègre une branche dans une autre ; si les historiques ont divergé, crée un commit de fusion à deux parents. L'historique réel est conservé (non linéaire).

Intègre une branche dans une autre ; si les historiques ont divergé, crée un commit de fusion à deux parents. L'historique réel est conservé (non linéaire).

bash
git switch main
git merge --no-ff feature/virement-instantane   # force un commit de merge explicite
Fast-forwardQuand la branche cible n'a pas avancé depuis la création de la branche, Git déplace simplement son pointeur : pas de commit de fusion, historique linéaire.

Quand la branche cible n'a pas avancé depuis la création de la branche, Git déplace simplement son pointeur : pas de commit de fusion, historique linéaire.

git rebaseRejoue les commits d'une branche au-dessus d'une autre base : historique linéaire et propre, mais les commits sont réécrits (nouveaux identifiants).

Rejoue les commits d'une branche au-dessus d'une autre base : historique linéaire et propre, mais les commits sont réécrits (nouveaux identifiants).

bash
git switch feature/kyc
git fetch origin
git rebase origin/main          # résoudre les conflits commit par commit, puis git rebase --continue
git push --force-with-lease     # la branche a été réécrite
⚠️ PiègeRègle d'or : ne jamais rebaser une branche partagée sur laquelle d'autres ont déjà basé leur travail.
push --force vs --force-with-lease--force écrase la branche distante quoi qu'il arrive (peut effacer le travail d'un collègue) ; --force-with-lease refuse si la branche distante a changé depuis ta dernière récupération.

--force écrase la branche distante quoi qu'il arrive (peut effacer le travail d'un collègue) ; --force-with-lease refuse si la branche distante a changé depuis ta dernière récupération.

SquashRegrouper plusieurs commits en un seul (rebase interactif ou « squash merge » d'une pull request) pour garder un historique lisible : un commit = une fonctionnalité.

Regrouper plusieurs commits en un seul (rebase interactif ou « squash merge » d'une pull request) pour garder un historique lisible : un commit = une fonctionnalité.

Cherry-pickAppliquer un commit précis d'une branche sur une autre, typiquement pour reporter un correctif urgent sur une branche de release.

Appliquer un commit précis d'une branche sur une autre, typiquement pour reporter un correctif urgent sur une branche de release.

bash
git switch release/1.4
git cherry-pick 3f2a9c1
git revert vs git resetrevert crée un nouveau commit qui annule un commit existant (sûr sur une branche partagée) ; reset déplace la branche en arrière et réécrit l'historique (à réserver au local).

revert crée un nouveau commit qui annule un commit existant (sûr sur une branche partagée) ; reset déplace la branche en arrière et réécrit l'historique (à réserver au local).

bash
git revert 3f2a9c1            # ✅ sur main en production
git reset --hard HEAD~1       # ⚠️ local uniquement : supprime le dernier commit
Pull request / merge request et code reviewDemande d'intégration d'une branche, accompagnée d'une revue par un pair et des vérifications automatiques (CI, Sonar) avant fusion ; principal outil de qualité et de partage de connaissances.

Demande d'intégration d'une branche, accompagnée d'une revue par un pair et des vérifications automatiques (CI, Sonar) avant fusion ; principal outil de qualité et de partage de connaissances.

🎯 En entretienBonne pratique de tech lead : petites PR (< 400 lignes), description du « pourquoi », commentaires bienveillants qui expliquent, au moins une approbation obligatoire.
Trunk-based development vs Git FlowTrunk-based : branches très courtes fusionnées au moins quotidiennement dans main, avec feature flags ; adapté au déploiement continu. Git Flow : branches develop, release, hotfix ; plus lourd, adapté aux livraisons planifiées.

Trunk-based : branches très courtes fusionnées au moins quotidiennement dans main, avec feature flags ; adapté au déploiement continu. Git Flow : branches develop, release, hotfix ; plus lourd, adapté aux livraisons planifiées.

Conventional CommitsConvention de messages de commit (feat:, fix:, refactor:, chore:…) qui rend l'historique lisible et permet de générer automatiquement changelog et numéro de version.

Convention de messages de commit (feat:, fix:, refactor:, chore:…) qui rend l'historique lisible et permet de générer automatiquement changelog et numéro de version.

text
feat(virement): ajoute le plafond journalier configurable
fix(kyc): corrige la validation du numéro CNI
refactor!: renomme l'API /clients en /customers   ← "!" = changement incompatible (MAJEUR)

📋 Synthèse express — DevOps

🐳 Docker — Multi-stage Build

# Stage 1: Build FROM eclipse-temurin:21-jdk-alpine AS builder WORKDIR /app COPY . . RUN ./mvnw package -DskipTests # Stage 2: Runtime (image finale légère) FROM eclipse-temurin:21-jre-alpine WORKDIR /app COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

☸️ Kubernetes — Probes

ProbeRôleÉchec
livenessProbeL'application est-elle vivante ?Restart du pod
readinessProbeL'application peut-elle recevoir du trafic ?Retiré du Service (pas de restart)
startupProbeL'application a-t-elle démarré ?Remplace liveness pendant le démarrage

🚀 Stratégies de déploiement

🔵🟢 Blue/Green

Deux environnements identiques. Basculement instantané. Rollback immédiat. Coûteux en ressources (2x).

🐦 Canary

Déploiement progressif : 5% → 25% → 100%. Validation sur petit pourcentage. Risque limité. Needs traffic routing (Istio/NGINX).

🔄 Rolling Update

Remplacement progressif pod par pod. Défaut Kubernetes. Risque de deux versions simultanées. Doit être backward-compatible.

🤖 GitOps (ArgoCD)

Git = source de vérité. ArgoCD synchronise le cluster avec l'état Git. Tout changement = commit. Rollback = revert Git.

🌿 Git — Merge vs Rebase

Critèregit mergegit rebase
HistoriqueNon-linéaire (commit de merge)Linéaire (propre)
ConflitsRésolu en 1 foisRésolu à chaque commit
UsageIntégration de branches longuesNettoyer avant PR/merge
RisqueHistorique surchargéRéécrire l'historique partagé ❌
Commandegit merge featuregit rebase main
⚠️ Ne jamais rebaser une branche partagée/publiée (git push --force dangereux)

🔄 Pipeline CI/CD — Étapes clés

1. Build

Compilation et génération des artefacts (mvn package, npm run build).

2. Tests

Tests unitaires → intégration → E2E. Couverture Jacoco/Istanbul. Blocage si seuil non atteint.

3. Analyse statique

SonarQube, ESLint, Checkstyle. Détection code smells et vulnérabilités.

4. Docker + Deploy

Build image, scan (Trivy), push registry, déploiement Blue/Green ou Rolling.

Secrets : Jamais en clair dans le code. Utiliser GitLab CI Variables / GitHub Secrets / HashiCorp Vault. Rotation régulière.
⚡ Quiz Rapide — DevOps
1. Différence entre livenessProbe et readinessProbe en Kubernetes ?
A
readinessProbe redémarre le pod, livenessProbe le retire du Service
B
livenessProbe redémarre le pod si échoue, readinessProbe le retire du Service sans restart
C
Les deux ont le même comportement
D
livenessProbe ne fonctionne qu'avec HTTP
✅ B. livenessProbe : si échoue → kubelet kill et redémarre le pod (résout deadlocks/crashes). readinessProbe : si échoue → pod retiré des endpoints du Service (plus de trafic) mais pas restart. Util pendant init ou surcharge temporaire.
Tests & TDD/BDD
Pyramide des tests, JUnit 5, Mockito, Testcontainers, BDD/Cucumber
📖 Cours complet — 44 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/44 maîtrisées

1Stratégie de test13

Test unitaireTeste une seule unité de code (une classe, une méthode) isolée de ses dépendances (remplacées par des doublures) ; très rapide (millisecondes), sans Spring, sans base, sans réseau.

Teste une seule unité de code (une classe, une méthode) isolée de ses dépendances (remplacées par des doublures) ; très rapide (millisecondes), sans Spring, sans base, sans réseau.

java
class CalculFraisTest {
    private final CalculFrais calcul = new CalculFrais();
    @Test
    void virement_interne_sans_frais() {
        assertThat(calcul.frais(TypeVirement.INTERNE, new BigDecimal("100000"))).isEqualByComparingTo("0");
    }
}
Test d'intégrationVérifie que plusieurs composants fonctionnent ensemble avec de vraies infrastructures ou des équivalents fidèles (contexte Spring, base PostgreSQL en conteneur, sérialisation JSON, transactions).

Vérifie que plusieurs composants fonctionnent ensemble avec de vraies infrastructures ou des équivalents fidèles (contexte Spring, base PostgreSQL en conteneur, sérialisation JSON, transactions).

Il détecte ce que les tests unitaires ne voient pas : mapping JPA erroné, requête SQL invalide, configuration de sécurité, contrat JSON.

Test end-to-end (E2E)Simule un utilisateur réel à travers toute l'application (navigateur → Angular → API → base) ; le plus réaliste, mais lent, coûteux et plus fragile. À réserver aux parcours critiques (connexion, virement).

Simule un utilisateur réel à travers toute l'application (navigateur → Angular → API → base) ; le plus réaliste, mais lent, coûteux et plus fragile. À réserver aux parcours critiques (connexion, virement).

Pyramide des testsRépartition recommandée : beaucoup de tests unitaires (base, ~70 %), moins de tests d'intégration (~20 %), peu de tests E2E (sommet, ~10 %). Plus on monte, plus c'est lent et coûteux.

Répartition recommandée : beaucoup de tests unitaires (base, ~70 %), moins de tests d'intégration (~20 %), peu de tests E2E (sommet, ~10 %). Plus on monte, plus c'est lent et coûteux.

⚠️ PiègeL'anti-pattern « cornet de glace » (surtout des tests E2E manuels ou automatisés) donne des suites lentes et instables.
Arrange-Act-Assert / Given-When-ThenStructure d'un test en trois temps : préparer les données (Arrange/Given), exécuter l'action (Act/When), vérifier le résultat (Assert/Then). Un test = un comportement.

Structure d'un test en trois temps : préparer les données (Arrange/Given), exécuter l'action (Act/When), vérifier le résultat (Assert/Then). Un test = un comportement.

java
@Test
void debit_refuse_si_solde_insuffisant() {
    // Given
    Compte compte = new Compte(new BigDecimal("1000"));
    // When
    Throwable erreur = catchThrowable(() -> compte.debiter(new BigDecimal("5000")));
    // Then
    assertThat(erreur).isInstanceOf(SoldeInsuffisantException.class);
    assertThat(compte.solde()).isEqualByComparingTo("1000");
}
TDD (Test-Driven Development)Développement piloté par les tests : écrire d'abord un test qui échoue (Red), puis le minimum de code pour le faire passer (Green), puis améliorer le code sans casser les tests (Refactor). Cycles de quelques minutes.

Développement piloté par les tests : écrire d'abord un test qui échoue (Red), puis le minimum de code pour le faire passer (Green), puis améliorer le code sans casser les tests (Refactor). Cycles de quelques minutes.

Bénéfices : conception guidée par l'usage, couverture naturelle, filet de sécurité pour refactorer. L'étape Red prouve que le test détecte bien l'absence de la fonctionnalité.

java
// 1. RED : le test échoue (la méthode n'existe pas ou renvoie 0)
@Test void frais_mobile_money_1_pourcent_plafonne_a_5000() {
    assertThat(calcul.fraisMobileMoney(new BigDecimal("800000"))).isEqualByComparingTo("5000");
}
// 2. GREEN : implémentation minimale
BigDecimal fraisMobileMoney(BigDecimal m) { return m.multiply(new BigDecimal("0.01")).min(new BigDecimal("5000")); }
// 3. REFACTOR : extraire les constantes TAUX et PLAFOND, relancer les tests
BDD (Behavior-Driven Development)Approche où les comportements attendus sont décrits en langage naturel structuré (Given/When/Then), rédigés avec le métier, puis automatisés comme tests d'acceptation.

Approche où les comportements attendus sont décrits en langage naturel structuré (Given/When/Then), rédigés avec le métier, puis automatisés comme tests d'acceptation.

Gherkin et CucumberGherkin est la syntaxe des scénarios BDD (Feature, Scenario, Given, When, Then) ; Cucumber exécute ces scénarios en les reliant à des méthodes Java (step definitions).

Gherkin est la syntaxe des scénarios BDD (Feature, Scenario, Given, When, Then) ; Cucumber exécute ces scénarios en les reliant à des méthodes Java (step definitions).

gherkin
Fonctionnalité: Virement entre comptes
  Scénario: Virement refusé pour solde insuffisant
    Étant donné un compte "SN01" avec un solde de 10000 XOF
    Quand le client vire 25000 XOF vers "SN02"
    Alors le virement est refusé avec le motif "SOLDE_INSUFFISANT"
java
@Quand("le client vire {int} XOF vers {string}")
public void virer(int montant, String dest) { resultat = service.virer(source, dest, BigDecimal.valueOf(montant)); }
Couverture de code (JaCoCo)Pourcentage du code exécuté par les tests (lignes, branches) mesuré par JaCoCo en Java (Istanbul côté JavaScript) ; indique ce qui n'est PAS testé, pas la qualité des tests.

Pourcentage du code exécuté par les tests (lignes, branches) mesuré par JaCoCo en Java (Istanbul côté JavaScript) ; indique ce qui n'est PAS testé, pas la qualité des tests.

xml
<plugin>
  <groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId>
  <executions>
    <execution><goals><goal>prepare-agent</goal></goals></execution>
    <execution><id>report</id><phase>verify</phase><goals><goal>report</goal></goals></execution>
  </executions>
</plugin>
⚠️ Piège100 % de couverture avec des tests sans assertion ne prouve rien. Viser une couverture élevée sur le code métier (~80 % sur le nouveau code) plutôt qu'un chiffre global.
Mutation testing (PIT)Outil qui modifie volontairement le code (inverse une condition, change un > en >=) et vérifie que les tests échouent ; un mutant qui « survit » révèle un test trop faible.

Outil qui modifie volontairement le code (inverse une condition, change un > en >=) et vérifie que les tests échouent ; un mutant qui « survit » révèle un test trop faible.

bash
./mvnw org.pitest:pitest-maven:mutationCoverage
Test flaky (instable)Test qui passe ou échoue de façon aléatoire sans changement de code (dépendance à l'heure, à l'ordre d'exécution, à des sleep, à des données partagées) ; il détruit la confiance dans la CI.

Test qui passe ou échoue de façon aléatoire sans changement de code (dépendance à l'heure, à l'ordre d'exécution, à des sleep, à des données partagées) ; il détruit la confiance dans la CI.

🎯 En entretienRemèdes : horloge injectée (Clock), données isolées par test, attentes sur condition (Awaitility) au lieu de Thread.sleep.
Test de contrat (Pact, Spring Cloud Contract)Vérifier automatiquement qu'un fournisseur d'API respecte ce que ses consommateurs attendent (format, champs, codes), sans démarrer tout le système ; évite de casser un autre microservice ou le front.

Vérifier automatiquement qu'un fournisseur d'API respecte ce que ses consommateurs attendent (format, champs, codes), sans démarrer tout le système ; évite de casser un autre microservice ou le front.

Tests de charge (Gatling, k6)Simuler de nombreux utilisateurs simultanés pour mesurer débit, latence (P95/P99) et point de rupture avant la production.

Simuler de nombreux utilisateurs simultanés pour mesurer débit, latence (P95/P99) et point de rupture avant la production.

javascript
// k6 : 200 utilisateurs virtuels pendant 5 minutes
import http from 'k6/http';
export const options = { vus: 200, duration: '5m', thresholds: { http_req_duration: ['p(95)<300'] } };
export default function () { http.get('https://staging.banque.sn/api/v1/comptes/42'); }

2JUnit & assertions7

JUnit 5 / JUnit 6Framework de tests standard en Java (plateforme JUnit Jupiter). Spring Boot 4 utilise JUnit 6 (Java 17+), dans la continuité de l'API de JUnit 5.

Framework de tests standard en Java (plateforme JUnit Jupiter). Spring Boot 4 utilise JUnit 6 (Java 17+), dans la continuité de l'API de JUnit 5.

java
@DisplayName("Service de virement")
class VirementServiceTest {
    @BeforeEach void setUp() { … }
    @Test @DisplayName("refuse un montant négatif")
    void montantNegatif() { … }
}
@Test et @DisplayName@Test marque une méthode de test ; @DisplayName donne un libellé lisible affiché dans les rapports (« refuse un virement au-delà du plafond »).

@Test marque une méthode de test ; @DisplayName donne un libellé lisible affiché dans les rapports (« refuse un virement au-delà du plafond »).

@BeforeEach, @AfterEach, @BeforeAllMéthodes exécutées avant/après chaque test (préparation, nettoyage) ou une seule fois avant/après tous les tests de la classe (méthodes statiques).

Méthodes exécutées avant/après chaque test (préparation, nettoyage) ou une seule fois avant/après tous les tests de la classe (méthodes statiques).

@ParameterizedTestExécute le même test avec plusieurs jeux de données, chacun rapporté séparément.

Exécute le même test avec plusieurs jeux de données, chacun rapporté séparément.

java
@ParameterizedTest(name = "{0} XOF → frais {1}")
@CsvSource({ "1000, 10", "100000, 1000", "800000, 5000" })
void frais(BigDecimal montant, BigDecimal attendu) {
    assertThat(calcul.fraisMobileMoney(montant)).isEqualByComparingTo(attendu);
}
@NestedClasse de test interne qui regroupe des tests par contexte (« quand le compte est gelé… »), pour des rapports structurés et lisibles.

Classe de test interne qui regroupe des tests par contexte (« quand le compte est gelé… »), pour des rapports structurés et lisibles.

AssertJ (assertThat)Bibliothèque d'assertions fluides et lisibles, avec des messages d'échec précis ; incluse dans spring-boot-starter-test.

Bibliothèque d'assertions fluides et lisibles, avec des messages d'échec précis ; incluse dans spring-boot-starter-test.

java
assertThat(clients)
    .hasSize(3)
    .extracting(Client::ville)
    .containsExactlyInAnyOrder("Dakar", "Thiès", "Saint-Louis");
assertThat(montant).isEqualByComparingTo("15000.00");   // BigDecimal : ignore l'échelle
⚠️ PiègePour un BigDecimal, isEqualTo compare aussi l'échelle (15000 ≠ 15000.00) : utiliser isEqualByComparingTo.
assertThatThrownByVérifie qu'un code lève bien une exception d'un type donné, avec le bon message.

Vérifie qu'un code lève bien une exception d'un type donné, avec le bon message.

java
assertThatThrownBy(() -> service.getOrder(99L))
    .isInstanceOf(NotFoundException.class)
    .hasMessage("Order not found: 99");

3Doublures de test (mocks)6

Mock, Stub, Spy, FakeDoublures qui remplacent une dépendance en test. Stub : renvoie des réponses prédéfinies. Mock : vérifie aussi les interactions (appelé ? combien de fois ?). Spy : objet réel dont on surcharge une partie. Fake : implémentation simplifiée qui fonctionne (repository en mémoire).

Doublures qui remplacent une dépendance en test. Stub : renvoie des réponses prédéfinies. Mock : vérifie aussi les interactions (appelé ? combien de fois ?). Spy : objet réel dont on surcharge une partie. Fake : implémentation simplifiée qui fonctionne (repository en mémoire).

DoublureRenvoie des valeursVérifie les appelsLogique réelle
Stub✅❌❌
Mock✅✅❌
Spypartiel✅✅
Fake✅❌simplifiée
MockitoBibliothèque Java de création de mocks : on définit leurs réponses (when/given) et on vérifie leurs interactions (verify/then).

Bibliothèque Java de création de mocks : on définit leurs réponses (when/given) et on vérifie leurs interactions (verify/then).

@Mock, @InjectMocks et MockitoExtension@ExtendWith(MockitoExtension.class) active Mockito ; @Mock crée une doublure ; @InjectMocks instancie la classe testée en lui injectant les mocks (par constructeur).

@ExtendWith(MockitoExtension.class) active Mockito ; @Mock crée une doublure ; @InjectMocks instancie la classe testée en lui injectant les mocks (par constructeur).

java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock OrderRepository repo;
    @Mock PaymentGateway gateway;
    @InjectMocks OrderService service;     // new OrderService(repo, gateway)
}
when/thenReturn et given/willReturn (BDDMockito)Programmer la réponse d'un mock : when(...).thenReturn(...) en style classique, given(...).willReturn(...) en style BDD, plus lisible dans un test Given/When/Then.

Programmer la réponse d'un mock : when(...).thenReturn(...) en style classique, given(...).willReturn(...) en style BDD, plus lisible dans un test Given/When/Then.

java
given(repo.findById(99L)).willReturn(Optional.empty());
given(gateway.charger(any(), any())).willThrow(new OperateurIndisponibleException());
when(tauxService.taux("EUR")).thenReturn(new BigDecimal("655.957"));
verify et then().should()Vérifier qu'un mock a été appelé (ou non), avec quels arguments et combien de fois.

Vérifier qu'un mock a été appelé (ou non), avec quels arguments et combien de fois.

java
then(repo).should().findById(99L);
then(notificateur).should(never()).envoyer(any());
verify(repo, times(1)).save(argThat(c -> c.solde().signum() >= 0));
ArgumentCaptorCapture l'argument passé à un mock pour l'inspecter en détail ensuite.

Capture l'argument passé à un mock pour l'inspecter en détail ensuite.

java
@Captor ArgumentCaptor<Audit> audit;
service.virer(…);
then(auditRepo).should().save(audit.capture());
assertThat(audit.getValue().type()).isEqualTo("VIREMENT_INITIE");

4Tests Spring Boot13

@SpringBootTestDémarre le contexte Spring complet (tous les beans) pour un test d'intégration ; avec webEnvironment = RANDOM_PORT, lance aussi un vrai serveur HTTP.

Démarre le contexte Spring complet (tous les beans) pour un test d'intégration ; avec webEnvironment = RANDOM_PORT, lance aussi un vrai serveur HTTP.

java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class VirementApiIT { … }
🎯 En entretienPlus lent qu'un test slice : Spring met en cache le contexte entre classes de test qui ont la même configuration (éviter de multiplier les variantes).
Test slices : @WebMvcTest, @DataJpaTestAnnotations qui ne chargent qu'une « tranche » de l'application : @WebMvcTest (contrôleurs, sécurité, JSON — services mockés), @DataJpaTest (repositories, JPA, base). Plus rapides que @SpringBootTest.

Annotations qui ne chargent qu'une « tranche » de l'application : @WebMvcTest (contrôleurs, sécurité, JSON — services mockés), @DataJpaTest (repositories, JPA, base). Plus rapides que @SpringBootTest.

java
@WebMvcTest(ClientController.class)
class ClientControllerTest {
    @Autowired MockMvc mvc;
    @MockitoBean ClientService service;
}

@DataJpaTest
class CompteRepositoryTest {
    @Autowired CompteRepository repo;
    @Test void trouve_par_iban() { … }
}
MockMvcPermet d'exécuter des requêtes HTTP simulées sur les contrôleurs Spring MVC sans démarrer de serveur, et de vérifier statut, en-têtes et JSON.

Permet d'exécuter des requêtes HTTP simulées sur les contrôleurs Spring MVC sans démarrer de serveur, et de vérifier statut, en-têtes et JSON.

java
mockMvc.perform(post("/api/v1/clients")
        .contentType(MediaType.APPLICATION_JSON)
        .content("""
                {"nom": "", "email": "pas-un-email"}
                """))
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.errors.email").exists());
@MockitoBean (remplace @MockBean)Remplace un bean du contexte Spring par un mock Mockito dans un test Spring. @MockitoBean (Spring Framework 6.2) remplace @MockBean, déprécié depuis Spring Boot 3.4.

Remplace un bean du contexte Spring par un mock Mockito dans un test Spring. @MockitoBean (Spring Framework 6.2) remplace @MockBean, déprécié depuis Spring Boot 3.4.

java
@WebMvcTest(UserController.class)
class UserControllerTest {
    @Autowired MockMvc mockMvc;
    @MockitoBean UserService userService;     // avant : @MockBean
}
jsonPathExpression qui cible une valeur dans une réponse JSON ($.name, $.items[0].id, $.length()) pour l'asserter dans MockMvc ou RestAssured.

Expression qui cible une valeur dans une réponse JSON ($.name, $.items[0].id, $.length()) pour l'asserter dans MockMvc ou RestAssured.

java
.andExpect(jsonPath("$.content.length()").value(20))
.andExpect(jsonPath("$.content[0].iban").value(startsWith("SN")))
TestcontainersBibliothèque qui démarre de vrais services dans des conteneurs Docker jetables pendant les tests (PostgreSQL, Kafka, Redis, Keycloak) : on teste contre la vraie technologie et non une base H2 différente.

Bibliothèque qui démarre de vrais services dans des conteneurs Docker jetables pendant les tests (PostgreSQL, Kafka, Redis, Keycloak) : on teste contre la vraie technologie et non une base H2 différente.

java
@SpringBootTest
@Testcontainers
class CompteRepositoryIT {
    @Container
    @ServiceConnection                                   // Boot configure la DataSource tout seul
    static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:17-alpine");

    @Autowired CompteRepository repo;
    @Test void sauvegarde_et_relit() { … }
}
@Container et @Testcontainers@Testcontainers active l'extension JUnit ; @Container marque un conteneur dont elle gère le cycle de vie (statique = un conteneur partagé par toute la classe).

@Testcontainers active l'extension JUnit ; @Container marque un conteneur dont elle gère le cycle de vie (statique = un conteneur partagé par toute la classe).

@DynamicPropertySourceInjecte dans le contexte Spring des propriétés calculées à l'exécution (URL et port aléatoires d'un conteneur), avant le démarrage du contexte.

Injecte dans le contexte Spring des propriétés calculées à l'exécution (URL et port aléatoires d'un conteneur), avant le démarrage du contexte.

java
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
    r.add("spring.datasource.url", pg::getJdbcUrl);
    r.add("spring.datasource.username", pg::getUsername);
    r.add("spring.datasource.password", pg::getPassword);
}
@ServiceConnectionAnnotation (Spring Boot 3.1+) posée sur un conteneur Testcontainers : Boot en déduit automatiquement la configuration de connexion (DataSource, Kafka, Redis…), sans @DynamicPropertySource.

Annotation (Spring Boot 3.1+) posée sur un conteneur Testcontainers : Boot en déduit automatiquement la configuration de connexion (DataSource, Kafka, Redis…), sans @DynamicPropertySource.

RestAssuredBibliothèque de tests d'API HTTP au style Given/When/Then, utilisée contre une application réellement démarrée (RANDOM_PORT) ou un environnement de recette.

Bibliothèque de tests d'API HTTP au style Given/When/Then, utilisée contre une application réellement démarrée (RANDOM_PORT) ou un environnement de recette.

java
given().port(port).auth().oauth2(token).contentType(JSON)
    .body(Map.of("ibanDest", "SN02…", "montant", 25000))
.when().post("/api/v1/virements")
.then().statusCode(201).body("statut", equalTo("EN_COURS"));
RestTestClient (Spring Boot 4)Client de test HTTP fluide de Spring Boot 4, qui remplace TestRestTemplate (déprécié) et s'utilise aussi bien sur MockMvc que sur un serveur démarré.

Client de test HTTP fluide de Spring Boot 4, qui remplace TestRestTemplate (déprécié) et s'utilise aussi bien sur MockMvc que sur un serveur démarré.

java
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureRestTestClient
class ApiIT {
    @Autowired RestTestClient client;
    @Test void sante() { client.get().uri("/actuator/health").exchange().expectStatus().isOk(); }
}
WireMockFaux serveur HTTP programmable qui simule une API externe (opérateur mobile money, BCEAO) : réponses, lenteurs et erreurs, pour tester timeouts, retries et circuit breakers.

Faux serveur HTTP programmable qui simule une API externe (opérateur mobile money, BCEAO) : réponses, lenteurs et erreurs, pour tester timeouts, retries et circuit breakers.

java
stubFor(post("/v1/paiements").willReturn(aResponse().withStatus(503).withFixedDelay(4000)));
Tester la sécurité (@WithMockUser, jwt())Simuler un utilisateur authentifié et ses rôles dans un test, pour vérifier que les règles d'accès renvoient bien 401/403 quand il le faut.

Simuler un utilisateur authentifié et ses rôles dans un test, pour vérifier que les règles d'accès renvoient bien 401/403 quand il le faut.

java
mockMvc.perform(get("/api/v1/admin/clients").with(jwt().authorities(new SimpleGrantedAuthority("ROLE_CLIENT"))))
       .andExpect(status().isForbidden());

@Test @WithMockUser(roles = "ADMIN")
void admin_peut_lister() throws Exception { mockMvc.perform(get("/api/v1/admin/clients")).andExpect(status().isOk()); }

5Tests Angular5

TestBedModule de test d'Angular qui configure un environnement d'injection et crée les composants à tester, avec leurs dépendances réelles ou simulées.

Module de test d'Angular qui configure un environnement d'injection et crée les composants à tester, avec leurs dépendances réelles ou simulées.

typescript
beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [ListeComptesComponent],
    providers: [{ provide: CompteService, useValue: { lister: () => of(COMPTES) } }],
  }).compileComponents();
});
ComponentFixture et detectChangesLa fixture enveloppe le composant créé : accès à son instance et à son DOM ; detectChanges() (ou whenStable() en zoneless) met le rendu à jour avant les vérifications.

La fixture enveloppe le composant créé : accès à son instance et à son DOM ; detectChanges() (ou whenStable() en zoneless) met le rendu à jour avant les vérifications.

typescript
const fixture = TestBed.createComponent(ListeComptesComponent);
fixture.detectChanges();
const lignes = fixture.nativeElement.querySelectorAll('li');
expect(lignes.length).toBe(3);
HttpTestingControllerContrôleur de test qui intercepte les requêtes HttpClient : on vérifie l'URL et la méthode appelées, puis on fournit la réponse simulée.

Contrôleur de test qui intercepte les requêtes HttpClient : on vérifie l'URL et la méthode appelées, puis on fournit la réponse simulée.

typescript
TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
const http = TestBed.inject(HttpTestingController);
service.lister().subscribe(c => expect(c.length).toBe(2));
const req = http.expectOne('/api/v1/clients');
expect(req.request.method).toBe('GET');
req.flush([{ id: 1 }, { id: 2 }]);
http.verify();                       // aucune requête inattendue
Jasmine/Karma, Jest et VitestOutils de tests JavaScript : Jasmine (assertions) exécuté par Karma dans un navigateur, historiquement par défaut ; Jest puis Vitest (défaut depuis Angular 21) sont plus rapides et s'exécutent en Node.

Outils de tests JavaScript : Jasmine (assertions) exécuté par Karma dans un navigateur, historiquement par défaut ; Jest puis Vitest (défaut depuis Angular 21) sont plus rapides et s'exécutent en Node.

Cypress et PlaywrightFrameworks de tests E2E qui pilotent un vrai navigateur ; Playwright (multi-navigateurs, rapide, attente automatique) est aujourd'hui le choix le plus courant, Selenium est l'ancien standard.

Frameworks de tests E2E qui pilotent un vrai navigateur ; Playwright (multi-navigateurs, rapide, attente automatique) est aujourd'hui le choix le plus courant, Selenium est l'ancien standard.

typescript
test('virement réussi', async ({ page }) => {
  await page.goto('/virements/nouveau');
  await page.getByLabel('IBAN bénéficiaire').fill('SN02…');
  await page.getByLabel('Montant').fill('25000');
  await page.getByRole('button', { name: 'Valider' }).click();
  await expect(page.getByText('Virement en cours')).toBeVisible();
});

📋 Synthèse express — Tests

🔺 Pyramide des tests

🔬 Tests Unitaires (70%)

Rapides, isolés. Mockent les dépendances. JUnit 5 + Mockito. Testent 1 classe/méthode à la fois.

🔗 Tests Intégration (20%)

Testent plusieurs couches ensemble. @SpringBootTest, Testcontainers pour DB/Redis réels en Docker.

🌐 Tests E2E (10%)

Simulent un vrai utilisateur. Playwright/Selenium. Lents, fragiles. Seulement les scénarios critiques.

🔴🟢🔵 Cycle TDD

Red → Green → Refactor : 1) Écrire un test qui échoue (Red) 2) Écrire le minimum de code pour passer (Green) 3) Refactoriser sans casser les tests (Refactor). Ne jamais sauter l'étape Red.

🃏 JUnit 5 + Mockito

@ExtendWith(MockitoExtension.class) class OrderServiceTest { @Mock OrderRepository repo; @InjectMocks OrderService service; @Test @DisplayName("Should throw when order not found") void shouldThrowWhenNotFound() { given(repo.findById(99L)).willReturn(Optional.empty()); assertThatThrownBy(() -> service.getOrder(99L)) .isInstanceOf(NotFoundException.class) .hasMessage("Order not found: 99"); then(repo).should().findById(99L); } }

🐳 Testcontainers

@SpringBootTest @Testcontainers class OrderRepositoryIT { @Container static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16-alpine"); @DynamicPropertySource static void configureProps(DynamicPropertyRegistry r) { r.add("spring.datasource.url", pg::getJdbcUrl); } }

🧪 MockMvc — Test Spring MVC

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
  @Autowired MockMvc mockMvc;
  @MockitoBean UserService userService;   // remplace @MockBean (déprécié depuis Boot 3.4)

  @Test
  void getUser_shouldReturn200() throws Exception {
    when(userService.findById(1L)).thenReturn(new UserDto(1L, "Alice"));

    mockMvc.perform(get("/api/users/1")
        .header("Authorization", "Bearer " + token))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.name").value("Alice"));
  }

  @Test
  void getUser_notFound_shouldReturn404() throws Exception {
    when(userService.findById(99L)).thenThrow(new ResourceNotFoundException("User not found"));

    mockMvc.perform(get("/api/users/99"))
      .andExpect(status().isNotFound());
  }
}

🧰 Outils par niveau de test

NiveauOutils JavaOutils AngularVitesseCouverture
Unitaires (70%)JUnit 5, MockitoJasmine, Jest⚡ Très rapideLogique métier
Intégration (20%)@SpringBootTest, MockMvcTestBed Angular🔄 MoyenCouche service/API
E2E (10%)RestAssuredCypress, Playwright🐢 LentParcours utilisateur
Qualité de code : SonarQube mesure la couverture, les duplications, les code smells et les vulnérabilités. Seuil recommandé : 80% de couverture pour les nouvelles lignes.
⚡ Quiz Rapide — Tests
1. Dans le cycle TDD, dans quel ordre doit-on écrire le code ?
A
Code → Test → Refactor
B
Test (Red) → Code minimal (Green) → Refactor
C
Refactor → Test → Code
D
L'ordre n'a pas d'importance en TDD
✅ B. Red → Green → Refactor. Le test doit OBLIGATOIREMENT échouer d'abord (Red) pour prouver qu'il teste bien la nouvelle fonctionnalité. Ensuite code minimal pour passer (Green). Enfin améliorer le code sans casser les tests (Refactor).
Observabilité
Prometheus, Grafana, ELK Stack, Distributed Tracing, RCA
📖 Cours complet — 45 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/45 maîtrisées

1Fondamentaux3

Monitoring vs observabilitéLe monitoring surveille des indicateurs connus à l'avance (CPU, erreurs) et alerte ; l'observabilité est la capacité à comprendre n'importe quel comportement inattendu du système à partir des données qu'il émet, sans redéployer.

Le monitoring surveille des indicateurs connus à l'avance (CPU, erreurs) et alerte ; l'observabilité est la capacité à comprendre n'importe quel comportement inattendu du système à partir des données qu'il émet, sans redéployer.

Monitoring : « est-ce que ça marche ? ». Observabilité : « pourquoi ça ne marche pas, pour ce client, sur ce virement précis ? ».

Les trois piliers : métriques, logs, tracesMétriques : chiffres agrégés dans le temps (combien, à quelle vitesse). Logs : événements détaillés (que s'est-il passé). Traces : parcours d'une requête à travers les services (où le temps a été passé).

Métriques : chiffres agrégés dans le temps (combien, à quelle vitesse). Logs : événements détaillés (que s'est-il passé). Traces : parcours d'une requête à travers les services (où le temps a été passé).

PilierQuestionOutil type
MétriquesLe taux d'erreur augmente-t-il ?Prometheus + Grafana
LogsQuelle erreur exactement, pour quel virement ?ELK, Loki
TracesQuel service ralentit cette requête ?Tempo, Jaeger, Zipkin
🎯 En entretienLa force vient de leur corrélation : le même traceId dans les logs, les traces et les exemplars des métriques.
TélémétrieEnsemble des données (métriques, logs, traces, profils) émises automatiquement par une application pour la superviser à distance.

Ensemble des données (métriques, logs, traces, profils) émises automatiquement par une application pour la superviser à distance.

2Métriques12

Métrique (série temporelle)Valeur numérique mesurée à intervalles réguliers, identifiée par un nom et des labels (dimensions) ; peu coûteuse à stocker et idéale pour les tableaux de bord et alertes.

Valeur numérique mesurée à intervalles réguliers, identifiée par un nom et des labels (dimensions) ; peu coûteuse à stocker et idéale pour les tableaux de bord et alertes.

text
http_server_requests_seconds_count{uri="/api/v1/virements", method="POST", status="201"} 15342
CounterMétrique qui ne fait qu'augmenter (ou repart à zéro au redémarrage) : nombre de requêtes, d'erreurs, de virements. On l'exploite par son taux de variation (rate).

Métrique qui ne fait qu'augmenter (ou repart à zéro au redémarrage) : nombre de requêtes, d'erreurs, de virements. On l'exploite par son taux de variation (rate).

java
Counter virements = Counter.builder("banque.virements")
        .tag("canal", "mobile").register(registry);
virements.increment();
GaugeMétrique qui monte et descend : valeur instantanée (connexions actives, taille d'une file, mémoire utilisée, lag Kafka).

Métrique qui monte et descend : valeur instantanée (connexions actives, taille d'une file, mémoire utilisée, lag Kafka).

java
Gauge.builder("banque.virements.en_attente", file, Queue::size).register(registry);
HistogramRépartit les mesures (durées, tailles) dans des intervalles (buckets) ; permet de calculer des percentiles côté serveur et de les agréger entre instances.

Répartit les mesures (durées, tailles) dans des intervalles (buckets) ; permet de calculer des percentiles côté serveur et de les agréger entre instances.

properties
management.metrics.distribution.percentiles-histogram.http.server.requests=true
SummaryCalcule les percentiles directement dans l'application ; précis pour une instance, mais ces percentiles ne peuvent pas être agrégés entre plusieurs pods (on préfère donc les histogrammes).

Calcule les percentiles directement dans l'application ; précis pour une instance, mais ces percentiles ne peuvent pas être agrégés entre plusieurs pods (on préfère donc les histogrammes).

Percentiles P50 / P95 / P99P95 = 95 % des requêtes sont plus rapides que cette valeur. La moyenne cache les cas lents ; le P99 montre ce que vivent les clients les moins bien servis.

P95 = 95 % des requêtes sont plus rapides que cette valeur. La moyenne cache les cas lents ; le P99 montre ce que vivent les clients les moins bien servis.

text
Moyenne 120 ms, P50 80 ms, P95 450 ms, P99 2,1 s
→ 1 requête sur 100 dépasse 2 s : à investiguer même si la moyenne semble bonne
Cardinalité des labelsNombre de combinaisons distinctes de labels d'une métrique ; un label à valeurs illimitées (id client, id virement) crée des millions de séries et fait exploser Prometheus.

Nombre de combinaisons distinctes de labels d'une métrique ; un label à valeurs illimitées (id client, id virement) crée des millions de séries et fait exploser Prometheus.

java
// ❌ une série par client
Counter.builder("virements").tag("clientId", id)
// ✅ dimensions bornées
Counter.builder("virements").tag("canal", "mobile").tag("statut", "OK")
🎯 En entretienLes identifiants individuels vont dans les logs et les traces, pas dans les labels de métriques.
PrometheusBase de séries temporelles qui « scrape » (vient lire) régulièrement l'endpoint de métriques de chaque application (modèle pull), stocke les séries et évalue les règles d'alerte.

Base de séries temporelles qui « scrape » (vient lire) régulièrement l'endpoint de métriques de chaque application (modèle pull), stocke les séries et évalue les règles d'alerte.

yaml
scrape_configs:
  - job_name: virements
    scrape_interval: 15s
    metrics_path: /actuator/prometheus
    kubernetes_sd_configs: [{ role: pod }]
PromQLLangage de requête de Prometheus pour calculer taux, ratios et percentiles à partir des séries.

Langage de requête de Prometheus pour calculer taux, ratios et percentiles à partir des séries.

text
# Requêtes par seconde (5 dernières minutes)
sum(rate(http_server_requests_seconds_count{application="virements"}[5m]))

# Taux d'erreurs 5xx
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
  / sum(rate(http_server_requests_seconds_count[5m]))

# Latence P95
histogram_quantile(0.95, sum by (le) (rate(http_server_requests_seconds_bucket[5m])))
MicrometerFaçade de métriques (et d'observations) de Spring Boot, « le SLF4J des métriques » : on instrumente une fois, et l'export se fait vers Prometheus, Datadog, etc. Spring instrumente déjà HTTP, JDBC, Kafka, JVM.

Façade de métriques (et d'observations) de Spring Boot, « le SLF4J des métriques » : on instrumente une fois, et l'export se fait vers Prometheus, Datadog, etc. Spring instrumente déjà HTTP, JDBC, Kafka, JVM.

java
@Observed(name = "virement.execution")              // métrique + span de trace automatiques
public void executer(Virement v) { … }

Timer.builder("operateur.appel").register(registry).record(() -> client.appeler());
Endpoint /actuator/prometheusEndpoint exposé par Spring Boot Actuator (avec la dépendance micrometer-registry-prometheus) qui publie toutes les métriques au format texte lu par Prometheus.

Endpoint exposé par Spring Boot Actuator (avec la dépendance micrometer-registry-prometheus) qui publie toutes les métriques au format texte lu par Prometheus.

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,prometheus
  metrics:
    tags:
      application: virements
GrafanaOutil de visualisation qui construit des tableaux de bord et des alertes à partir de Prometheus, Loki, Tempo, Elasticsearch… et permet de naviguer d'une métrique aux logs et traces correspondants.

Outil de visualisation qui construit des tableaux de bord et des alertes à partir de Prometheus, Loki, Tempo, Elasticsearch… et permet de naviguer d'une métrique aux logs et traces correspondants.

3Logs7

Log structuré (JSON)Chaque ligne de log est un objet JSON avec des champs (niveau, message, traceId, compte, durée) au lieu d'une phrase libre : recherche et filtrage fiables dans Kibana/Loki.

Chaque ligne de log est un objet JSON avec des champs (niveau, message, traceId, compte, durée) au lieu d'une phrase libre : recherche et filtrage fiables dans Kibana/Loki.

properties
# Spring Boot 3.4+ : logs structurés intégrés
logging.structured.format.console=ecs
json
{"@timestamp":"2026-10-14T16:33:00Z","log.level":"ERROR","message":"Paiement refusé",
 "trace.id":"4bf92f3577b34da6","service.name":"virements","motif":"SOLDE_INSUFFISANT"}
Niveaux de logTRACE < DEBUG < INFO < WARN < ERROR : on règle par package le niveau minimal affiché. ERROR = action requise, WARN = anomalie gérée, INFO = événements métier importants, DEBUG = diagnostic.

TRACE < DEBUG < INFO < WARN < ERROR : on règle par package le niveau minimal affiché. ERROR = action requise, WARN = anomalie gérée, INFO = événements métier importants, DEBUG = diagnostic.

properties
logging.level.root=INFO
logging.level.sn.banque.virement=DEBUG
logging.level.org.hibernate.SQL=DEBUG      # temporaire, pour voir les requêtes
⚠️ PiègeNe pas loguer en ERROR une erreur client attendue (400) : les vraies alertes se noient.
MDC et identifiant de corrélationLe MDC (Mapped Diagnostic Context) ajoute automatiquement des champs (traceId, utilisateur, idVirement) à toutes les lignes de log du thread courant ; un identifiant de corrélation relie toutes les lignes d'une même requête.

Le MDC (Mapped Diagnostic Context) ajoute automatiquement des champs (traceId, utilisateur, idVirement) à toutes les lignes de log du thread courant ; un identifiant de corrélation relie toutes les lignes d'une même requête.

java
MDC.put("virementId", v.id().toString());
try { log.info("Débit du compte source"); … }
finally { MDC.remove("virementId"); }
// Avec Micrometer Tracing, traceId et spanId sont ajoutés au MDC automatiquement
Pile ELK (Elasticsearch, Logstash, Kibana)Chaîne de gestion des logs : Logstash (ou Fluent Bit) collecte et transforme, Elasticsearch indexe et stocke, Kibana permet de rechercher et visualiser.

Chaîne de gestion des logs : Logstash (ou Fluent Bit) collecte et transforme, Elasticsearch indexe et stocke, Kibana permet de rechercher et visualiser.

text
Pods (stdout JSON) → Fluent Bit (DaemonSet) → Elasticsearch → Kibana
Fluentd / Fluent BitCollecteurs de logs : sur Kubernetes, un agent par nœud lit les sorties des conteneurs, enrichit avec les métadonnées (pod, namespace) et envoie vers Elasticsearch, Loki ou un SIEM.

Collecteurs de logs : sur Kubernetes, un agent par nœud lit les sorties des conteneurs, enrichit avec les métadonnées (pod, namespace) et envoie vers Elasticsearch, Loki ou un SIEM.

LokiSystème de logs de Grafana Labs qui indexe seulement les labels (pas le texte complet) : beaucoup moins coûteux qu'Elasticsearch, interrogé avec LogQL depuis Grafana.

Système de logs de Grafana Labs qui indexe seulement les labels (pas le texte complet) : beaucoup moins coûteux qu'Elasticsearch, interrogé avec LogQL depuis Grafana.

Données sensibles dans les logsLes logs sont largement accessibles et conservés longtemps : jamais de mot de passe, token, numéro de carte complet ou pièce d'identité en clair ; masquer les données personnelles.

Les logs sont largement accessibles et conservés longtemps : jamais de mot de passe, token, numéro de carte complet ou pièce d'identité en clair ; masquer les données personnelles.

java
log.info("Virement vers {}", Iban.masquer(iban));   // SN08 **** **** **** 4521

4Traces distribuées7

Trace distribuéeEnregistrement du parcours complet d'une requête à travers tous les services traversés, sous forme d'un arbre de spans chronométrés : on voit immédiatement quel service ou quelle requête SQL prend le temps.

Enregistrement du parcours complet d'une requête à travers tous les services traversés, sous forme d'un arbre de spans chronométrés : on voit immédiatement quel service ou quelle requête SQL prend le temps.

text
POST /virements                         820 ms
├─ virements-service: valider            12 ms
├─ comptes-service: GET /comptes/42      35 ms
├─ PostgreSQL: UPDATE compte             18 ms
└─ operateur-api: POST /paiements       740 ms   ← goulot
SpanUnité de travail chronométrée dans une trace (un appel HTTP, une requête SQL, un message Kafka), avec début, durée, attributs, statut et lien vers son span parent.

Unité de travail chronométrée dans une trace (un appel HTTP, une requête SQL, un message Kafka), avec début, durée, attributs, statut et lien vers son span parent.

Trace ID et propagation (W3C traceparent)Identifiant unique d'une requête, transmis d'un service à l'autre dans les en-têtes HTTP (traceparent) et les en-têtes de messages Kafka, pour reconstituer la trace complète.

Identifiant unique d'une requête, transmis d'un service à l'autre dans les en-têtes HTTP (traceparent) et les en-têtes de messages Kafka, pour reconstituer la trace complète.

http
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             └v┘└──────────── trace id ────────────┘└── span id ───┘└flags┘
🎯 En entretienAvec RestClient, WebClient et KafkaTemplate créés par Spring Boot, la propagation est automatique ; un client créé avec new ne propage rien.
OpenTelemetry (OTel)Standard ouvert (CNCF) pour produire et transporter métriques, logs et traces de façon indépendante des fournisseurs ; protocole OTLP vers un collecteur, puis vers Tempo, Jaeger, Datadog…

Standard ouvert (CNCF) pour produire et transporter métriques, logs et traces de façon indépendante des fournisseurs ; protocole OTLP vers un collecteur, puis vers Tempo, Jaeger, Datadog…

properties
# Spring Boot 4 : starter OpenTelemetry
management.otlp.tracing.endpoint=http://otel-collector:4318/v1/traces
management.tracing.sampling.probability=0.1
Zipkin, Jaeger, TempoBackends de stockage et de visualisation des traces : Zipkin (historique, simple), Jaeger (CNCF), Grafana Tempo (stockage objet peu coûteux, intégré à Grafana).

Backends de stockage et de visualisation des traces : Zipkin (historique, simple), Jaeger (CNCF), Grafana Tempo (stockage objet peu coûteux, intégré à Grafana).

Micrometer TracingCouche de traçage de Spring Boot 3+ (successeur de Spring Cloud Sleuth) qui crée les spans pour HTTP, JDBC, Kafka et les exporte via OpenTelemetry ou Brave.

Couche de traçage de Spring Boot 3+ (successeur de Spring Cloud Sleuth) qui crée les spans pour HTTP, JDBC, Kafka et les exporte via OpenTelemetry ou Brave.

Échantillonnage (sampling)Ne conserver qu'une fraction des traces (ex. 10 %) pour maîtriser les coûts ; le « tail sampling » garde en priorité les traces lentes ou en erreur.

Ne conserver qu'une fraction des traces (ex. 10 %) pour maîtriser les coûts ; le « tail sampling » garde en priorité les traces lentes ou en erreur.

5Fiabilité (SRE)10

SLI (Service Level Indicator)Mesure chiffrée de la qualité de service vue par l'utilisateur : proportion de requêtes réussies, proportion de requêtes servies en moins de 300 ms, fraîcheur des données.

Mesure chiffrée de la qualité de service vue par l'utilisateur : proportion de requêtes réussies, proportion de requêtes servies en moins de 300 ms, fraîcheur des données.

text
SLI disponibilité = requêtes non-5xx / requêtes totales
SLI latence       = requêtes < 300 ms / requêtes totales
SLO (Service Level Objective)Objectif interne fixé sur un SLI pour une période : « 99,9 % des virements aboutissent sur 30 jours glissants ». Il guide les priorités entre nouvelles fonctionnalités et fiabilité.

Objectif interne fixé sur un SLI pour une période : « 99,9 % des virements aboutissent sur 30 jours glissants ». Il guide les priorités entre nouvelles fonctionnalités et fiabilité.

SLA (Service Level Agreement)Engagement contractuel envers un client ou partenaire, assorti de pénalités ; toujours moins exigeant que le SLO interne pour garder une marge.

Engagement contractuel envers un client ou partenaire, assorti de pénalités ; toujours moins exigeant que le SLO interne pour garder une marge.

Error budget (budget d'erreur)Part d'échec tolérée par le SLO (100 % − SLO) : avec 99,9 % sur 30 jours, environ 43 minutes d'indisponibilité. Tant qu'il reste du budget, on livre ; s'il est épuisé, on priorise la fiabilité.

Part d'échec tolérée par le SLO (100 % − SLO) : avec 99,9 % sur 30 jours, environ 43 minutes d'indisponibilité. Tant qu'il reste du budget, on livre ; s'il est épuisé, on priorise la fiabilité.

Disponibilité et « nombre de 9 »Pourcentage de temps où le service répond correctement ; chaque « 9 » supplémentaire divise par dix l'indisponibilité tolérée.

Pourcentage de temps où le service répond correctement ; chaque « 9 » supplémentaire divise par dix l'indisponibilité tolérée.

DisponibilitéIndisponibilité / mois/ an
99 %~7 h 18~3,65 jours
99,9 %~43 min~8 h 45
99,95 %~22 min~4 h 22
99,99 %~4 min 23~52 min
Méthode USE (infrastructure)Pour chaque ressource (CPU, mémoire, disque, réseau, pool de connexions) : Utilization (taux d'occupation), Saturation (travail en attente), Errors (erreurs).

Pour chaque ressource (CPU, mémoire, disque, réseau, pool de connexions) : Utilization (taux d'occupation), Saturation (travail en attente), Errors (erreurs).

Méthode RED (services)Pour chaque service : Rate (requêtes par seconde), Errors (taux d'erreurs), Duration (latence, en percentiles). Base d'un tableau de bord de microservice.

Pour chaque service : Rate (requêtes par seconde), Errors (taux d'erreurs), Duration (latence, en percentiles). Base d'un tableau de bord de microservice.

Les quatre signaux d'or (Golden Signals)Recommandation de Google SRE : surveiller latence, trafic, erreurs et saturation ; proche de RED + saturation.

Recommandation de Google SRE : surveiller latence, trafic, erreurs et saturation ; proche de RED + saturation.

Alerting : alerter sur les symptômesAlerter sur ce que vit l'utilisateur (taux d'erreur, latence, consommation rapide du budget d'erreur) plutôt que sur des causes (CPU à 80 %) ; chaque alerte doit être actionnable et liée à un runbook.

Alerter sur ce que vit l'utilisateur (taux d'erreur, latence, consommation rapide du budget d'erreur) plutôt que sur des causes (CPU à 80 %) ; chaque alerte doit être actionnable et liée à un runbook.

yaml
- alert: TauxErreurVirementsEleve
  expr: |
    sum(rate(http_server_requests_seconds_count{application="virements",status=~"5.."}[5m]))
    / sum(rate(http_server_requests_seconds_count{application="virements"}[5m])) > 0.02
  for: 5m
  labels: { severity: critical }
  annotations:
    summary: "Plus de 2 % d'erreurs 5xx sur les virements"
    runbook: "https://wiki.banque.sn/runbooks/virements-erreurs"
Latence de queue (tail latency)Les requêtes les plus lentes (P99, P99.9) ; dans une architecture où une page appelle 10 services, la probabilité de tomber sur au moins un appel lent devient forte.

Les requêtes les plus lentes (P99, P99.9) ; dans une architecture où une page appelle 10 services, la probabilité de tomber sur au moins un appel lent devient forte.

6Incidents6

Processus d'investigation d'un incidentDémarche structurée : 1) mesurer l'impact (qui, combien, depuis quand) ; 2) stabiliser (rollback, feature flag, scale) ; 3) corréler métriques → traces → logs ; 4) identifier ce qui a changé ; 5) corriger ; 6) communiquer ; 7) post-mortem.

Démarche structurée : 1) mesurer l'impact (qui, combien, depuis quand) ; 2) stabiliser (rollback, feature flag, scale) ; 3) corréler métriques → traces → logs ; 4) identifier ce qui a changé ; 5) corriger ; 6) communiquer ; 7) post-mortem.

🎯 En entretienEn entretien, insister sur « d'abord restaurer le service, ensuite comprendre » et sur la communication régulière aux parties prenantes.
RCA (Root Cause Analysis)Recherche de la cause profonde d'un incident (et pas seulement du symptôme), souvent avec la méthode des « 5 pourquoi ».

Recherche de la cause profonde d'un incident (et pas seulement du symptôme), souvent avec la méthode des « 5 pourquoi ».

text
Pourquoi les virements ont échoué ? → pool de connexions épuisé
Pourquoi ? → requêtes lentes qui gardent les connexions
Pourquoi ? → index supprimé par une migration
Pourquoi ? → migration non revue, pas de test de performance
Cause racine → absence de revue des migrations + pas de garde-fou en CI
Post-mortem sans reproche (blameless)Document écrit après un incident : chronologie, impact, cause racine, ce qui a bien ou mal fonctionné, actions correctives avec responsables et échéances ; on analyse le système, pas les personnes.

Document écrit après un incident : chronologie, impact, cause racine, ce qui a bien ou mal fonctionné, actions correctives avec responsables et échéances ; on analyse le système, pas les personnes.

MTTD et MTTRMTTD (Mean Time To Detect) : temps moyen pour détecter un incident. MTTR (Mean Time To Recover/Restore) : temps moyen pour rétablir le service. On les réduit avec de bonnes alertes, des runbooks et des rollbacks rapides.

MTTD (Mean Time To Detect) : temps moyen pour détecter un incident. MTTR (Mean Time To Recover/Restore) : temps moyen pour rétablir le service. On les réduit avec de bonnes alertes, des runbooks et des rollbacks rapides.

RunbookProcédure écrite et testée pour diagnostiquer et traiter un type d'alerte (commandes, tableaux de bord à ouvrir, actions de mitigation, contacts d'escalade).

Procédure écrite et testée pour diagnostiquer et traiter un type d'alerte (commandes, tableaux de bord à ouvrir, actions de mitigation, contacts d'escalade).

Health checkEndpoint qui indique l'état de l'application et de ses dépendances (base, broker, disque) ; utilisé par les probes Kubernetes, les load balancers et la supervision.

Endpoint qui indique l'état de l'application et de ses dépendances (base, broker, disque) ; utilisé par les probes Kubernetes, les load balancers et la supervision.

json
GET /actuator/health
{ "status": "UP", "components": { "db": { "status": "UP" }, "kafka": { "status": "UP" }, "diskSpace": { "status": "UP" } } }

📋 Synthèse express — Observabilité

📊 Les 3 piliers de l'observabilité

📈 Métriques (Prometheus)

Données numériques agrégées dans le temps. Types : Counter, Gauge, Histogram, Summary. Scraping toutes les 15s.

📋 Logs (ELK / FluentD)

Événements discrets avec contexte. Elasticsearch + Logstash/FluentD + Kibana. Structurés JSON recommandés.

🔍 Traces (Zipkin/Jaeger)

Suivi d'une requête à travers les microservices. Trace ID propagé entre services. Permet d'identifier la latence par service.

📐 SLI / SLO / SLA

AcronymeDéfinitionExemple
SLIService Level Indicator — la métrique mesuréeTaux de requêtes <200ms
SLOService Level Objective — l'objectif interne99.9% des requêtes <200ms
SLAService Level Agreement — l'engagement contractuel99.5% de disponibilité mensuelle

📊 Méthodes Grafana

USE Method (infrastructure) : Utilization (% d'utilisation) · Saturation (file d'attente) · Errors (taux d'erreurs)
RED Method (services) : Rate (requêtes/sec) · Errors (taux d'erreurs) · Duration (latence P50/P95/P99)
⚡ Quiz Rapide — Observabilité
1. Quelle est la différence entre SLO et SLA ?
A
SLO est contractuel avec le client, SLA est interne
B
SLO est l'objectif interne (plus strict), SLA est l'engagement contractuel client
C
Ce sont deux noms pour la même chose
D
SLA concerne uniquement la disponibilité
✅ B. Le SLO (objectif interne) est toujours plus strict que le SLA (contractuel) pour avoir une marge de manœuvre. Ex : SLO interne 99.9%, SLA client 99.5%. Le Error Budget = 100% - SLO = temps disponible pour les incidents sans violer le SLO.
Fiche Mémo — Révision Express
Les points clés à ne pas oublier en entretien Tech Lead Baobab
📖 Cours complet — 28 notions expliquées
Ouvre une notion : définition, exemple de code et ce qu'il faut dire en entretien. Coche « Je maîtrise » quand tu sais l'expliquer sans regarder.
0/28 maîtrisées

1Réussir l'entretien3

Méthode STARStructure pour raconter une expérience : Situation (contexte), Tâche (ton objectif), Action (ce que TU as fait concrètement), Résultat (chiffré si possible) ; évite les réponses vagues.

Structure pour raconter une expérience : Situation (contexte), Tâche (ton objectif), Action (ce que TU as fait concrètement), Résultat (chiffré si possible) ; évite les réponses vagues.

text
S : L'API de consultation de solde dépassait 8 s aux heures de pointe (fin de mois, paie).
T : Ramener le P95 sous 500 ms sans changer d'infrastructure, en 2 semaines.
A : Profilage + traces → N+1 Hibernate sur les mouvements ; JOIN FETCH, index (compte_id, date),
    cache Redis des plafonds, pagination keyset ; test de charge k6 avant/après.
R : P95 de 8 s à 320 ms, CPU base -60 %, zéro incident sur les 3 fins de mois suivantes.
🎯 En entretienParler en « je » pour les actions (le recruteur évalue ta contribution), en « nous » pour le contexte d'équipe.
Pitch de présentation (3 minutes)Présentation orale courte et structurée : qui tu es, ton parcours en 2-3 étapes marquantes, ta spécialité, un résultat dont tu es fier, et pourquoi ce poste.

Présentation orale courte et structurée : qui tu es, ton parcours en 2-3 étapes marquantes, ta spécialité, un résultat dont tu es fier, et pourquoi ce poste.

text
1. Identité (15 s)        : développeur fullstack / tech lead, X ans d'expérience
2. Parcours (60 s)        : 2-3 expériences, chacune avec un résultat concret
3. Expertise (45 s)       : Java/Spring, Angular, microservices, sécurité (Keycloak), IA (RAG)
4. Leadership (30 s)      : mentorat, revues de code, décisions d'architecture
5. Motivation (30 s)      : pourquoi ce poste, cette entreprise, ce secteur
Exercice de conception (system design)Exercice où l'on conçoit un système à l'oral (ex. « un service de virements instantanés ») : on évalue la démarche plus que la solution.

Exercice où l'on conçoit un système à l'oral (ex. « un service de virements instantanés ») : on évalue la démarche plus que la solution.

Démarche : 1) clarifier besoins et volumes ; 2) exigences non fonctionnelles (latence, disponibilité, sécurité, conformité) ; 3) API et modèle de données ; 4) architecture et flux ; 5) points de défaillance, cohérence, idempotence ; 6) observabilité et déploiement ; 7) compromis assumés.

🎯 En entretienToujours annoncer les hypothèses (« je suppose 50 virements/s en pointe ») et les compromis (« je choisis la cohérence plutôt que la disponibilité pour le débit des comptes »).

2Domaine bancaire & fintech9

Core Banking SystemSystème central d'une banque qui tient les comptes, les soldes, les écritures, les produits (épargne, crédit) et la clôture journalière ; les canaux (mobile, web, agences) s'y connectent par API.

Système central d'une banque qui tient les comptes, les soldes, les écritures, les produits (épargne, crédit) et la clôture journalière ; les canaux (mobile, web, agences) s'y connectent par API.

Souvent un progiciel ancien et critique : on l'entoure d'APIs et de services modernes plutôt que de le modifier (pattern Strangler Fig, couche anti-corruption).

KYC (Know Your Customer)Obligation de vérifier l'identité d'un client et d'évaluer son profil de risque avant et pendant la relation : pièce d'identité, justificatif, selfie/biométrie, contrôle des listes de sanctions.

Obligation de vérifier l'identité d'un client et d'évaluer son profil de risque avant et pendant la relation : pièce d'identité, justificatif, selfie/biométrie, contrôle des listes de sanctions.

Côté technique : workflow de vérification (souvent BPMN), stockage chiffré des documents, traçabilité de chaque décision, niveaux de KYC qui débloquent des plafonds.

AML / LCB-FTLutte contre le blanchiment d'argent et le financement du terrorisme : surveillance des transactions (montants inhabituels, fractionnement, pays à risque), déclarations de soupçon, filtrage des listes de sanctions.

Lutte contre le blanchiment d'argent et le financement du terrorisme : surveillance des transactions (montants inhabituels, fractionnement, pays à risque), déclarations de soupçon, filtrage des listes de sanctions.

sql
-- Exemple de règle : fractionnement (beaucoup de petits dépôts juste sous le seuil déclaratif)
SELECT client_id, COUNT(*), SUM(montant)
FROM depot
WHERE date_operation >= CURRENT_DATE - 7 AND montant BETWEEN 4500000 AND 4999999
GROUP BY client_id HAVING COUNT(*) >= 3;
Réconciliation (rapprochement)Comparer deux sources censées concorder (écritures de la banque vs relevé d'un opérateur mobile money ou d'un partenaire) pour détecter écarts, doublons et opérations manquantes, puis les traiter.

Comparer deux sources censées concorder (écritures de la banque vs relevé d'un opérateur mobile money ou d'un partenaire) pour détecter écarts, doublons et opérations manquantes, puis les traiter.

Traitement batch quotidien typique : chargement des deux fichiers, appariement par référence, rapport des écarts, workflow de correction. Voir FULL OUTER JOIN.

Conformité réglementaireRespect des règles imposées au secteur : réglementation bancaire (BCEAO en zone UEMOA), protection des données personnelles (CDP au Sénégal), sécurité des données de carte (PCI DSS), obligations LCB-FT.

Respect des règles imposées au secteur : réglementation bancaire (BCEAO en zone UEMOA), protection des données personnelles (CDP au Sénégal), sécurité des données de carte (PCI DSS), obligations LCB-FT.

Impacts techniques : chiffrement, journal d'audit immuable, conservation et purge des données, contrôle des accès, hébergement et sous-traitance encadrés.

Comptabilité en partie doubleChaque opération génère au moins deux écritures de même montant, un débit et un crédit, dont la somme est toujours nulle ; c'est la base de toute tenue de comptes fiable.

Chaque opération génère au moins deux écritures de même montant, un débit et un crédit, dont la somme est toujours nulle ; c'est la base de toute tenue de comptes fiable.

sql
-- Virement de 50 000 XOF : 2 écritures dans la même transaction
INSERT INTO ecriture(operation_id, compte, sens, montant) VALUES
  (:op, 'SN01-CLIENT-A', 'DEBIT',  50000),
  (:op, 'SN01-CLIENT-B', 'CREDIT', 50000);
-- Contrôle d'intégrité : pour chaque opération, somme des débits = somme des crédits
🎯 En entretienOn ne modifie ni ne supprime une écriture : une erreur se corrige par une écriture inverse (contre-passation), ce qui garde l'historique intact.
Arrondis monétaires (BigDecimal et RoundingMode)Les montants se calculent en BigDecimal avec une échelle et un mode d'arrondi explicites ; HALF_EVEN (arrondi bancaire) limite les biais cumulés sur de grands volumes.

Les montants se calculent en BigDecimal avec une échelle et un mode d'arrondi explicites ; HALF_EVEN (arrondi bancaire) limite les biais cumulés sur de grands volumes.

java
BigDecimal frais = montant.multiply(new BigDecimal("0.015")).setScale(0, RoundingMode.HALF_EVEN); // XOF : pas de centimes
new BigDecimal(0.1);       // ❌ 0.1000000000000000055511151231257827…
new BigDecimal("0.1");     // ✅ valeur exacte
Mobile moneyPortefeuilles électroniques liés à un numéro de téléphone (Orange Money, Wave, Free Money) très utilisés en Afrique de l'Ouest ; une banque s'y interface par API pour les dépôts, retraits et transferts wallet ↔ compte.

Portefeuilles électroniques liés à un numéro de téléphone (Orange Money, Wave, Free Money) très utilisés en Afrique de l'Ouest ; une banque s'y interface par API pour les dépôts, retraits et transferts wallet ↔ compte.

Enjeux techniques : callbacks asynchrones (webhooks) à sécuriser, idempotence, statuts intermédiaires, réconciliation quotidienne, gestion des timeouts des opérateurs.

ISO 20022Norme internationale de messages financiers (XML structuré : pacs.008 pour un virement, camt.053 pour un relevé) qui remplace progressivement les anciens formats SWIFT MT.

Norme internationale de messages financiers (XML structuré : pacs.008 pour un virement, camt.053 pour un relevé) qui remplace progressivement les anciens formats SWIFT MT.

3Principes de conception11

DRY, KISS, YAGNIDRY (Don't Repeat Yourself) : une connaissance à un seul endroit. KISS (Keep It Simple) : la solution la plus simple qui marche. YAGNI (You Aren't Gonna Need It) : ne pas construire ce qui n'est pas demandé.

DRY (Don't Repeat Yourself) : une connaissance à un seul endroit. KISS (Keep It Simple) : la solution la plus simple qui marche. YAGNI (You Aren't Gonna Need It) : ne pas construire ce qui n'est pas demandé.

⚠️ PiègeDRY appliqué trop tôt crée de mauvaises abstractions : deux codes qui se ressemblent aujourd'hui mais évolueront différemment peuvent rester séparés.
Design patternSolution éprouvée et nommée à un problème de conception récurrent (catalogue du « Gang of Four ») ; donne un vocabulaire commun à l'équipe.

Solution éprouvée et nommée à un problème de conception récurrent (catalogue du « Gang of Four ») ; donne un vocabulaire commun à l'équipe.

SingletonUne seule instance d'une classe dans l'application. En Spring, les beans sont singletons par défaut : on n'écrit plus le pattern à la main (constructeur privé + getInstance).

Une seule instance d'une classe dans l'application. En Spring, les beans sont singletons par défaut : on n'écrit plus le pattern à la main (constructeur privé + getInstance).

⚠️ PiègeUn singleton fait à la main (static) est un état global difficile à tester ; laisser Spring gérer le cycle de vie.
FactoryDéléguer la création d'objets à une méthode ou une classe dédiée, qui choisit l'implémentation selon le contexte ; l'appelant ne fait plus de new sur des classes concrètes.

Déléguer la création d'objets à une méthode ou une classe dédiée, qui choisit l'implémentation selon le contexte ; l'appelant ne fait plus de new sur des classes concrètes.

java
@Component
class PaymentGatewayFactory {
    private final Map<String, PaymentGateway> gateways;            // Spring injecte toutes les implémentations
    PaymentGatewayFactory(List<PaymentGateway> liste) {
        this.gateways = liste.stream().collect(Collectors.toMap(PaymentGateway::code, g -> g));
    }
    PaymentGateway pour(String operateur) {
        return Optional.ofNullable(gateways.get(operateur)).orElseThrow(() -> new OperateurInconnuException(operateur));
    }
}
BuilderConstruire pas à pas un objet complexe avec des méthodes nommées, au lieu d'un constructeur à 10 paramètres ; utilisé partout dans Spring (RestClient.builder(), ResponseEntity.ok()...).

Construire pas à pas un objet complexe avec des méthodes nommées, au lieu d'un constructeur à 10 paramètres ; utilisé partout dans Spring (RestClient.builder(), ResponseEntity.ok()...).

java
Releve r = Releve.builder()
        .compte("SN01…")
        .periode(YearMonth.of(2026, 9))
        .format(Format.PDF)
        .avecDetailFrais(true)
        .build();
StrategyEncapsuler des algorithmes interchangeables derrière une même interface et choisir lequel utiliser à l'exécution (calcul de frais par type d'opération, scoring par produit).

Encapsuler des algorithmes interchangeables derrière une même interface et choisir lequel utiliser à l'exécution (calcul de frais par type d'opération, scoring par produit).

java
interface CalculFrais { BigDecimal calculer(Operation op); }
class FraisVirementInterne implements CalculFrais { … }
class FraisMobileMoney implements CalculFrais { … }
// le service reçoit la stratégie adaptée, sans if/else en cascade
ObserverUn sujet notifie automatiquement ses abonnés quand son état change ; base des événements Spring, de RxJS et du pub/sub.

Un sujet notifie automatiquement ses abonnés quand son état change ; base des événements Spring, de RxJS et du pub/sub.

java
public record CompteOuvert(Long compteId) {}
publisher.publishEvent(new CompteOuvert(id));                // émetteur

@EventListener
void envoyerKitBienvenue(CompteOuvert e) { … }               // abonné découplé
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
void indexer(CompteOuvert e) { … }                           // seulement si la transaction a été validée
Template MethodUne classe abstraite fixe les étapes d'un algorithme et laisse les sous-classes implémenter certaines étapes (voir Classe abstraite). JdbcTemplate et RestTemplate en sont des variantes par callback.

Une classe abstraite fixe les étapes d'un algorithme et laisse les sous-classes implémenter certaines étapes (voir Classe abstraite). JdbcTemplate et RestTemplate en sont des variantes par callback.

DecoratorEnvelopper un objet pour lui ajouter un comportement (cache, log, retry) sans modifier sa classe, en respectant la même interface ; les proxies Spring en sont une application.

Envelopper un objet pour lui ajouter un comportement (cache, log, retry) sans modifier sa classe, en respectant la même interface ; les proxies Spring en sont une application.

ADR (Architecture Decision Record)Court document versionné avec le code qui trace une décision d'architecture : contexte, options étudiées, décision, conséquences. Évite de rejouer les mêmes débats et aide les nouveaux arrivants.

Court document versionné avec le code qui trace une décision d'architecture : contexte, options étudiées, décision, conséquences. Évite de rejouer les mêmes débats et aide les nouveaux arrivants.

text
ADR-007 : Kafka pour les événements de virement
Statut   : Accepté (2026-03-12)
Contexte : 6 services doivent réagir aux virements ; besoin de relecture et d'audit.
Options  : RabbitMQ, Kafka, appels REST synchrones.
Décision : Kafka (rétention 7 j, partition par compte), Outbox côté producteur.
Conséquences : exploitation d'un cluster Kafka, consommateurs idempotents obligatoires.
Dette techniqueCoût futur créé par des raccourcis (code dupliqué, absence de tests, dépendances obsolètes) : comme une dette financière, elle génère des « intérêts » (lenteur, bugs) tant qu'on ne la rembourse pas.

Coût futur créé par des raccourcis (code dupliqué, absence de tests, dépendances obsolètes) : comme une dette financière, elle génère des « intérêts » (lenteur, bugs) tant qu'on ne la rembourse pas.

🎯 En entretienPosture de tech lead : la rendre visible (backlog, métriques Sonar), la prioriser avec le métier en parlant d'impact (délais, incidents), réserver une part régulière de chaque sprint.

4Leadership technique5

MentoratAccompagner la montée en compétence des développeurs : objectifs individuels, revues de code pédagogiques, pair programming, partage de lectures, autonomie progressive.

Accompagner la montée en compétence des développeurs : objectifs individuels, revues de code pédagogiques, pair programming, partage de lectures, autonomie progressive.

Pair programmingDeux développeurs sur le même code : un « pilote » écrit, un « navigateur » relit et réfléchit à la suite, en alternant. Efficace sur les sujets complexes et pour former.

Deux développeurs sur le même code : un « pilote » écrit, un « navigateur » relit et réfléchit à la suite, en alternant. Efficace sur les sujets complexes et pour former.

Definition of DoneListe partagée des critères qu'une tâche doit remplir pour être considérée terminée : code revu, tests verts, couverture, documentation, déployé en recette, critères d'acceptation validés.

Liste partagée des critères qu'une tâche doit remplir pour être considérée terminée : code revu, tests verts, couverture, documentation, déployé en recette, critères d'acceptation validés.

Rituels agiles (Scrum)Cadre itératif : sprint (1 à 4 semaines), planning, daily (point quotidien de synchronisation), revue (démo au métier), rétrospective (amélioration du fonctionnement de l'équipe).

Cadre itératif : sprint (1 à 4 semaines), planning, daily (point quotidien de synchronisation), revue (démo au métier), rétrospective (amélioration du fonctionnement de l'équipe).

Gérer un désaccord technique (POC)Argumenter avec des faits (mesures, impacts métier, risques), écouter les contre-arguments, et si le désaccord persiste, trancher par un POC court et mesurable ; documenter la décision (ADR) et s'engager collectivement (« disagree and commit »).

Argumenter avec des faits (mesures, impacts métier, risques), écouter les contre-arguments, et si le désaccord persiste, trancher par un POC court et mesurable ; documenter la décision (ADR) et s'engager collectivement (« disagree and commit »).

📋 Synthèse express — Mémo

⚡ Questions pièges fréquentes

== vs equals() en Java

== : comparaison de référence. equals() : comparaison de valeur. Toujours implémenter hashCode() si on override equals().

@Transactional propagation

REQUIRED (défaut) = rejoint la transaction existante. REQUIRES_NEW = nouvelle transaction. Rollback par défaut sur RuntimeException uniquement.

StringBuilder vs String

String est immutable → concaténation dans une boucle = N objets créés. StringBuilder est mutable → O(n) vs O(n²). StringBuffer = synchronized.

REST : codes HTTP

200 OK · 201 Created · 204 No Content · 400 Bad Request · 401 Unauthorized · 403 Forbidden · 404 Not Found · 409 Conflict · 500 Internal Error

🎯 Présentation STAR — Template

Situation → Task → Action → Résultat
"Sur le projet X, j'ai dû [Situation]. Ma mission était [Task]. J'ai [Actions concrètes]. Résultat : [Métriques chiffrées]."

📋 Checklist avant entretien

J-3 : Révision techno
  • Java 21/25 nouveautés
  • Spring Boot 3.5/4.0
  • Architecture microservices
  • SOLID + Design Patterns
J-1 : Cas pratiques
  • API CRUD complète
  • Diagnostiquer N+1
  • Circuit Breaker setup
  • JWT OAuth2 flow
Jour J : Soft skills
  • Présentation STAR préparée
  • Questions à poser (roadmap, stack, équipe)
  • Finance/banking: Core Banking, KYC, risk
  • Leadership : mentorat, code review

🏦 Contexte Finance/Banking (Baobab)

Mots-clés à mentionner : Core Banking System, KYC (Know Your Customer), AML (Anti-Money Laundering), transactions ACID, audit trail, idempotence des paiements, BigDecimal (jamais float/double pour argent), reconciliation, regulatory compliance.

❓ Questions à poser au recruteur

📐 Architecture
  • Quelle taille du système ? Nb de microservices ?
  • Monolith → micro migration en cours ?
  • Spring Boot 3.x ou encore 2.x ?
👥 Équipe & Process
  • Taille de l'équipe tech ?
  • Rituels agiles (sprints de combien) ?
  • Code review obligatoire ?
  • Dette technique principale ?

💼 Exercices pratiques types

A — API CRUD sécurisée

Concevoir un endpoint POST /api/virements avec validation (@Valid), vérification du solde, transaction ACID, audit trail, réponse 201 avec idempotency-key. Mentionner BigDecimal.

B — Diagnostiquer N+1

Détecter via les logs Hibernate (show_sql), corriger avec @EntityGraph ou JOIN FETCH, valider avec les métriques de requêtes. Expliquer la différence LAZY/EAGER.

C — Circuit Breaker Resilience4j

Configurer states CLOSED/OPEN/HALF-OPEN, seuils d'erreur, timeout, fallback. Montrer l'annotation @CircuitBreaker(name="payment", fallbackMethod="fallback").

D — JWT OAuth2 Flow

1. Client → /login → JWT signé. 2. Client envoie Bearer token. 3. Spring Security valide signature. 4. Claims extraits → SecurityContext. Mentionner refresh token et expiration.

E — Optimisation requête lente

EXPLAIN ANALYZE → identifier seq scan → ajouter index composite → vérifier statistiques → VACUUM ANALYZE → mesurer amélioration. Mentionner index partiel pour tables volumineuses.

🗣️ Questions comportementales — Réponses STAR

« Donnez un exemple de problème technique résolu »

S : Performance dégradée en production (temps de réponse 8s). T : Réduire à moins de 500ms. A : Profiling → requête N+1 identifiée → JOIN FETCH + cache Redis. R : 95% de réduction, 0 incident en 3 mois.

« Comment gérez-vous les désaccords techniques ? »

J'expose mes arguments avec des données (benchmarks, impact métier). J'écoute les contre-arguments. Si toujours en désaccord, je propose un POC pour décider objectivement. Je documente la décision finale.

« Décrivez votre approche du mentorat »

Code reviews bienveillantes avec explications (pas juste "à corriger"). Sessions de pair-programming sur les sujets complexes. Documentation des décisions architecturales (ADR). Encourager les junior à poser des questions.

« Quelle est votre plus grande erreur technique ? »

Choisir un pattern trop complexe (CQRS) pour un petit projet. Résultat : délais + frustration équipe. Leçon : toujours choisir la solution la plus simple qui répond au besoin (KISS/YAGNI).

✅ Checklist complète avant entretien

QuandActionFait ?
J-7Réviser Java 21/25 (Virtual Threads, Records, Sealed Classes)☐
J-7Réviser Spring Boot 3.5 → 4.x (Jackson 3, versioning d'API, JSpecify, virtual threads)☐
J-5Pratiquer conception API REST + sécurité JWT☐
J-5Revoir Angular 17 → 22 (Signals, @defer, zoneless, Signal Forms)☐
J-3Préparer 3 réponses STAR (performance, conflit, leadership)☐
J-3Réviser SOLID + Design Patterns (Factory, Strategy, Observer)☐
J-2Lire les docs Baobab / fintech / Core Banking☐
J-1Faire les exercices A à E de cette fiche☐
J-1Préparer 5 questions à poser au recruteur☐
J-1Réviser microservices : Circuit Breaker, Kafka, Idempotence☐
Jour JPrésentation 3 min : parcours + spécialité + valeur ajoutée☐
Jour JEnvironnement de code prêt (IDE, Java 21 installé)☐
Glossaire A→Z
Toutes les notions du cours, triées par ordre alphabétique. Clique sur un nom pour ouvrir sa fiche complète.