Étiquette : Computers

  • Socle V004 – Dépannage

    Socle V004 – Dépannage

    18 – Troubleshooting

    Version : 4.0.0 Date : 2025-12-09

    1. Problèmes de démarrage

    1.1 Application ne démarre pas

    Symptôme : L’application ne démarre pas ou crashe immédiatement.

    Causes possibles :

    1. Port déjà utilisé

      Error: Address already in use: bind
      

      Solution :

      # Trouver le processus
      lsof -i :8080
      # Changer le port
      export HTTP_PORT=8081
      
    2. Configuration manquante

      Failed to bind properties under 'socle.xxx'
      

      Solution : Vérifier les variables d’environnement et application.yml

    3. Erreur de logging

      ERROR StatusLogger Log4j2 could not find a logging implementation
      

      Solution : Vérifier que log4j2.xml existe dans src/main/resources/

    1.2 Workers ne démarrent pas

    Symptôme : Les workers sont enregistrés mais restent en état REGISTERED.

    Solutions :

    1. Vérifier les logs d’initialisation
    2. Vérifier les dépendances (base de données, Redis, etc.)
    3. Vérifier les priorités de démarrage
    curl http://localhost:8080/admin/workers
    

    2. Problèmes de connectivité

    2.1 Redis non accessible

    Symptôme :

    Cannot get Jedis connection
    

    Solutions :

    1. Vérifier l’hôte et le port Redis
      redis-cli -h localhost -p 6379 ping
      
    2. Vérifier le mot de passe
    3. Vérifier les firewalls

    2.2 Database non accessible

    Symptôme :

    Database may be already in use: "locked by another process"
    

    Solutions :

    1. Arrêter les autres instances
    2. Utiliser AUTO_SERVER=TRUE dans l’URL H2
    3. Supprimer les fichiers de lock
    rm ./data/socle-techdb.lock.db
    

    3. Problèmes de logging (V4)

    3.1 Logs non visibles

    Symptôme : Aucun log n’apparaît.

    Solutions :

    1. Vérifier que log4j2.xml existe
    2. Vérifier le niveau de log
    3. Vérifier logging.config dans application.yml
    logging:
      config: classpath:log4j2.xml
    

    3.2 Conflit Logback/Log4j2

    Symptôme :

    SLF4J: Class path contains multiple SLF4J bindings
    

    Solution : Exclure Logback de toutes les dépendances

    mvn dependency:tree | grep logback
    
    <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
    

    3.3 AsyncLoggers non actifs

    Symptôme : Performance de logging dégradée.

    Solution : Vérifier log4j2.component.properties

    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    

    3.4 LogForwarder queue pleine

    Symptôme :

    WARN - Log queue full, storing to fallback
    

    Solutions :

    1. Augmenter queue-capacity
    2. Vérifier la connectivité réseau vers le LogHub
    3. Réduire le volume de logs
    # Vérifier les logs en fallback
    curl http://localhost:8080/admin/logforwarder/status
    

    4. Problèmes de performance

    4.1 Haute consommation CPU

    Solutions :

    1. Vérifier les workers en boucle infinie
    2. Vérifier les logs en DEBUG
    3. Profiler avec JFR
    jcmd <pid> JFR.start duration=60s filename=recording.jfr
    

    4.2 Haute consommation mémoire

    Solutions :

    1. Analyser le heap dump
      jmap -dump:format=b,file=heap.hprof <pid>
      
    2. Vérifier les fuites dans KvBus/SharedDataRegistry
    3. Ajuster la configuration JVM

    4.3 Latence élevée

    Solutions :

    1. Vérifier les circuit breakers
      curl http://localhost:8080/admin/resilience/circuits
      
    2. Vérifier les connexions réseau
    3. Vérifier les métriques
      curl http://localhost:8080/actuator/prometheus | grep latency
      

    5. Problèmes de résilience

    5.1 Circuit Breaker bloqué en OPEN

    Symptôme : Un circuit reste ouvert malgré la récupération du service.

    Solutions :

    1. Reset manuel
      curl -X POST http://localhost:8080/admin/resilience/circuits/my-circuit/reset
      
    2. Vérifier le timeout configuré
    3. Vérifier que le service cible répond

    5.2 Retry infini

    Symptôme : L’application retry sans fin.

    Solutions :

    1. Vérifier max-attempts configuré
    2. Vérifier les exceptions retryables
    3. Ajouter un circuit breaker

    6. Problèmes H2 TechDB (V4)

    6.1 Base corrompue

    Symptôme :

    File corrupted
    

    Solution :

    rm -rf ./data/socle-techdb.*
    # Redémarrer l'application
    

    6.2 H2 Console inaccessible

    Solutions :

    1. Vérifier socle.techdb.console.enabled=true
    2. Vérifier l’URL : http://localhost:8080/h2-console
    3. Utiliser l’URL JDBC correcte : jdbc:h2:file:./data/socle-techdb

    6.3 Offsets perdus

    Solutions :

    1. Vérifier que TechDB est enabled
    2. Vérifier les logs de saveOffset
    3. Requêter directement H2
      SELECT * FROM socle_offsets;
      

    7. Problèmes d’authentification (V4)

    7.1 Login échoue

    Symptôme :

    AuthenticationException: Login failed: 401
    

    Solutions :

    1. Vérifier API_KEY
    2. Vérifier SOURCE_NAME
    3. Vérifier AUTH_SERVER_URL

    7.2 Token expiré

    Symptôme :

    Token expired
    

    Solutions :

    1. Vérifier l’horloge système (NTP)
    2. Augmenter access-token-buffer-seconds

    7.3 Admin API 401

    Solutions :

    1. Vérifier si l’auth est activée
      socle.admin.auth.enabled: true
      
    2. Utiliser les credentials corrects
      curl -u admin:password http://localhost:8080/admin/workers
      

    8. Problèmes Kubernetes

    8.1 Pod en CrashLoopBackOff

    Solutions :

    1. Vérifier les logs
      kubectl logs <pod-name> --previous
      
    2. Vérifier les ressources
    3. Vérifier les probes

    8.2 Probes échouent

    Solutions :

    1. Augmenter initialDelaySeconds
    2. Vérifier que l’endpoint /admin/health répond
    3. Vérifier le port

    8.3 OOMKilled

    Solutions :

    1. Augmenter les limites mémoire
    2. Ajuster les options JVM
      -XX:MaxRAMPercentage=75.0
      

    9. Commandes de diagnostic

    9.1 API Admin

    # Santé globale
    curl http://localhost:8080/admin/health
    
    # État des workers
    curl http://localhost:8080/admin/workers
    
    # Registry
    curl http://localhost:8080/admin/registry
    
    # Circuits breakers
    curl http://localhost:8080/admin/resilience/circuits
    
    # Configuration
    curl http://localhost:8080/admin/config
    
    # Métriques
    curl http://localhost:8080/actuator/prometheus
    

    9.2 JVM

    # Thread dump
    jstack <pid>
    
    # Heap info
    jmap -heap <pid>
    
    # GC stats
    jstat -gcutil <pid> 1000
    
    # JFR recording
    jcmd <pid> JFR.start duration=60s filename=recording.jfr
    

    9.3 Réseau

    # Test Redis
    redis-cli -h localhost ping
    
    # Test HTTP
    curl -v http://localhost:8080/admin/health
    
    # DNS
    nslookup myservice.namespace.svc.cluster.local
    

    10. Logs utiles à activer

    # application.yml ou variables d'environnement
    
    logging:
      level:
        eu.lmvi.socle: DEBUG
        eu.lmvi.socle.mop: DEBUG
        eu.lmvi.socle.supervisor: DEBUG
        eu.lmvi.socle.techdb: DEBUG
        eu.lmvi.socle.resilience: DEBUG
        org.springframework.web: DEBUG
        io.lettuce: DEBUG  # Redis
    

    11. Checklist de diagnostic

    □ L'application démarre-t-elle ?
      □ Logs de démarrage présents ?
      □ Port disponible ?
      □ Configuration valide ?
    
    □ Les workers sont-ils healthy ?
      □ GET /admin/workers
      □ Heartbeats reçus ?
      □ Erreurs dans les logs ?
    
    □ Les connexions externes fonctionnent-elles ?
      □ Redis accessible ?
      □ Base de données accessible ?
      □ APIs externes accessibles ?
    
    □ Les métriques sont-elles normales ?
      □ CPU < 80% ?
      □ Mémoire < 80% ?
      □ Latence acceptable ?
      □ Taux d'erreur bas ?
    
    □ Les logs sont-ils corrects ?
      □ Log4j2 configuré ?
      □ LogForwarder fonctionne ?
      □ Pas de logs en fallback ?
    

    12. Références

  • Socle V004 – Introduction

    Socle V004 – Introduction

    01 – Introduction au Socle V4

    Version : 4.0.0 Date : 2025-01-25

    1. Qu’est-ce que le Socle V4 ?

    Le Socle V4 est un framework Java de grade production construit sur Spring Boot 3.2.1 qui implémente le pattern MOP (Main Orchestrator Process). Il fournit une base solide pour construire des applications d’entreprise robustes et observables.

    Évolution depuis V3

    Le Socle V4 conserve et étend l’architecture V3 en ajoutant :

    Nouveauté V4 Description
    H2 TechDB Base embarquée pour état technique (remplace Nitrite)
    Log4j2 Framework logging haute performance (remplace Logback)
    LogForwarder Centralisation des logs vers LogHub (HTTP/NATS)
    SocleAuthClient Client authentification JWT
    WorkerRegistryClient Auto-enregistrement des workers
    StatusDashboard Dashboard HTML de supervision temps réel (port 9374)
    Pipeline V2 Pipeline asynchrone avec garantie at-least-once (Queue/Claim/Ack)

    2. Philosophie « MOP Pilote Tout »

    Le Main Orchestrator Process est le cœur du framework :

    ┌─────────────────────────────────────────────────────────────────┐
    │                           MOP                                    │
    │  - Orchestre tous les Workers                                   │
    │  - Gère le lifecycle (start/stop)                               │
    │  - Appelle doWork() automatiquement                             │
    │  - Garantit le shutdown gracieux                                │
    └─────────────────────────────────────────────────────────────────┘
                                  │
             ┌────────────────────┼────────────────────┐
             ▼                    ▼                    ▼
        ┌─────────┐         ┌─────────┐         ┌─────────┐
        │ Worker  │         │ Worker  │         │  HTTP   │
        │ Métier  │         │ Métier  │         │ Worker  │
        └─────────┘         └─────────┘         └─────────┘
    

    Principes clés

    1. Orchestration centralisée : Le MOP contrôle tout le lifecycle
    2. Démarrage ordonné : Workers par priorité (petit → grand), HTTP en dernier
    3. Arrêt gracieux : HTTP d’abord (drain), puis Workers
    4. Scheduling automatique : doWork() appelé selon cron ou interval

    3. Les 4 principes fondamentaux V4

    3.1 Portabilité

    • Fonctionne sur ARM/AMD64, Linux/macOS
    • Aucune dépendance serveur externe obligatoire
    • Base H2 embarquée pour l’état technique

    3.2 Sécurité

    • Aucun port entrant sur les NUC/agents
    • Communication sortante uniquement (HTTP/NATS)
    • Authentification JWT pour les services centraux

    3.3 Observabilité

    • Logs centralisés via LogForwarder
    • Corrélation par correlationId / execId
    • Suivi des workers via Registry

    3.4 Standardisation

    • Même authentification partout
    • Même format de logs
    • Même enregistrement des workers

    4. Stack technique

    Composant Version Usage
    Java 21 LTS Runtime
    Spring Boot 3.2.1 Framework
    Log4j2 2.22.1 Logging (nouveau V4)
    LMAX Disruptor 4.0.0 AsyncLoggers
    H2 2.2.x Base technique embarquée (nouveau V4)
    Kafka 3.6.0 Messaging
    NATS 2.17.0 Messaging
    Redisson 3.24.3 Redis client
    OkHttp 4.12.0 HTTP client
    Micrometer 1.12.0 Metrics

    5. Composants du Socle

    Composants V3 (conservés)

    Package Description
    mop Main Orchestrator Process
    worker Interface Worker
    config SocleConfiguration
    kv KvBus (in_memory / Redis)
    shared SharedDataRegistry
    supervisor Supervision heartbeats
    http HttpWorker, TomcatManager
    admin AdminRestApi
    metrics SocleMetrics
    pipeline PipelineEngine
    resilience CircuitBreaker, Retry
    scheduler WorkerScheduler
    security AdminAuthFilter, RateLimit

    Nouveaux composants V4

    Package Description
    techdb H2 TechDB Manager
    logging Log4j2 + LogForwarder
    client/auth SocleAuthClient
    client/registry WorkerRegistryClient

    6. Cas d’usage

    Le Socle V4 est idéal pour :

    • Agents de collecte (DB2 Journal Reader, CDC)
    • Services de synchronisation (ODH-sync)
    • Proxies et bridges (Kafka Proxy)
    • Workers de traitement (ETL, pipelines)
    • Services multi-région (MTQ, GUA, REU, etc.)

    7. Prérequis

    Développement

    • JDK 21+
    • Maven 3.9+
    • IDE (IntelliJ IDEA recommandé)

    Production

    • JRE 21+
    • Docker (optionnel)
    • Accès NATS ou HTTP pour LogForwarder (optionnel)

    8. Premiers pas

    # Cloner le projet
    git clone <repo>/socle-v004.git
    
    # Build
    cd socle-v004
    mvn clean package -DskipTests
    
    # Run
    java -jar target/socle-v004-4.0.0.jar
    
    # Vérifier
    curl http://localhost:8080/health
    

    9. Documentation

    Document Description
    02-ARCHITECTURE Architecture détaillée
    03-QUICKSTART Guide de démarrage
    08-SUPERVISOR Supervision et heartbeats
    09-PIPELINE Pipeline V1 et V2
    21-H2-TECHDB Base H2 (V4)
    22-LOG4J2-LOGFORWARDER Logging V4
    25-MIGRATION-V3-V4 Migration
    27-STATUS-DASHBOARD Dashboard supervision
    GUIDE-METHODOLOGIQUE Bonnes pratiques

    10. Support

    • Issues : GitHub Issues
    • Documentation : Ce dossier docs/Help/
    • Exemples : 20-EXEMPLES
  • Socle V004 – Dépannage

    Socle V004 – Dépannage

    18 – Troubleshooting

    Version : 4.0.0 Date : 2025-12-09

    1. Problèmes de démarrage

    1.1 Application ne démarre pas

    Symptôme : L’application ne démarre pas ou crashe immédiatement.

    Causes possibles :

    1. Port déjà utilisé

      Error: Address already in use: bind
      

      Solution :

      # Trouver le processus
      lsof -i :8080
      # Changer le port
      export HTTP_PORT=8081
      
    2. Configuration manquante

      Failed to bind properties under 'socle.xxx'
      

      Solution : Vérifier les variables d’environnement et application.yml

    3. Erreur de logging

      ERROR StatusLogger Log4j2 could not find a logging implementation
      

      Solution : Vérifier que log4j2.xml existe dans src/main/resources/

    1.2 Workers ne démarrent pas

    Symptôme : Les workers sont enregistrés mais restent en état REGISTERED.

    Solutions :

    1. Vérifier les logs d’initialisation
    2. Vérifier les dépendances (base de données, Redis, etc.)
    3. Vérifier les priorités de démarrage
    curl http://localhost:8080/admin/workers
    

    2. Problèmes de connectivité

    2.1 Redis non accessible

    Symptôme :

    Cannot get Jedis connection
    

    Solutions :

    1. Vérifier l’hôte et le port Redis
      redis-cli -h localhost -p 6379 ping
      
    2. Vérifier le mot de passe
    3. Vérifier les firewalls

    2.2 Database non accessible

    Symptôme :

    Database may be already in use: "locked by another process"
    

    Solutions :

    1. Arrêter les autres instances
    2. Utiliser AUTO_SERVER=TRUE dans l’URL H2
    3. Supprimer les fichiers de lock
    rm ./data/socle-techdb.lock.db
    

    3. Problèmes de logging (V4)

    3.1 Logs non visibles

    Symptôme : Aucun log n’apparaît.

    Solutions :

    1. Vérifier que log4j2.xml existe
    2. Vérifier le niveau de log
    3. Vérifier logging.config dans application.yml
    logging:
      config: classpath:log4j2.xml
    

    3.2 Conflit Logback/Log4j2

    Symptôme :

    SLF4J: Class path contains multiple SLF4J bindings
    

    Solution : Exclure Logback de toutes les dépendances

    mvn dependency:tree | grep logback
    
    <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
    

    3.3 AsyncLoggers non actifs

    Symptôme : Performance de logging dégradée.

    Solution : Vérifier log4j2.component.properties

    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    

    3.4 LogForwarder queue pleine

    Symptôme :

    WARN - Log queue full, storing to fallback
    

    Solutions :

    1. Augmenter queue-capacity
    2. Vérifier la connectivité réseau vers le LogHub
    3. Réduire le volume de logs
    # Vérifier les logs en fallback
    curl http://localhost:8080/admin/logforwarder/status
    

    4. Problèmes de performance

    4.1 Haute consommation CPU

    Solutions :

    1. Vérifier les workers en boucle infinie
    2. Vérifier les logs en DEBUG
    3. Profiler avec JFR
    jcmd <pid> JFR.start duration=60s filename=recording.jfr
    

    4.2 Haute consommation mémoire

    Solutions :

    1. Analyser le heap dump
      jmap -dump:format=b,file=heap.hprof <pid>
      
    2. Vérifier les fuites dans KvBus/SharedDataRegistry
    3. Ajuster la configuration JVM

    4.3 Latence élevée

    Solutions :

    1. Vérifier les circuit breakers
      curl http://localhost:8080/admin/resilience/circuits
      
    2. Vérifier les connexions réseau
    3. Vérifier les métriques
      curl http://localhost:8080/actuator/prometheus | grep latency
      

    5. Problèmes de résilience

    5.1 Circuit Breaker bloqué en OPEN

    Symptôme : Un circuit reste ouvert malgré la récupération du service.

    Solutions :

    1. Reset manuel
      curl -X POST http://localhost:8080/admin/resilience/circuits/my-circuit/reset
      
    2. Vérifier le timeout configuré
    3. Vérifier que le service cible répond

    5.2 Retry infini

    Symptôme : L’application retry sans fin.

    Solutions :

    1. Vérifier max-attempts configuré
    2. Vérifier les exceptions retryables
    3. Ajouter un circuit breaker

    6. Problèmes H2 TechDB (V4)

    6.1 Base corrompue

    Symptôme :

    File corrupted
    

    Solution :

    rm -rf ./data/socle-techdb.*
    # Redémarrer l'application
    

    6.2 H2 Console inaccessible

    Solutions :

    1. Vérifier socle.techdb.console.enabled=true
    2. Vérifier l’URL : http://localhost:8080/h2-console
    3. Utiliser l’URL JDBC correcte : jdbc:h2:file:./data/socle-techdb

    6.3 Offsets perdus

    Solutions :

    1. Vérifier que TechDB est enabled
    2. Vérifier les logs de saveOffset
    3. Requêter directement H2
      SELECT * FROM socle_offsets;
      

    7. Problèmes d’authentification (V4)

    7.1 Login échoue

    Symptôme :

    AuthenticationException: Login failed: 401
    

    Solutions :

    1. Vérifier API_KEY
    2. Vérifier SOURCE_NAME
    3. Vérifier AUTH_SERVER_URL

    7.2 Token expiré

    Symptôme :

    Token expired
    

    Solutions :

    1. Vérifier l’horloge système (NTP)
    2. Augmenter access-token-buffer-seconds

    7.3 Admin API 401

    Solutions :

    1. Vérifier si l’auth est activée
      socle.admin.auth.enabled: true
      
    2. Utiliser les credentials corrects
      curl -u admin:password http://localhost:8080/admin/workers
      

    8. Problèmes Kubernetes

    8.1 Pod en CrashLoopBackOff

    Solutions :

    1. Vérifier les logs
      kubectl logs <pod-name> --previous
      
    2. Vérifier les ressources
    3. Vérifier les probes

    8.2 Probes échouent

    Solutions :

    1. Augmenter initialDelaySeconds
    2. Vérifier que l’endpoint /admin/health répond
    3. Vérifier le port

    8.3 OOMKilled

    Solutions :

    1. Augmenter les limites mémoire
    2. Ajuster les options JVM
      -XX:MaxRAMPercentage=75.0
      

    9. Commandes de diagnostic

    9.1 API Admin

    # Santé globale
    curl http://localhost:8080/admin/health
    
    # État des workers
    curl http://localhost:8080/admin/workers
    
    # Registry
    curl http://localhost:8080/admin/registry
    
    # Circuits breakers
    curl http://localhost:8080/admin/resilience/circuits
    
    # Configuration
    curl http://localhost:8080/admin/config
    
    # Métriques
    curl http://localhost:8080/actuator/prometheus
    

    9.2 JVM

    # Thread dump
    jstack <pid>
    
    # Heap info
    jmap -heap <pid>
    
    # GC stats
    jstat -gcutil <pid> 1000
    
    # JFR recording
    jcmd <pid> JFR.start duration=60s filename=recording.jfr
    

    9.3 Réseau

    # Test Redis
    redis-cli -h localhost ping
    
    # Test HTTP
    curl -v http://localhost:8080/admin/health
    
    # DNS
    nslookup myservice.namespace.svc.cluster.local
    

    10. Logs utiles à activer

    # application.yml ou variables d'environnement
    
    logging:
      level:
        eu.lmvi.socle: DEBUG
        eu.lmvi.socle.mop: DEBUG
        eu.lmvi.socle.supervisor: DEBUG
        eu.lmvi.socle.techdb: DEBUG
        eu.lmvi.socle.resilience: DEBUG
        org.springframework.web: DEBUG
        io.lettuce: DEBUG  # Redis
    

    11. Checklist de diagnostic

    □ L'application démarre-t-elle ?
      □ Logs de démarrage présents ?
      □ Port disponible ?
      □ Configuration valide ?
    
    □ Les workers sont-ils healthy ?
      □ GET /admin/workers
      □ Heartbeats reçus ?
      □ Erreurs dans les logs ?
    
    □ Les connexions externes fonctionnent-elles ?
      □ Redis accessible ?
      □ Base de données accessible ?
      □ APIs externes accessibles ?
    
    □ Les métriques sont-elles normales ?
      □ CPU < 80% ?
      □ Mémoire < 80% ?
      □ Latence acceptable ?
      □ Taux d'erreur bas ?
    
    □ Les logs sont-ils corrects ?
      □ Log4j2 configuré ?
      □ LogForwarder fonctionne ?
      □ Pas de logs en fallback ?
    

    12. Références

  • Socle V004 – TLS/HTTPS

    Socle V004 – TLS/HTTPS

    13 – TLS/HTTPS

    Version : 4.0.0 Date : 2025-12-09

    1. Introduction

    Configuration du TLS/HTTPS pour sécuriser les communications HTTP du Socle V4.

    2. Configuration Spring Boot

    2.1 application.yml

    server:
      port: ${HTTPS_PORT:8443}
      ssl:
        enabled: ${SSL_ENABLED:true}
        key-store: ${SSL_KEYSTORE:classpath:keystore.p12}
        key-store-password: ${SSL_KEYSTORE_PASSWORD:changeit}
        key-store-type: ${SSL_KEYSTORE_TYPE:PKCS12}
        key-alias: ${SSL_KEY_ALIAS:socle}
    

    2.2 Variables d’environnement

    Variable Description Défaut
    SSL_ENABLED Activer SSL false
    SSL_KEYSTORE Chemin du keystore classpath:keystore.p12
    SSL_KEYSTORE_PASSWORD Mot de passe keystore changeit
    SSL_KEYSTORE_TYPE Type de keystore PKCS12
    SSL_KEY_ALIAS Alias de la clé socle

    3. Génération des certificats

    3.1 Certificat auto-signé (développement)

    # Générer un keystore PKCS12 avec certificat auto-signé
    keytool -genkeypair \
      -alias socle \
      -keyalg RSA \
      -keysize 2048 \
      -storetype PKCS12 \
      -keystore keystore.p12 \
      -validity 365 \
      -dname "CN=localhost,OU=Dev,O=MyCompany,L=Paris,C=FR" \
      -storepass changeit \
      -keypass changeit
    
    # Exporter le certificat (pour les clients)
    keytool -exportcert \
      -alias socle \
      -keystore keystore.p12 \
      -storetype PKCS12 \
      -storepass changeit \
      -file socle.crt
    

    3.2 Avec Let’s Encrypt (production)

    # Obtenir le certificat
    certbot certonly --standalone -d myapp.example.com
    
    # Convertir en PKCS12
    openssl pkcs12 -export \
      -in /etc/letsencrypt/live/myapp.example.com/fullchain.pem \
      -inkey /etc/letsencrypt/live/myapp.example.com/privkey.pem \
      -out keystore.p12 \
      -name socle \
      -passout pass:changeit
    

    3.3 Avec CA interne

    # Générer CSR
    keytool -certreq \
      -alias socle \
      -keystore keystore.p12 \
      -file socle.csr \
      -storepass changeit
    
    # Après signature par la CA, importer le certificat
    keytool -importcert \
      -alias socle \
      -keystore keystore.p12 \
      -file signed-cert.crt \
      -storepass changeit
    
    # Importer la chaîne CA
    keytool -importcert \
      -alias ca-root \
      -keystore keystore.p12 \
      -file ca-root.crt \
      -storepass changeit
    

    4. Configuration avancée

    4.1 Mutual TLS (mTLS)

    server:
      ssl:
        enabled: true
        key-store: ${SSL_KEYSTORE:keystore.p12}
        key-store-password: ${SSL_KEYSTORE_PASSWORD}
        key-store-type: PKCS12
        # Trust store pour vérifier les clients
        trust-store: ${SSL_TRUSTSTORE:truststore.p12}
        trust-store-password: ${SSL_TRUSTSTORE_PASSWORD}
        trust-store-type: PKCS12
        # Exiger certificat client
        client-auth: ${SSL_CLIENT_AUTH:need}  # none, want, need
    

    4.2 Protocoles et Ciphers

    server:
      ssl:
        enabled-protocols: TLSv1.3,TLSv1.2
        ciphers:
          - TLS_AES_256_GCM_SHA384
          - TLS_AES_128_GCM_SHA256
          - TLS_CHACHA20_POLY1305_SHA256
          - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
          - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    

    5. HTTP + HTTPS (dual port)

    5.1 Configuration

    @Configuration
    public class TlsConfiguration {
    
        @Value("${server.http.port:8080}")
        private int httpPort;
    
        @Bean
        public ServletWebServerFactory servletContainer() {
            TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
            tomcat.addAdditionalTomcatConnectors(createHttpConnector());
            return tomcat;
        }
    
        private Connector createHttpConnector() {
            Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
            connector.setScheme("http");
            connector.setPort(httpPort);
            connector.setSecure(false);
            return connector;
        }
    }
    

    5.2 Redirection HTTP → HTTPS

    @Configuration
    public class HttpsRedirectConfiguration {
    
        @Bean
        public TomcatServletWebServerFactory servletContainer() {
            TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
                @Override
                protected void postProcessContext(Context context) {
                    SecurityConstraint securityConstraint = new SecurityConstraint();
                    securityConstraint.setUserConstraint("CONFIDENTIAL");
                    SecurityCollection collection = new SecurityCollection();
                    collection.addPattern("/*");
                    securityConstraint.addCollection(collection);
                    context.addConstraint(securityConstraint);
                }
            };
    
            tomcat.addAdditionalTomcatConnectors(httpToHttpsRedirectConnector());
            return tomcat;
        }
    
        private Connector httpToHttpsRedirectConnector() {
            Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
            connector.setScheme("http");
            connector.setPort(8080);
            connector.setSecure(false);
            connector.setRedirectPort(8443);
            return connector;
        }
    }
    

    6. Client HTTPS

    6.1 OkHttpClient avec TLS

    @Configuration
    public class HttpClientConfiguration {
    
        @Value("${ssl.truststore:#{null}}")
        private Resource trustStore;
    
        @Value("${ssl.truststore-password:changeit}")
        private String trustStorePassword;
    
        @Bean
        public OkHttpClient secureHttpClient() throws Exception {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
    
            if (trustStore != null && trustStore.exists()) {
                KeyStore ks = KeyStore.getInstance("PKCS12");
                try (InputStream is = trustStore.getInputStream()) {
                    ks.load(is, trustStorePassword.toCharArray());
                }
    
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
                tmf.init(ks);
    
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
    
                builder.sslSocketFactory(sslContext.getSocketFactory(),
                    (X509TrustManager) tmf.getTrustManagers()[0]);
            }
    
            return builder
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();
        }
    }
    

    6.2 Bypass SSL pour développement (NON RECOMMANDÉ)

    // UNIQUEMENT POUR LE DÉVELOPPEMENT - NE PAS UTILISER EN PRODUCTION
    public OkHttpClient insecureClient() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType) {}
                public void checkServerTrusted(X509Certificate[] chain, String authType) {}
                public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
            }
        };
    
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new SecureRandom());
    
        return new OkHttpClient.Builder()
            .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0])
            .hostnameVerifier((hostname, session) -> true)
            .build();
    }
    

    7. Docker avec TLS

    7.1 Dockerfile

    FROM eclipse-temurin:21-jre
    
    WORKDIR /app
    
    # Copier le certificat
    COPY keystore.p12 /app/certs/keystore.p12
    
    # Copier l'application
    COPY target/socle-v004-4.0.0.jar app.jar
    
    ENV SSL_ENABLED=true
    ENV SSL_KEYSTORE=/app/certs/keystore.p12
    
    EXPOSE 8443
    
    ENTRYPOINT ["java", "-jar", "app.jar"]
    

    7.2 docker-compose.yml

    version: '3.8'
    
    services:
      socle-app:
        image: socle-v4:latest
        environment:
          - SSL_ENABLED=true
          - SSL_KEYSTORE=/app/certs/keystore.p12
          - SSL_KEYSTORE_PASSWORD_FILE=/run/secrets/ssl_password
        ports:
          - "8443:8443"
        volumes:
          - ./certs:/app/certs:ro
        secrets:
          - ssl_password
    
    secrets:
      ssl_password:
        file: ./secrets/ssl_password.txt
    

    8. Kubernetes avec TLS

    8.1 Secret pour le certificat

    apiVersion: v1
    kind: Secret
    metadata:
      name: socle-tls
    type: kubernetes.io/tls
    data:
      tls.crt: <base64-encoded-cert>
      tls.key: <base64-encoded-key>
    

    8.2 Ingress avec TLS

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: socle-ingress
      annotations:
        nginx.ingress.kubernetes.io/ssl-redirect: "true"
    spec:
      tls:
        - hosts:
            - socle.example.com
          secretName: socle-tls
      rules:
        - host: socle.example.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: socle-service
                    port:
                      number: 8080
    

    8.3 cert-manager

    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: socle-cert
    spec:
      secretName: socle-tls
      issuerRef:
        name: letsencrypt-prod
        kind: ClusterIssuer
      dnsNames:
        - socle.example.com
    

    9. Vérification

    9.1 Test avec curl

    # Test HTTPS
    curl -v https://localhost:8443/admin/health
    
    # Avec certificat client (mTLS)
    curl -v --cert client.crt --key client.key https://localhost:8443/admin/health
    
    # Ignorer la vérification (dev only)
    curl -vk https://localhost:8443/admin/health
    

    9.2 Test avec openssl

    # Vérifier le certificat du serveur
    openssl s_client -connect localhost:8443 -showcerts
    
    # Vérifier les protocoles supportés
    openssl s_client -connect localhost:8443 -tls1_3
    
    # Vérifier les ciphers
    openssl s_client -connect localhost:8443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'
    

    10. Troubleshooting

    Erreur: PKIX path building failed

    Le certificat du serveur n’est pas trusté.

    # Importer le certificat dans le truststore Java
    keytool -importcert \
      -alias server-cert \
      -file server.crt \
      -keystore $JAVA_HOME/lib/security/cacerts \
      -storepass changeit
    

    Erreur: Handshake failure

    Incompatibilité de protocole ou cipher.

    # Vérifier les protocoles
    openssl s_client -connect host:port -tls1_2
    openssl s_client -connect host:port -tls1_3
    

    Erreur: Certificate expired

    Renouveler le certificat et recréer le keystore.

    11. Bonnes pratiques

    DO

    • Utiliser TLS 1.2 minimum, TLS 1.3 recommandé
    • Renouveler les certificats avant expiration
    • Utiliser des clés RSA 2048 bits minimum ou ECDSA 256 bits
    • Activer HSTS en production
    • Utiliser cert-manager en Kubernetes

    DON’T

    • Ne pas utiliser de certificats auto-signés en production
    • Ne pas désactiver la vérification des certificats
    • Ne pas stocker les mots de passe en clair
    • Ne pas utiliser TLS 1.0 ou 1.1 (dépréciés)

    12. Références

  • Socle V004 – Guides Pratiques

    Socle V004 – Guides Pratiques

    17 – How-To Guides

    Version : 4.0.0 Date : 2025-12-09

    1. Comment créer un nouveau Worker

    1.1 Worker simple

    package com.myapp.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(MyWorker.class);
        private volatile boolean running = false;
    
        @Override
        public String getName() {
            return "my-worker";
        }
    
        @Override
        public void initialize() {
            log.info("[{}] Initializing", getName());
        }
    
        @Override
        public void start() {
            log.info("[{}] Starting", getName());
            running = true;
        }
    
        @Override
        public void doWork() {
            if (!running) return;
            // Votre logique ici
        }
    
        @Override
        public void stop() {
            log.info("[{}] Stopping", getName());
            running = false;
        }
    
        @Override
        public boolean isHealthy() {
            return running;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of("running", running);
        }
    }
    

    1.2 Worker avec priorité

    @Override
    public int getStartPriority() {
        return 10;  // Démarre en premier
    }
    
    @Override
    public int getStopPriority() {
        return 90;  // S'arrête en dernier
    }
    

    1.3 Worker schedulé

    @Override
    public String getSchedule() {
        return "0 0 6 * * ?";  // Tous les jours à 6h
    }
    
    @Override
    public boolean isScheduled() {
        return true;
    }
    

    2. Comment utiliser KvBus

    2.1 Opérations basiques

    @Service
    public class MyService {
    
        @Autowired
        private KvBus kvBus;
    
        public void example() {
            // Stocker
            kvBus.put("key", "value");
            kvBus.put("key-with-ttl", "value", Duration.ofHours(1));
    
            // Récupérer
            Optional<String> value = kvBus.get("key");
    
            // Supprimer
            kvBus.delete("key");
    
            // Compteur atomique
            long count = kvBus.increment("counter");
        }
    }
    

    2.2 JSON

    // Stocker un objet
    kvBus.putJson("order:123", order);
    
    // Récupérer un objet
    Optional<Order> order = kvBus.getJson("order:123", Order.class);
    

    2.3 Lock distribué

    public boolean tryLock(String resource) {
        return kvBus.putIfAbsent("lock:" + resource, "locked", Duration.ofMinutes(5));
    }
    
    public void unlock(String resource) {
        kvBus.delete("lock:" + resource);
    }
    

    3. Comment utiliser SharedDataRegistry

    3.1 Key-Value

    @Service
    public class MyService {
    
        @Autowired
        private SharedDataRegistry registry;
    
        public void example() {
            // Stocker avec niveau de santé
            registry.put("database.connected", true, HealthLevel.CRITICAL);
    
            // Récupérer
            boolean connected = registry.getBoolean("database.connected").orElse(false);
        }
    }
    

    3.2 Compteurs

    // Créer une séquence
    registry.createSequence("orders.processed", 0, HealthLevel.NORMAL);
    
    // Incrémenter
    long count = registry.incrementSequence("orders.processed");
    
    // Lire
    long total = registry.getSequence("orders.processed");
    

    4. Comment utiliser TechDB (V4)

    4.1 Offsets

    @Service
    public class MyService {
    
        @Autowired
        private TechDbManager techDb;
    
        public void example() {
            // Sauvegarder un offset
            techDb.saveOffset("kafka", "my-topic-0", 123456L, null);
    
            // Récupérer un offset
            OptionalLong offset = techDb.getOffset("kafka", "my-topic-0");
        }
    }
    

    4.2 État des workers

    // Sauvegarder l'état
    techDb.saveWorkerState("my-worker", "RUNNING", Map.of("progress", 50));
    
    // Récupérer l'état
    Optional<WorkerState> state = techDb.getWorkerState("my-worker");
    

    4.3 Événements techniques

    // Logger un événement
    techDb.logEvent("ERROR", Map.of(
        "message", "Connection failed",
        "target", "database"
    ));
    
    // Récupérer les événements
    List<TechEvent> events = techDb.getEvents("ERROR", Instant.now().minus(1, ChronoUnit.HOURS), 100);
    

    5. Comment implémenter un Pipeline

    5.1 Pipeline simple

    Pipeline<Order, ProcessedOrder> pipeline = PipelineBuilder
        .<Order, ProcessedOrder>create("order-processing")
        .addStep("validate", this::validateOrder)
        .addStep("enrich", this::enrichOrder)
        .addStep("process", this::processOrder)
        .build();
    
    PipelineResult<ProcessedOrder> result = pipelineEngine.execute(pipeline, order);
    

    5.2 Étape personnalisée

    public class ValidationStep implements PipelineStep<Order, ValidatedOrder> {
    
        @Override
        public String getName() {
            return "validation";
        }
    
        @Override
        public StepResult<ValidatedOrder> execute(Order input, PipelineContext context) {
            // Validation...
            return StepResult.success(getName(), new ValidatedOrder(input), Duration.ZERO);
        }
    
        @Override
        public boolean isRetryable() {
            return false;
        }
    }
    

    6. Comment configurer le logging (V4)

    6.1 Log4j2 basique

    <!-- src/main/resources/log4j2.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{ISO8601} %-5level [%thread] %logger{36} - %msg%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="INFO">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>
    

    6.2 Avec LogForwarder

    # application.yml
    socle:
      logging:
        forwarder:
          enabled: true
          transport-mode: http
          log-hub-url: https://logs.mycompany.com/api/ingest
    

    7. Comment utiliser l’authentification JWT (V4)

    7.1 Configuration

    socle:
      auth:
        enabled: true
        server-url: https://auth.mycompany.com
        api-key: ${API_KEY}
    

    7.2 Utilisation

    @Service
    public class SecuredService {
    
        @Autowired(required = false)
        private SocleAuthClient authClient;
    
        public void callSecuredApi() {
            if (authClient == null) {
                throw new IllegalStateException("Auth not configured");
            }
    
            String token = authClient.getValidAccessToken();
    
            // Utiliser le token dans les requêtes HTTP
            Request request = new Request.Builder()
                .url("https://api.mycompany.com/data")
                .header("Authorization", "Bearer " + token)
                .build();
        }
    }
    

    8. Comment ajouter des métriques personnalisées

    8.1 Counter

    @Component
    public class MyMetrics {
    
        private final Counter ordersProcessed;
    
        public MyMetrics(MeterRegistry registry) {
            this.ordersProcessed = Counter.builder("my_orders_processed_total")
                .description("Total orders processed")
                .register(registry);
        }
    
        public void orderProcessed() {
            ordersProcessed.increment();
        }
    }
    

    8.2 Timer

    private final Timer processingTime;
    
    public MyMetrics(MeterRegistry registry) {
        this.processingTime = Timer.builder("my_processing_duration_seconds")
            .description("Processing duration")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
    }
    
    public void process() {
        Timer.Sample sample = Timer.start();
        try {
            doProcess();
        } finally {
            sample.stop(processingTime);
        }
    }
    

    9. Comment gérer la résilience

    9.1 Retry

    @Autowired
    private RetryTemplate retryTemplate;
    
    public Data fetchData() {
        return retryTemplate.execute(() -> httpClient.get("/api/data"));
    }
    

    9.2 Circuit Breaker

    @Autowired
    private CircuitBreakerRegistry cbRegistry;
    
    public Data fetchData() {
        CircuitBreaker cb = cbRegistry.getOrCreate("external-api");
    
        return cb.executeWithFallback(
            () -> httpClient.get("/api/data"),
            () -> getCachedData()
        );
    }
    

    10. Comment déployer sur Kubernetes

    10.1 Build de l’image

    # Build
    mvn clean package -DskipTests
    docker build -t my-app:1.0.0 .
    
    # Push
    docker push my-registry/my-app:1.0.0
    

    10.2 Déploiement

    # Appliquer les manifests
    kubectl apply -f k8s/
    
    # Ou avec Helm
    helm install my-app ./chart -n my-namespace
    

    10.3 Vérification

    # Logs
    kubectl logs -f deployment/my-app
    
    # Port forward
    kubectl port-forward svc/my-app 8080:80
    
    # Health check
    curl http://localhost:8080/admin/health
    

    11. Comment migrer de V3 à V4

    11.1 Dépendances Maven

    <!-- Remplacer Logback par Log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    
    <!-- Ajouter H2 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    

    11.2 Configuration

    # Ajouter à application.yml
    socle:
      techdb:
        enabled: true
      logging:
        forwarder:
          enabled: false
      auth:
        enabled: false
      worker-registry:
        enabled: false
    
    logging:
      config: classpath:log4j2.xml
    

    11.3 Fichiers Log4j2

    Créer src/main/resources/log4j2.xml et log4j2.component.properties.

    Voir 25-MIGRATION-V3-V4 pour le guide complet.

    12. Comment debugger

    12.1 H2 Console

    socle:
      techdb:
        console:
          enabled: true
          path: /h2-console
    

    Accéder à http://localhost:8080/h2-console

    12.2 Endpoints Admin

    # État de santé
    curl http://localhost:8080/admin/health
    
    # Workers
    curl http://localhost:8080/admin/workers
    
    # Registry
    curl http://localhost:8080/admin/registry
    
    # Métriques
    curl http://localhost:8080/actuator/prometheus
    

    12.3 Logs

    // Activer le debug pour le Socle
    logging.level.eu.lmvi.socle=DEBUG
    

    13. Références

  • Socle V004 – Exemples de Code

    Socle V004 – Exemples de Code

    19 – Exemples

    Version : 4.0.0 Date : 2025-12-09

    1. Application minimale

    1.1 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
             https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.2.1</version>
        </parent>
    
        <groupId>com.example</groupId>
        <artifactId>my-socle-app</artifactId>
        <version>1.0.0</version>
    
        <properties>
            <java.version>21</java.version>
        </properties>
    
        <dependencies>
            <!-- Socle V4 -->
            <dependency>
                <groupId>eu.lmvi</groupId>
                <artifactId>socle-v004</artifactId>
                <version>4.0.0</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    1.2 Application.java

    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    
    @SpringBootApplication
    @ComponentScan(basePackages = {"com.example", "eu.lmvi.socle"})
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    

    1.3 application.yml

    socle:
      app_name: my-app
      env_name: ${ENV_NAME:DEV}
      region: ${REGION:local}
    
    server:
      port: ${HTTP_PORT:8080}
    
    logging:
      config: classpath:log4j2.xml
    

    1.4 Worker simple

    package com.example.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import org.springframework.stereotype.Component;
    
    @Component
    public class HelloWorker implements Worker {
    
        @Override
        public String getName() {
            return "hello-worker";
        }
    
        @Override
        public void initialize() {
            System.out.println("Hello Worker initialized");
        }
    
        @Override
        public void start() {
            System.out.println("Hello Worker started");
        }
    
        @Override
        public void doWork() {
            System.out.println("Hello from worker!");
        }
    
        @Override
        public void stop() {
            System.out.println("Hello Worker stopped");
        }
    
        @Override
        public boolean isHealthy() {
            return true;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of("status", "running");
        }
    }
    

    2. Worker Kafka Consumer

    package com.example.worker;
    
    import eu.lmvi.socle.worker.AbstractWorker;
    import eu.lmvi.socle.techdb.TechDbManager;
    import org.apache.kafka.clients.consumer.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class KafkaConsumerWorker extends AbstractWorker {
    
        @Autowired
        private TechDbManager techDb;
    
        private KafkaConsumer<String, String> consumer;
        private String topic = "my-topic";
        private long lastOffset = 0;
    
        @Override
        public String getName() {
            return "kafka-consumer";
        }
    
        @Override
        public int getStartPriority() {
            return 10;
        }
    
        @Override
        protected void doInitialize() {
            // Restaurer l'offset
            lastOffset = techDb.getOffset("kafka", topic + "-0").orElse(0L);
            log.info("Starting from offset: {}", lastOffset);
    
            // Créer le consumer
            Properties props = new Properties();
            props.put("bootstrap.servers", "localhost:9092");
            props.put("group.id", "my-group");
            props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.put("enable.auto.commit", "false");
    
            consumer = new KafkaConsumer<>(props);
            consumer.subscribe(List.of(topic));
        }
    
        @Override
        protected void doProcess() {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
    
            for (ConsumerRecord<String, String> record : records) {
                processMessage(record);
                lastOffset = record.offset();
                incrementProcessed();
            }
    
            // Persister périodiquement
            if (processedCount.get() % 100 == 0) {
                techDb.saveOffset("kafka", topic + "-0", lastOffset, null);
            }
        }
    
        private void processMessage(ConsumerRecord<String, String> record) {
            log.debug("Processing: key={}, value={}", record.key(), record.value());
            // Traitement...
        }
    
        @Override
        protected void doStop() {
            // Sauvegarder l'offset final
            techDb.saveOffset("kafka", topic + "-0", lastOffset, null);
    
            if (consumer != null) {
                consumer.close();
            }
        }
    
        @Override
        public Map<String, Object> getStats() {
            Map<String, Object> stats = new HashMap<>(super.getStats());
            stats.put("lastOffset", lastOffset);
            return stats;
        }
    }
    

    3. Worker HTTP API

    package com.example.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import eu.lmvi.socle.kv.KvBus;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.*;
    
    @Component
    @RestController
    @RequestMapping("/api/orders")
    public class OrderApiWorker implements Worker {
    
        @Autowired
        private KvBus kvBus;
    
        @Autowired
        private OrderService orderService;
    
        private volatile boolean running = false;
    
        @Override
        public String getName() {
            return "order-api";
        }
    
        @Override
        public boolean isPassive() {
            return true;  // Pas de doWork cyclique
        }
    
        @Override
        public void initialize() {}
    
        @Override
        public void start() {
            running = true;
        }
    
        @Override
        public void doWork() {
            // Passif - traitement via endpoints REST
        }
    
        @Override
        public void stop() {
            running = false;
        }
    
        @Override
        public boolean isHealthy() {
            return running;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of("running", running);
        }
    
        // === REST Endpoints ===
    
        @PostMapping
        public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
            Order order = orderService.create(request);
    
            // Cache l'order
            kvBus.putJson("order:" + order.getId(), order);
            kvBus.setTtl("order:" + order.getId(), Duration.ofHours(1));
    
            return ResponseEntity.status(HttpStatus.CREATED).body(order);
        }
    
        @GetMapping("/{id}")
        public ResponseEntity<Order> getOrder(@PathVariable String id) {
            // Vérifier le cache d'abord
            Optional<Order> cached = kvBus.getJson("order:" + id, Order.class);
            if (cached.isPresent()) {
                return ResponseEntity.ok(cached.get());
            }
    
            // Sinon charger depuis la DB
            return orderService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
        }
    }
    

    4. Worker Schedulé (Cron)

    package com.example.worker;
    
    import eu.lmvi.socle.worker.AbstractWorker;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DailyReportWorker extends AbstractWorker {
    
        @Override
        public String getName() {
            return "daily-report";
        }
    
        @Override
        public String getSchedule() {
            return "0 0 6 * * ?";  // Tous les jours à 6h
        }
    
        @Override
        public boolean isScheduled() {
            return true;
        }
    
        @Override
        protected void doProcess() {
            log.info("Generating daily report...");
    
            // Collecter les données
            ReportData data = collectReportData();
    
            // Générer le rapport
            Report report = generateReport(data);
    
            // Envoyer par email
            sendReportByEmail(report);
    
            log.info("Daily report sent successfully");
            incrementProcessed();
        }
    
        private ReportData collectReportData() {
            // ...
            return new ReportData();
        }
    
        private Report generateReport(ReportData data) {
            // ...
            return new Report();
        }
    
        private void sendReportByEmail(Report report) {
            // ...
        }
    }
    

    5. Pipeline de traitement

    package com.example.pipeline;
    
    import eu.lmvi.socle.pipeline.*;
    import org.springframework.stereotype.Component;
    
    @Component
    public class OrderProcessingPipeline {
    
        @Autowired
        private PipelineEngine engine;
    
        @Autowired
        private OrderValidator validator;
    
        @Autowired
        private OrderEnricher enricher;
    
        @Autowired
        private PaymentProcessor paymentProcessor;
    
        @Autowired
        private NotificationService notificationService;
    
        public PipelineResult<ProcessedOrder> process(Order order) {
            Pipeline<Order, ProcessedOrder> pipeline = PipelineBuilder
                .<Order, ProcessedOrder>create("order-processing")
                .addStep(new ValidationStep(validator))
                .addStep(new EnrichmentStep(enricher))
                .addStep(new PaymentStep(paymentProcessor))
                .addStep(new NotificationStep(notificationService))
                .build();
    
            return engine.execute(pipeline, order);
        }
    }
    
    // Étape de validation
    class ValidationStep implements PipelineStep<Order, ValidatedOrder> {
    
        private final OrderValidator validator;
    
        public ValidationStep(OrderValidator validator) {
            this.validator = validator;
        }
    
        @Override
        public String getName() {
            return "validation";
        }
    
        @Override
        public StepResult<ValidatedOrder> execute(Order input, PipelineContext context) {
            List<String> errors = validator.validate(input);
            if (!errors.isEmpty()) {
                return StepResult.failure(getName(),
                    new ValidationException(errors), Duration.ZERO, 1);
            }
            return StepResult.success(getName(), new ValidatedOrder(input), Duration.ZERO);
        }
    
        @Override
        public boolean isRetryable() {
            return false;
        }
    }
    
    // Étape de notification (optionnelle)
    class NotificationStep implements PipelineStep<ProcessedOrder, ProcessedOrder> {
    
        private final NotificationService notificationService;
    
        @Override
        public String getName() {
            return "notification";
        }
    
        @Override
        public StepResult<ProcessedOrder> execute(ProcessedOrder input, PipelineContext context) {
            try {
                notificationService.sendOrderConfirmation(input);
                return StepResult.success(getName(), input, Duration.ZERO);
            } catch (Exception e) {
                return StepResult.failure(getName(), e, Duration.ZERO, 1);
            }
        }
    
        @Override
        public boolean isOptional() {
            return true;  // Le pipeline continue même si la notif échoue
        }
    }
    

    6. Service avec résilience

    package com.example.service;
    
    import eu.lmvi.socle.resilience.*;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ExternalApiService {
    
        @Autowired
        private RetryTemplate retryTemplate;
    
        @Autowired
        private CircuitBreakerRegistry cbRegistry;
    
        @Autowired
        private KvBus kvBus;
    
        private final OkHttpClient httpClient;
    
        public Data fetchData(String id) {
            // Circuit breaker + Retry + Cache fallback
            CircuitBreaker cb = cbRegistry.getOrCreate("external-api");
    
            return cb.executeWithFallback(
                () -> retryTemplate.execute(() -> doFetchData(id)),
                () -> getCachedData(id)
            );
        }
    
        private Data doFetchData(String id) throws IOException {
            Request request = new Request.Builder()
                .url("https://api.example.com/data/" + id)
                .build();
    
            try (Response response = httpClient.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    throw new IOException("API returned " + response.code());
                }
    
                Data data = parseResponse(response.body().string());
    
                // Mettre en cache
                kvBus.putJson("cache:data:" + id, data);
                kvBus.setTtl("cache:data:" + id, Duration.ofMinutes(5));
    
                return data;
            }
        }
    
        private Data getCachedData(String id) {
            return kvBus.getJson("cache:data:" + id, Data.class)
                .orElseThrow(() -> new RuntimeException("No cached data available"));
        }
    }
    

    7. Configuration multi-environnement

    application.yml (base)

    socle:
      app_name: ${APP_NAME:my-app}
      env_name: ${ENV_NAME:DEV}
      region: ${REGION:local}
    
    spring:
      profiles:
        active: ${PROFILE:dev}
    

    application-dev.yml

    socle:
      kvbus:
        mode: in_memory
      techdb:
        console:
          enabled: true
      admin:
        auth:
          enabled: false
    
    logging:
      level:
        eu.lmvi.socle: DEBUG
    

    application-prod.yml

    socle:
      kvbus:
        mode: redis
        redis:
          host: ${REDIS_HOST}
          password: ${REDIS_PASSWORD}
      techdb:
        console:
          enabled: false
      logging:
        forwarder:
          enabled: true
      auth:
        enabled: true
      admin:
        auth:
          enabled: true
    
    logging:
      level:
        eu.lmvi.socle: INFO
    

    8. Dockerfile complet

    # Build stage
    FROM eclipse-temurin:21-jdk-alpine AS build
    WORKDIR /app
    COPY pom.xml .
    COPY src ./src
    RUN apk add --no-cache maven && \
        mvn clean package -DskipTests
    
    # Runtime stage
    FROM eclipse-temurin:21-jre-alpine
    WORKDIR /app
    
    # Security
    RUN addgroup -S app && adduser -S app -G app
    USER app
    
    # Copy artifact
    COPY --from=build --chown=app:app /app/target/*.jar app.jar
    
    # Config
    ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom"
    
    # Health check
    HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
        CMD wget -qO- http://localhost:8080/admin/health/live || exit 1
    
    EXPOSE 8080
    
    ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
    

    9. Kubernetes deployment complet

    Voir 16-KUBERNETES pour l’exemple complet de déploiement K8s.

    10. Références

  • Socle V004 – Guides Pratiques

    Socle V004 – Guides Pratiques

    17 – How-To Guides

    Version : 4.0.0 Date : 2025-12-09

    1. Comment créer un nouveau Worker

    1.1 Worker simple

    package com.myapp.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(MyWorker.class);
        private volatile boolean running = false;
    
        @Override
        public String getName() {
            return "my-worker";
        }
    
        @Override
        public void initialize() {
            log.info("[{}] Initializing", getName());
        }
    
        @Override
        public void start() {
            log.info("[{}] Starting", getName());
            running = true;
        }
    
        @Override
        public void doWork() {
            if (!running) return;
            // Votre logique ici
        }
    
        @Override
        public void stop() {
            log.info("[{}] Stopping", getName());
            running = false;
        }
    
        @Override
        public boolean isHealthy() {
            return running;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of("running", running);
        }
    }
    

    1.2 Worker avec priorité

    @Override
    public int getStartPriority() {
        return 10;  // Démarre en premier
    }
    
    @Override
    public int getStopPriority() {
        return 90;  // S'arrête en dernier
    }
    

    1.3 Worker schedulé

    @Override
    public String getSchedule() {
        return "0 0 6 * * ?";  // Tous les jours à 6h
    }
    
    @Override
    public boolean isScheduled() {
        return true;
    }
    

    2. Comment utiliser KvBus

    2.1 Opérations basiques

    @Service
    public class MyService {
    
        @Autowired
        private KvBus kvBus;
    
        public void example() {
            // Stocker
            kvBus.put("key", "value");
            kvBus.put("key-with-ttl", "value", Duration.ofHours(1));
    
            // Récupérer
            Optional<String> value = kvBus.get("key");
    
            // Supprimer
            kvBus.delete("key");
    
            // Compteur atomique
            long count = kvBus.increment("counter");
        }
    }
    

    2.2 JSON

    // Stocker un objet
    kvBus.putJson("order:123", order);
    
    // Récupérer un objet
    Optional<Order> order = kvBus.getJson("order:123", Order.class);
    

    2.3 Lock distribué

    public boolean tryLock(String resource) {
        return kvBus.putIfAbsent("lock:" + resource, "locked", Duration.ofMinutes(5));
    }
    
    public void unlock(String resource) {
        kvBus.delete("lock:" + resource);
    }
    

    3. Comment utiliser SharedDataRegistry

    3.1 Key-Value

    @Service
    public class MyService {
    
        @Autowired
        private SharedDataRegistry registry;
    
        public void example() {
            // Stocker avec niveau de santé
            registry.put("database.connected", true, HealthLevel.CRITICAL);
    
            // Récupérer
            boolean connected = registry.getBoolean("database.connected").orElse(false);
        }
    }
    

    3.2 Compteurs

    // Créer une séquence
    registry.createSequence("orders.processed", 0, HealthLevel.NORMAL);
    
    // Incrémenter
    long count = registry.incrementSequence("orders.processed");
    
    // Lire
    long total = registry.getSequence("orders.processed");
    

    4. Comment utiliser TechDB (V4)

    4.1 Offsets

    @Service
    public class MyService {
    
        @Autowired
        private TechDbManager techDb;
    
        public void example() {
            // Sauvegarder un offset
            techDb.saveOffset("kafka", "my-topic-0", 123456L, null);
    
            // Récupérer un offset
            OptionalLong offset = techDb.getOffset("kafka", "my-topic-0");
        }
    }
    

    4.2 État des workers

    // Sauvegarder l'état
    techDb.saveWorkerState("my-worker", "RUNNING", Map.of("progress", 50));
    
    // Récupérer l'état
    Optional<WorkerState> state = techDb.getWorkerState("my-worker");
    

    4.3 Événements techniques

    // Logger un événement
    techDb.logEvent("ERROR", Map.of(
        "message", "Connection failed",
        "target", "database"
    ));
    
    // Récupérer les événements
    List<TechEvent> events = techDb.getEvents("ERROR", Instant.now().minus(1, ChronoUnit.HOURS), 100);
    

    5. Comment implémenter un Pipeline

    5.1 Pipeline simple

    Pipeline<Order, ProcessedOrder> pipeline = PipelineBuilder
        .<Order, ProcessedOrder>create("order-processing")
        .addStep("validate", this::validateOrder)
        .addStep("enrich", this::enrichOrder)
        .addStep("process", this::processOrder)
        .build();
    
    PipelineResult<ProcessedOrder> result = pipelineEngine.execute(pipeline, order);
    

    5.2 Étape personnalisée

    public class ValidationStep implements PipelineStep<Order, ValidatedOrder> {
    
        @Override
        public String getName() {
            return "validation";
        }
    
        @Override
        public StepResult<ValidatedOrder> execute(Order input, PipelineContext context) {
            // Validation...
            return StepResult.success(getName(), new ValidatedOrder(input), Duration.ZERO);
        }
    
        @Override
        public boolean isRetryable() {
            return false;
        }
    }
    

    6. Comment configurer le logging (V4)

    6.1 Log4j2 basique

    <!-- src/main/resources/log4j2.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{ISO8601} %-5level [%thread] %logger{36} - %msg%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="INFO">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>
    

    6.2 Avec LogForwarder

    # application.yml
    socle:
      logging:
        forwarder:
          enabled: true
          transport-mode: http
          log-hub-url: https://logs.mycompany.com/api/ingest
    

    7. Comment utiliser l’authentification JWT (V4)

    7.1 Configuration

    socle:
      auth:
        enabled: true
        server-url: https://auth.mycompany.com
        api-key: ${API_KEY}
    

    7.2 Utilisation

    @Service
    public class SecuredService {
    
        @Autowired(required = false)
        private SocleAuthClient authClient;
    
        public void callSecuredApi() {
            if (authClient == null) {
                throw new IllegalStateException("Auth not configured");
            }
    
            String token = authClient.getValidAccessToken();
    
            // Utiliser le token dans les requêtes HTTP
            Request request = new Request.Builder()
                .url("https://api.mycompany.com/data")
                .header("Authorization", "Bearer " + token)
                .build();
        }
    }
    

    8. Comment ajouter des métriques personnalisées

    8.1 Counter

    @Component
    public class MyMetrics {
    
        private final Counter ordersProcessed;
    
        public MyMetrics(MeterRegistry registry) {
            this.ordersProcessed = Counter.builder("my_orders_processed_total")
                .description("Total orders processed")
                .register(registry);
        }
    
        public void orderProcessed() {
            ordersProcessed.increment();
        }
    }
    

    8.2 Timer

    private final Timer processingTime;
    
    public MyMetrics(MeterRegistry registry) {
        this.processingTime = Timer.builder("my_processing_duration_seconds")
            .description("Processing duration")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
    }
    
    public void process() {
        Timer.Sample sample = Timer.start();
        try {
            doProcess();
        } finally {
            sample.stop(processingTime);
        }
    }
    

    9. Comment gérer la résilience

    9.1 Retry

    @Autowired
    private RetryTemplate retryTemplate;
    
    public Data fetchData() {
        return retryTemplate.execute(() -> httpClient.get("/api/data"));
    }
    

    9.2 Circuit Breaker

    @Autowired
    private CircuitBreakerRegistry cbRegistry;
    
    public Data fetchData() {
        CircuitBreaker cb = cbRegistry.getOrCreate("external-api");
    
        return cb.executeWithFallback(
            () -> httpClient.get("/api/data"),
            () -> getCachedData()
        );
    }
    

    10. Comment déployer sur Kubernetes

    10.1 Build de l’image

    # Build
    mvn clean package -DskipTests
    docker build -t my-app:1.0.0 .
    
    # Push
    docker push my-registry/my-app:1.0.0
    

    10.2 Déploiement

    # Appliquer les manifests
    kubectl apply -f k8s/
    
    # Ou avec Helm
    helm install my-app ./chart -n my-namespace
    

    10.3 Vérification

    # Logs
    kubectl logs -f deployment/my-app
    
    # Port forward
    kubectl port-forward svc/my-app 8080:80
    
    # Health check
    curl http://localhost:8080/admin/health
    

    11. Comment migrer de V3 à V4

    11.1 Dépendances Maven

    <!-- Remplacer Logback par Log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    
    <!-- Ajouter H2 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    

    11.2 Configuration

    # Ajouter à application.yml
    socle:
      techdb:
        enabled: true
      logging:
        forwarder:
          enabled: false
      auth:
        enabled: false
      worker-registry:
        enabled: false
    
    logging:
      config: classpath:log4j2.xml
    

    11.3 Fichiers Log4j2

    Créer src/main/resources/log4j2.xml et log4j2.component.properties.

    Voir 25-MIGRATION-V3-V4 pour le guide complet.

    12. Comment debugger

    12.1 H2 Console

    socle:
      techdb:
        console:
          enabled: true
          path: /h2-console
    

    Accéder à http://localhost:8080/h2-console

    12.2 Endpoints Admin

    # État de santé
    curl http://localhost:8080/admin/health
    
    # Workers
    curl http://localhost:8080/admin/workers
    
    # Registry
    curl http://localhost:8080/admin/registry
    
    # Métriques
    curl http://localhost:8080/actuator/prometheus
    

    12.3 Logs

    // Activer le debug pour le Socle
    logging.level.eu.lmvi.socle=DEBUG
    

    13. Références

  • Socle V004 – Exemples de Code

    Socle V004 – Exemples de Code

    19 – Exemples

    Version : 4.0.0 Date : 2025-12-09

    1. Application minimale

    1.1 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
             https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.2.1</version>
        </parent>
    
        <groupId>com.example</groupId>
        <artifactId>my-socle-app</artifactId>
        <version>1.0.0</version>
    
        <properties>
            <java.version>21</java.version>
        </properties>
    
        <dependencies>
            <!-- Socle V4 -->
            <dependency>
                <groupId>eu.lmvi</groupId>
                <artifactId>socle-v004</artifactId>
                <version>4.0.0</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    1.2 Application.java

    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    
    @SpringBootApplication
    @ComponentScan(basePackages = {"com.example", "eu.lmvi.socle"})
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    

    1.3 application.yml

    socle:
      app_name: my-app
      env_name: ${ENV_NAME:DEV}
      region: ${REGION:local}
    
    server:
      port: ${HTTP_PORT:8080}
    
    logging:
      config: classpath:log4j2.xml
    

    1.4 Worker simple

    package com.example.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import org.springframework.stereotype.Component;
    
    @Component
    public class HelloWorker implements Worker {
    
        @Override
        public String getName() {
            return "hello-worker";
        }
    
        @Override
        public void initialize() {
            System.out.println("Hello Worker initialized");
        }
    
        @Override
        public void start() {
            System.out.println("Hello Worker started");
        }
    
        @Override
        public void doWork() {
            System.out.println("Hello from worker!");
        }
    
        @Override
        public void stop() {
            System.out.println("Hello Worker stopped");
        }
    
        @Override
        public boolean isHealthy() {
            return true;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of("status", "running");
        }
    }
    

    2. Worker Kafka Consumer

    package com.example.worker;
    
    import eu.lmvi.socle.worker.AbstractWorker;
    import eu.lmvi.socle.techdb.TechDbManager;
    import org.apache.kafka.clients.consumer.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class KafkaConsumerWorker extends AbstractWorker {
    
        @Autowired
        private TechDbManager techDb;
    
        private KafkaConsumer<String, String> consumer;
        private String topic = "my-topic";
        private long lastOffset = 0;
    
        @Override
        public String getName() {
            return "kafka-consumer";
        }
    
        @Override
        public int getStartPriority() {
            return 10;
        }
    
        @Override
        protected void doInitialize() {
            // Restaurer l'offset
            lastOffset = techDb.getOffset("kafka", topic + "-0").orElse(0L);
            log.info("Starting from offset: {}", lastOffset);
    
            // Créer le consumer
            Properties props = new Properties();
            props.put("bootstrap.servers", "localhost:9092");
            props.put("group.id", "my-group");
            props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.put("enable.auto.commit", "false");
    
            consumer = new KafkaConsumer<>(props);
            consumer.subscribe(List.of(topic));
        }
    
        @Override
        protected void doProcess() {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
    
            for (ConsumerRecord<String, String> record : records) {
                processMessage(record);
                lastOffset = record.offset();
                incrementProcessed();
            }
    
            // Persister périodiquement
            if (processedCount.get() % 100 == 0) {
                techDb.saveOffset("kafka", topic + "-0", lastOffset, null);
            }
        }
    
        private void processMessage(ConsumerRecord<String, String> record) {
            log.debug("Processing: key={}, value={}", record.key(), record.value());
            // Traitement...
        }
    
        @Override
        protected void doStop() {
            // Sauvegarder l'offset final
            techDb.saveOffset("kafka", topic + "-0", lastOffset, null);
    
            if (consumer != null) {
                consumer.close();
            }
        }
    
        @Override
        public Map<String, Object> getStats() {
            Map<String, Object> stats = new HashMap<>(super.getStats());
            stats.put("lastOffset", lastOffset);
            return stats;
        }
    }
    

    3. Worker HTTP API

    package com.example.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import eu.lmvi.socle.kv.KvBus;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.*;
    
    @Component
    @RestController
    @RequestMapping("/api/orders")
    public class OrderApiWorker implements Worker {
    
        @Autowired
        private KvBus kvBus;
    
        @Autowired
        private OrderService orderService;
    
        private volatile boolean running = false;
    
        @Override
        public String getName() {
            return "order-api";
        }
    
        @Override
        public boolean isPassive() {
            return true;  // Pas de doWork cyclique
        }
    
        @Override
        public void initialize() {}
    
        @Override
        public void start() {
            running = true;
        }
    
        @Override
        public void doWork() {
            // Passif - traitement via endpoints REST
        }
    
        @Override
        public void stop() {
            running = false;
        }
    
        @Override
        public boolean isHealthy() {
            return running;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of("running", running);
        }
    
        // === REST Endpoints ===
    
        @PostMapping
        public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
            Order order = orderService.create(request);
    
            // Cache l'order
            kvBus.putJson("order:" + order.getId(), order);
            kvBus.setTtl("order:" + order.getId(), Duration.ofHours(1));
    
            return ResponseEntity.status(HttpStatus.CREATED).body(order);
        }
    
        @GetMapping("/{id}")
        public ResponseEntity<Order> getOrder(@PathVariable String id) {
            // Vérifier le cache d'abord
            Optional<Order> cached = kvBus.getJson("order:" + id, Order.class);
            if (cached.isPresent()) {
                return ResponseEntity.ok(cached.get());
            }
    
            // Sinon charger depuis la DB
            return orderService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
        }
    }
    

    4. Worker Schedulé (Cron)

    package com.example.worker;
    
    import eu.lmvi.socle.worker.AbstractWorker;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DailyReportWorker extends AbstractWorker {
    
        @Override
        public String getName() {
            return "daily-report";
        }
    
        @Override
        public String getSchedule() {
            return "0 0 6 * * ?";  // Tous les jours à 6h
        }
    
        @Override
        public boolean isScheduled() {
            return true;
        }
    
        @Override
        protected void doProcess() {
            log.info("Generating daily report...");
    
            // Collecter les données
            ReportData data = collectReportData();
    
            // Générer le rapport
            Report report = generateReport(data);
    
            // Envoyer par email
            sendReportByEmail(report);
    
            log.info("Daily report sent successfully");
            incrementProcessed();
        }
    
        private ReportData collectReportData() {
            // ...
            return new ReportData();
        }
    
        private Report generateReport(ReportData data) {
            // ...
            return new Report();
        }
    
        private void sendReportByEmail(Report report) {
            // ...
        }
    }
    

    5. Pipeline de traitement

    package com.example.pipeline;
    
    import eu.lmvi.socle.pipeline.*;
    import org.springframework.stereotype.Component;
    
    @Component
    public class OrderProcessingPipeline {
    
        @Autowired
        private PipelineEngine engine;
    
        @Autowired
        private OrderValidator validator;
    
        @Autowired
        private OrderEnricher enricher;
    
        @Autowired
        private PaymentProcessor paymentProcessor;
    
        @Autowired
        private NotificationService notificationService;
    
        public PipelineResult<ProcessedOrder> process(Order order) {
            Pipeline<Order, ProcessedOrder> pipeline = PipelineBuilder
                .<Order, ProcessedOrder>create("order-processing")
                .addStep(new ValidationStep(validator))
                .addStep(new EnrichmentStep(enricher))
                .addStep(new PaymentStep(paymentProcessor))
                .addStep(new NotificationStep(notificationService))
                .build();
    
            return engine.execute(pipeline, order);
        }
    }
    
    // Étape de validation
    class ValidationStep implements PipelineStep<Order, ValidatedOrder> {
    
        private final OrderValidator validator;
    
        public ValidationStep(OrderValidator validator) {
            this.validator = validator;
        }
    
        @Override
        public String getName() {
            return "validation";
        }
    
        @Override
        public StepResult<ValidatedOrder> execute(Order input, PipelineContext context) {
            List<String> errors = validator.validate(input);
            if (!errors.isEmpty()) {
                return StepResult.failure(getName(),
                    new ValidationException(errors), Duration.ZERO, 1);
            }
            return StepResult.success(getName(), new ValidatedOrder(input), Duration.ZERO);
        }
    
        @Override
        public boolean isRetryable() {
            return false;
        }
    }
    
    // Étape de notification (optionnelle)
    class NotificationStep implements PipelineStep<ProcessedOrder, ProcessedOrder> {
    
        private final NotificationService notificationService;
    
        @Override
        public String getName() {
            return "notification";
        }
    
        @Override
        public StepResult<ProcessedOrder> execute(ProcessedOrder input, PipelineContext context) {
            try {
                notificationService.sendOrderConfirmation(input);
                return StepResult.success(getName(), input, Duration.ZERO);
            } catch (Exception e) {
                return StepResult.failure(getName(), e, Duration.ZERO, 1);
            }
        }
    
        @Override
        public boolean isOptional() {
            return true;  // Le pipeline continue même si la notif échoue
        }
    }
    

    6. Service avec résilience

    package com.example.service;
    
    import eu.lmvi.socle.resilience.*;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ExternalApiService {
    
        @Autowired
        private RetryTemplate retryTemplate;
    
        @Autowired
        private CircuitBreakerRegistry cbRegistry;
    
        @Autowired
        private KvBus kvBus;
    
        private final OkHttpClient httpClient;
    
        public Data fetchData(String id) {
            // Circuit breaker + Retry + Cache fallback
            CircuitBreaker cb = cbRegistry.getOrCreate("external-api");
    
            return cb.executeWithFallback(
                () -> retryTemplate.execute(() -> doFetchData(id)),
                () -> getCachedData(id)
            );
        }
    
        private Data doFetchData(String id) throws IOException {
            Request request = new Request.Builder()
                .url("https://api.example.com/data/" + id)
                .build();
    
            try (Response response = httpClient.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    throw new IOException("API returned " + response.code());
                }
    
                Data data = parseResponse(response.body().string());
    
                // Mettre en cache
                kvBus.putJson("cache:data:" + id, data);
                kvBus.setTtl("cache:data:" + id, Duration.ofMinutes(5));
    
                return data;
            }
        }
    
        private Data getCachedData(String id) {
            return kvBus.getJson("cache:data:" + id, Data.class)
                .orElseThrow(() -> new RuntimeException("No cached data available"));
        }
    }
    

    7. Configuration multi-environnement

    application.yml (base)

    socle:
      app_name: ${APP_NAME:my-app}
      env_name: ${ENV_NAME:DEV}
      region: ${REGION:local}
    
    spring:
      profiles:
        active: ${PROFILE:dev}
    

    application-dev.yml

    socle:
      kvbus:
        mode: in_memory
      techdb:
        console:
          enabled: true
      admin:
        auth:
          enabled: false
    
    logging:
      level:
        eu.lmvi.socle: DEBUG
    

    application-prod.yml

    socle:
      kvbus:
        mode: redis
        redis:
          host: ${REDIS_HOST}
          password: ${REDIS_PASSWORD}
      techdb:
        console:
          enabled: false
      logging:
        forwarder:
          enabled: true
      auth:
        enabled: true
      admin:
        auth:
          enabled: true
    
    logging:
      level:
        eu.lmvi.socle: INFO
    

    8. Dockerfile complet

    # Build stage
    FROM eclipse-temurin:21-jdk-alpine AS build
    WORKDIR /app
    COPY pom.xml .
    COPY src ./src
    RUN apk add --no-cache maven && \
        mvn clean package -DskipTests
    
    # Runtime stage
    FROM eclipse-temurin:21-jre-alpine
    WORKDIR /app
    
    # Security
    RUN addgroup -S app && adduser -S app -G app
    USER app
    
    # Copy artifact
    COPY --from=build --chown=app:app /app/target/*.jar app.jar
    
    # Config
    ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom"
    
    # Health check
    HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
        CMD wget -qO- http://localhost:8080/admin/health/live || exit 1
    
    EXPOSE 8080
    
    ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
    

    9. Kubernetes deployment complet

    Voir 16-KUBERNETES pour l’exemple complet de déploiement K8s.

    10. Références

  • Socle V004 – Guides Pratiques

    Socle V004 – Guides Pratiques

    17 – How-To Guides

    Version : 4.0.0 Date : 2025-12-09

    1. Comment créer un nouveau Worker

    1.1 Worker simple

    package com.myapp.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(MyWorker.class);
        private volatile boolean running = false;
    
        @Override
        public String getName() {
            return "my-worker";
        }
    
        @Override
        public void initialize() {
            log.info("[{}] Initializing", getName());
        }
    
        @Override
        public void start() {
            log.info("[{}] Starting", getName());
            running = true;
        }
    
        @Override
        public void doWork() {
            if (!running) return;
            // Votre logique ici
        }
    
        @Override
        public void stop() {
            log.info("[{}] Stopping", getName());
            running = false;
        }
    
        @Override
        public boolean isHealthy() {
            return running;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of("running", running);
        }
    }
    

    1.2 Worker avec priorité

    @Override
    public int getStartPriority() {
        return 10;  // Démarre en premier
    }
    
    @Override
    public int getStopPriority() {
        return 90;  // S'arrête en dernier
    }
    

    1.3 Worker schedulé

    @Override
    public String getSchedule() {
        return "0 0 6 * * ?";  // Tous les jours à 6h
    }
    
    @Override
    public boolean isScheduled() {
        return true;
    }
    

    2. Comment utiliser KvBus

    2.1 Opérations basiques

    @Service
    public class MyService {
    
        @Autowired
        private KvBus kvBus;
    
        public void example() {
            // Stocker
            kvBus.put("key", "value");
            kvBus.put("key-with-ttl", "value", Duration.ofHours(1));
    
            // Récupérer
            Optional<String> value = kvBus.get("key");
    
            // Supprimer
            kvBus.delete("key");
    
            // Compteur atomique
            long count = kvBus.increment("counter");
        }
    }
    

    2.2 JSON

    // Stocker un objet
    kvBus.putJson("order:123", order);
    
    // Récupérer un objet
    Optional<Order> order = kvBus.getJson("order:123", Order.class);
    

    2.3 Lock distribué

    public boolean tryLock(String resource) {
        return kvBus.putIfAbsent("lock:" + resource, "locked", Duration.ofMinutes(5));
    }
    
    public void unlock(String resource) {
        kvBus.delete("lock:" + resource);
    }
    

    3. Comment utiliser SharedDataRegistry

    3.1 Key-Value

    @Service
    public class MyService {
    
        @Autowired
        private SharedDataRegistry registry;
    
        public void example() {
            // Stocker avec niveau de santé
            registry.put("database.connected", true, HealthLevel.CRITICAL);
    
            // Récupérer
            boolean connected = registry.getBoolean("database.connected").orElse(false);
        }
    }
    

    3.2 Compteurs

    // Créer une séquence
    registry.createSequence("orders.processed", 0, HealthLevel.NORMAL);
    
    // Incrémenter
    long count = registry.incrementSequence("orders.processed");
    
    // Lire
    long total = registry.getSequence("orders.processed");
    

    4. Comment utiliser TechDB (V4)

    4.1 Offsets

    @Service
    public class MyService {
    
        @Autowired
        private TechDbManager techDb;
    
        public void example() {
            // Sauvegarder un offset
            techDb.saveOffset("kafka", "my-topic-0", 123456L, null);
    
            // Récupérer un offset
            OptionalLong offset = techDb.getOffset("kafka", "my-topic-0");
        }
    }
    

    4.2 État des workers

    // Sauvegarder l'état
    techDb.saveWorkerState("my-worker", "RUNNING", Map.of("progress", 50));
    
    // Récupérer l'état
    Optional<WorkerState> state = techDb.getWorkerState("my-worker");
    

    4.3 Événements techniques

    // Logger un événement
    techDb.logEvent("ERROR", Map.of(
        "message", "Connection failed",
        "target", "database"
    ));
    
    // Récupérer les événements
    List<TechEvent> events = techDb.getEvents("ERROR", Instant.now().minus(1, ChronoUnit.HOURS), 100);
    

    5. Comment implémenter un Pipeline

    5.1 Pipeline simple

    Pipeline<Order, ProcessedOrder> pipeline = PipelineBuilder
        .<Order, ProcessedOrder>create("order-processing")
        .addStep("validate", this::validateOrder)
        .addStep("enrich", this::enrichOrder)
        .addStep("process", this::processOrder)
        .build();
    
    PipelineResult<ProcessedOrder> result = pipelineEngine.execute(pipeline, order);
    

    5.2 Étape personnalisée

    public class ValidationStep implements PipelineStep<Order, ValidatedOrder> {
    
        @Override
        public String getName() {
            return "validation";
        }
    
        @Override
        public StepResult<ValidatedOrder> execute(Order input, PipelineContext context) {
            // Validation...
            return StepResult.success(getName(), new ValidatedOrder(input), Duration.ZERO);
        }
    
        @Override
        public boolean isRetryable() {
            return false;
        }
    }
    

    6. Comment configurer le logging (V4)

    6.1 Log4j2 basique

    <!-- src/main/resources/log4j2.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{ISO8601} %-5level [%thread] %logger{36} - %msg%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="INFO">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>
    

    6.2 Avec LogForwarder

    # application.yml
    socle:
      logging:
        forwarder:
          enabled: true
          transport-mode: http
          log-hub-url: https://logs.mycompany.com/api/ingest
    

    7. Comment utiliser l’authentification JWT (V4)

    7.1 Configuration

    socle:
      auth:
        enabled: true
        server-url: https://auth.mycompany.com
        api-key: ${API_KEY}
    

    7.2 Utilisation

    @Service
    public class SecuredService {
    
        @Autowired(required = false)
        private SocleAuthClient authClient;
    
        public void callSecuredApi() {
            if (authClient == null) {
                throw new IllegalStateException("Auth not configured");
            }
    
            String token = authClient.getValidAccessToken();
    
            // Utiliser le token dans les requêtes HTTP
            Request request = new Request.Builder()
                .url("https://api.mycompany.com/data")
                .header("Authorization", "Bearer " + token)
                .build();
        }
    }
    

    8. Comment ajouter des métriques personnalisées

    8.1 Counter

    @Component
    public class MyMetrics {
    
        private final Counter ordersProcessed;
    
        public MyMetrics(MeterRegistry registry) {
            this.ordersProcessed = Counter.builder("my_orders_processed_total")
                .description("Total orders processed")
                .register(registry);
        }
    
        public void orderProcessed() {
            ordersProcessed.increment();
        }
    }
    

    8.2 Timer

    private final Timer processingTime;
    
    public MyMetrics(MeterRegistry registry) {
        this.processingTime = Timer.builder("my_processing_duration_seconds")
            .description("Processing duration")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
    }
    
    public void process() {
        Timer.Sample sample = Timer.start();
        try {
            doProcess();
        } finally {
            sample.stop(processingTime);
        }
    }
    

    9. Comment gérer la résilience

    9.1 Retry

    @Autowired
    private RetryTemplate retryTemplate;
    
    public Data fetchData() {
        return retryTemplate.execute(() -> httpClient.get("/api/data"));
    }
    

    9.2 Circuit Breaker

    @Autowired
    private CircuitBreakerRegistry cbRegistry;
    
    public Data fetchData() {
        CircuitBreaker cb = cbRegistry.getOrCreate("external-api");
    
        return cb.executeWithFallback(
            () -> httpClient.get("/api/data"),
            () -> getCachedData()
        );
    }
    

    10. Comment déployer sur Kubernetes

    10.1 Build de l’image

    # Build
    mvn clean package -DskipTests
    docker build -t my-app:1.0.0 .
    
    # Push
    docker push my-registry/my-app:1.0.0
    

    10.2 Déploiement

    # Appliquer les manifests
    kubectl apply -f k8s/
    
    # Ou avec Helm
    helm install my-app ./chart -n my-namespace
    

    10.3 Vérification

    # Logs
    kubectl logs -f deployment/my-app
    
    # Port forward
    kubectl port-forward svc/my-app 8080:80
    
    # Health check
    curl http://localhost:8080/admin/health
    

    11. Comment migrer de V3 à V4

    11.1 Dépendances Maven

    <!-- Remplacer Logback par Log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    
    <!-- Ajouter H2 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    

    11.2 Configuration

    # Ajouter à application.yml
    socle:
      techdb:
        enabled: true
      logging:
        forwarder:
          enabled: false
      auth:
        enabled: false
      worker-registry:
        enabled: false
    
    logging:
      config: classpath:log4j2.xml
    

    11.3 Fichiers Log4j2

    Créer src/main/resources/log4j2.xml et log4j2.component.properties.

    Voir 25-MIGRATION-V3-V4 pour le guide complet.

    12. Comment debugger

    12.1 H2 Console

    socle:
      techdb:
        console:
          enabled: true
          path: /h2-console
    

    Accéder à http://localhost:8080/h2-console

    12.2 Endpoints Admin

    # État de santé
    curl http://localhost:8080/admin/health
    
    # Workers
    curl http://localhost:8080/admin/workers
    
    # Registry
    curl http://localhost:8080/admin/registry
    
    # Métriques
    curl http://localhost:8080/actuator/prometheus
    

    12.3 Logs

    // Activer le debug pour le Socle
    logging.level.eu.lmvi.socle=DEBUG
    

    13. Références

  • Socle V004 – Introduction

    Socle V004 – Introduction

    01 – Introduction au Socle V4

    Version : 4.0.0 Date : 2025-01-25

    1. Qu’est-ce que le Socle V4 ?

    Le Socle V4 est un framework Java de grade production construit sur Spring Boot 3.2.1 qui implémente le pattern MOP (Main Orchestrator Process). Il fournit une base solide pour construire des applications d’entreprise robustes et observables.

    Évolution depuis V3

    Le Socle V4 conserve et étend l’architecture V3 en ajoutant :

    Nouveauté V4 Description
    H2 TechDB Base embarquée pour état technique (remplace Nitrite)
    Log4j2 Framework logging haute performance (remplace Logback)
    LogForwarder Centralisation des logs vers LogHub (HTTP/NATS)
    SocleAuthClient Client authentification JWT
    WorkerRegistryClient Auto-enregistrement des workers
    StatusDashboard Dashboard HTML de supervision temps réel (port 9374)
    Pipeline V2 Pipeline asynchrone avec garantie at-least-once (Queue/Claim/Ack)

    2. Philosophie « MOP Pilote Tout »

    Le Main Orchestrator Process est le cœur du framework :

    ┌─────────────────────────────────────────────────────────────────┐
    │                           MOP                                    │
    │  - Orchestre tous les Workers                                   │
    │  - Gère le lifecycle (start/stop)                               │
    │  - Appelle doWork() automatiquement                             │
    │  - Garantit le shutdown gracieux                                │
    └─────────────────────────────────────────────────────────────────┘
                                  │
             ┌────────────────────┼────────────────────┐
             ▼                    ▼                    ▼
        ┌─────────┐         ┌─────────┐         ┌─────────┐
        │ Worker  │         │ Worker  │         │  HTTP   │
        │ Métier  │         │ Métier  │         │ Worker  │
        └─────────┘         └─────────┘         └─────────┘
    

    Principes clés

    1. Orchestration centralisée : Le MOP contrôle tout le lifecycle
    2. Démarrage ordonné : Workers par priorité (petit → grand), HTTP en dernier
    3. Arrêt gracieux : HTTP d’abord (drain), puis Workers
    4. Scheduling automatique : doWork() appelé selon cron ou interval

    3. Les 4 principes fondamentaux V4

    3.1 Portabilité

    • Fonctionne sur ARM/AMD64, Linux/macOS
    • Aucune dépendance serveur externe obligatoire
    • Base H2 embarquée pour l’état technique

    3.2 Sécurité

    • Aucun port entrant sur les NUC/agents
    • Communication sortante uniquement (HTTP/NATS)
    • Authentification JWT pour les services centraux

    3.3 Observabilité

    • Logs centralisés via LogForwarder
    • Corrélation par correlationId / execId
    • Suivi des workers via Registry

    3.4 Standardisation

    • Même authentification partout
    • Même format de logs
    • Même enregistrement des workers

    4. Stack technique

    Composant Version Usage
    Java 21 LTS Runtime
    Spring Boot 3.2.1 Framework
    Log4j2 2.22.1 Logging (nouveau V4)
    LMAX Disruptor 4.0.0 AsyncLoggers
    H2 2.2.x Base technique embarquée (nouveau V4)
    Kafka 3.6.0 Messaging
    NATS 2.17.0 Messaging
    Redisson 3.24.3 Redis client
    OkHttp 4.12.0 HTTP client
    Micrometer 1.12.0 Metrics

    5. Composants du Socle

    Composants V3 (conservés)

    Package Description
    mop Main Orchestrator Process
    worker Interface Worker
    config SocleConfiguration
    kv KvBus (in_memory / Redis)
    shared SharedDataRegistry
    supervisor Supervision heartbeats
    http HttpWorker, TomcatManager
    admin AdminRestApi
    metrics SocleMetrics
    pipeline PipelineEngine
    resilience CircuitBreaker, Retry
    scheduler WorkerScheduler
    security AdminAuthFilter, RateLimit

    Nouveaux composants V4

    Package Description
    techdb H2 TechDB Manager
    logging Log4j2 + LogForwarder
    client/auth SocleAuthClient
    client/registry WorkerRegistryClient

    6. Cas d’usage

    Le Socle V4 est idéal pour :

    • Agents de collecte (DB2 Journal Reader, CDC)
    • Services de synchronisation (ODH-sync)
    • Proxies et bridges (Kafka Proxy)
    • Workers de traitement (ETL, pipelines)
    • Services multi-région (MTQ, GUA, REU, etc.)

    7. Prérequis

    Développement

    • JDK 21+
    • Maven 3.9+
    • IDE (IntelliJ IDEA recommandé)

    Production

    • JRE 21+
    • Docker (optionnel)
    • Accès NATS ou HTTP pour LogForwarder (optionnel)

    8. Premiers pas

    # Cloner le projet
    git clone <repo>/socle-v004.git
    
    # Build
    cd socle-v004
    mvn clean package -DskipTests
    
    # Run
    java -jar target/socle-v004-4.0.0.jar
    
    # Vérifier
    curl http://localhost:8080/health
    

    9. Documentation

    Document Description
    02-ARCHITECTURE Architecture détaillée
    03-QUICKSTART Guide de démarrage
    08-SUPERVISOR Supervision et heartbeats
    09-PIPELINE Pipeline V1 et V2
    21-H2-TECHDB Base H2 (V4)
    22-LOG4J2-LOGFORWARDER Logging V4
    25-MIGRATION-V3-V4 Migration
    27-STATUS-DASHBOARD Dashboard supervision
    GUIDE-METHODOLOGIQUE Bonnes pratiques

    10. Support

    • Issues : GitHub Issues
    • Documentation : Ce dossier docs/Help/
    • Exemples : 20-EXEMPLES