Auteur/autrice : jmh

  • Socle V004 – gRPC Inter-Socles

    Socle V004 – gRPC Inter-Socles

    31 – Communication gRPC Inter-Socles

    Vue d’ensemble

    Le module gRPC permet aux instances Socle V4 de communiquer entre elles via streaming bidirectionnel. Il offre :

    • Sessions : Gestion de sessions multi-participants avec TTL
    • Streaming bidirectionnel : Communication temps reel entre Socles
    • Pipeline de traitement : Transformation des messages via Janino et JDM
    • Fan-out : Routage broadcast ou cible vers les participants
    • Pool de connexions : Connexions persistantes vers les peers

    Architecture

    ┌─────────────────────────────────────────────────────────────────┐
    │                         Socle A                                  │
    │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
    │  │ SessionMgr  │  │  Pipeline   │  │    GrpcServerWorker     │  │
    │  │             │  │  Executor   │  │    (port 9400)          │  │
    │  └─────────────┘  └─────────────┘  └─────────────────────────┘  │
    │         │               │                      │                 │
    │         └───────────────┼──────────────────────┘                 │
    │                         │                                        │
    │              ┌──────────┴──────────┐                            │
    │              │   gRPC Services     │                            │
    │              │  - SocleComm        │                            │
    │              │  - SessionService   │                            │
    │              │  - DiscoveryService │                            │
    │              └──────────┬──────────┘                            │
    └─────────────────────────┼───────────────────────────────────────┘
                              │ gRPC/HTTP2
                              ▼
    ┌─────────────────────────────────────────────────────────────────┐
    │                         Socle B                                  │
    │              ┌──────────────────────┐                           │
    │              │  PeerConnectionPool  │                           │
    │              └──────────────────────┘                           │
    └─────────────────────────────────────────────────────────────────┘
    

    Configuration

    application.yml

    socle:
      grpc:
        # Activation du module
        enabled: ${GRPC_ENABLED:false}
    
        # Port du serveur gRPC
        port: ${GRPC_PORT:9400}
    
        # Identification du Socle
        socle-id: ${SOCLE_ID:${socle.app_name}}
        socle-version: ${socle.version}
    
        # Limites serveur
        max-inbound-message-size: ${GRPC_MAX_MESSAGE_SIZE:4194304}  # 4MB
        max-concurrent-calls-per-connection: ${GRPC_MAX_CONCURRENT_CALLS:100}
    
        # Sessions
        session:
          ttl-seconds: ${GRPC_SESSION_TTL:1800}           # 30 min
          max-participants: ${GRPC_MAX_PARTICIPANTS:100}
          persist-to-tech-db: ${GRPC_PERSIST_SESSIONS:true}
          cache-in-redis: ${GRPC_CACHE_REDIS:true}
    
        # Pipeline de traitement
        pipeline:
          enabled: ${GRPC_PIPELINE_ENABLED:true}
          config-cache-ttl-seconds: ${GRPC_PIPELINE_CACHE_TTL:300}
    
        # Connexions peer
        peer:
          max-channels-per-peer: ${GRPC_PEER_MAX_CHANNELS:4}
          connection-timeout-ms: ${GRPC_PEER_CONNECT_TIMEOUT:5000}
          idle-timeout-seconds: ${GRPC_PEER_IDLE_TIMEOUT:300}
          keep-alive-enabled: ${GRPC_PEER_KEEPALIVE:true}
          keep-alive-time-seconds: ${GRPC_PEER_KEEPALIVE_TIME:30}
          keep-alive-timeout-seconds: ${GRPC_PEER_KEEPALIVE_TIMEOUT:10}
    

    Variables d’environnement

    Variable Default Description
    GRPC_ENABLED false Active le module gRPC
    GRPC_PORT 9400 Port du serveur gRPC
    SOCLE_ID ${app_name} Identifiant unique du Socle
    GRPC_SESSION_TTL 1800 TTL des sessions en secondes
    GRPC_MAX_PARTICIPANTS 100 Max participants par session
    GRPC_PIPELINE_ENABLED true Active le pipeline de traitement

    Services gRPC

    DiscoveryService

    Service de decouverte et health check.

    service DiscoveryService {
        rpc GetCapabilities(CapabilitiesRequest) returns (CapabilitiesResponse);
        rpc Ping(PingRequest) returns (PingResponse);
    }
    

    Test avec grpcurl :

    # Ping
    grpcurl -plaintext localhost:9400 socle.DiscoveryService/Ping
    
    # Capabilities
    grpcurl -plaintext localhost:9400 socle.DiscoveryService/GetCapabilities
    

    SessionService

    Gestion du cycle de vie des sessions.

    service SessionService {
        rpc CreateSession(CreateSessionRequest) returns (SessionInfo);
        rpc JoinSession(JoinSessionRequest) returns (JoinSessionResponse);
        rpc LeaveSession(LeaveSessionRequest) returns (LeaveSessionResponse);
        rpc GetSession(GetSessionRequest) returns (SessionInfo);
        rpc CloseSession(CloseSessionRequest) returns (CloseSessionResponse);
    }
    

    Exemples :

    # Creer une session
    grpcurl -plaintext -d '{
      "session_type": "chat",
      "owner_id": "user1",
      "ttl_seconds": 3600
    }' localhost:9400 socle.SessionService/CreateSession
    
    # Joindre une session
    grpcurl -plaintext -d '{
      "session_id": "uuid-de-la-session",
      "participant_id": "user2",
      "display_name": "User 2"
    }' localhost:9400 socle.SessionService/JoinSession
    
    # Obtenir info session
    grpcurl -plaintext -d '{
      "session_id": "uuid-de-la-session"
    }' localhost:9400 socle.SessionService/GetSession
    

    SocleComm

    Streaming bidirectionnel pour l’echange de messages.

    service SocleComm {
        rpc Exchange(stream SessionMessage) returns (stream SessionMessage);
    }
    

    Format des messages :

    message SessionMessage {
        string session_id = 1;
        string sender_id = 2;
        MessageKind kind = 3;        // JOIN, LEAVE, DATA, REQUEST, RESPONSE, etc.
        repeated string target_ids = 4;  // Vide = broadcast
        string correlation_id = 5;
        int64 timestamp = 6;
        string payload = 7;          // JSON
        map<string, string> headers = 8;
    }
    

    Sessions

    Cycle de vie

    CREATE ──► ACTIVE ──► CLOSING ──► CLOSED
                  │
                  └──► EXPIRED (TTL depasse)
    

    Stockage

    Les sessions sont stockees dans :

    1. Cache memoire : Acces rapide, stream observers
    2. Redis (si KvBus en mode redis) : Partage entre instances
    3. TechDB : Audit et persistance

    Tables TechDB

    -- Sessions (audit)
    CREATE TABLE grpc_sessions (
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
        session_id VARCHAR(36) NOT NULL UNIQUE,
        session_type VARCHAR(100) NOT NULL,
        owner_id VARCHAR(100) NOT NULL,
        status VARCHAR(20) DEFAULT 'ACTIVE',
        datas CLOB
    );
    
    -- Configuration pipeline par type de session
    CREATE TABLE grpc_pipeline_configs (
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
        session_type VARCHAR(100) NOT NULL UNIQUE,
        janino_pre VARCHAR(255),
        jdm_rules VARCHAR(255),
        janino_post VARCHAR(255),
        default_targets VARCHAR(50) DEFAULT 'broadcast',
        enabled BOOLEAN DEFAULT TRUE,
        datas CLOB
    );
    

    Pipeline de traitement

    Le pipeline permet de transformer les messages et determiner leur routage.

    Etapes

    Message entrant
          │
          ▼
    ┌─────────────────┐
    │  1. Janino PRE  │  Transformation du payload
    └────────┬────────┘
             │
             ▼
    ┌─────────────────┐
    │  2. JDM Rules   │  Determination des cibles (routing)
    └────────┬────────┘
             │
             ▼
    ┌─────────────────┐
    │  3. Janino POST │  Transformation finale
    └────────┬────────┘
             │
             ▼
        Routage
       /        \
    Broadcast   Cible
    

    Configuration du pipeline

    Inserez dans grpc_pipeline_configs :

    INSERT INTO grpc_pipeline_configs
    (session_type, janino_pre, jdm_rules, janino_post, default_targets, enabled)
    VALUES
    ('chat', NULL, 'chat_routing', NULL, 'broadcast', TRUE);
    

    Exemple script Janino PRE

    // repository/scripts/java/grpc/chat_filter.java
    public class ChatFilter {
        public Object execute(Map<String, Object> context) {
            Map<String, Object> payload = (Map<String, Object>) context.get("payload");
            String senderId = (String) context.get("senderId");
    
            // Ajouter metadata
            payload.put("processed_at", System.currentTimeMillis());
            payload.put("sender", senderId);
    
            return payload;
        }
    }
    

    Exemple modele JDM pour routing

    {
      "name": "chat_routing",
      "nodes": [
        {
          "id": "input",
          "type": "inputNode",
          "content": {
            "fields": [
              {"field": "payload.target", "type": "string"},
              {"field": "payload.type", "type": "string"}
            ]
          }
        },
        {
          "id": "decision",
          "type": "decisionTableNode",
          "content": {
            "hitPolicy": "first",
            "inputs": [
              {"field": "payload.type"}
            ],
            "outputs": [
              {"field": "targets", "type": "string"},
              {"field": "broadcast", "type": "boolean"}
            ],
            "rules": [
              {"_input": ["private"], "_output": ["${payload.target}", false]},
              {"_input": ["broadcast"], "_output": ["", true]},
              {"_input": ["*"], "_output": ["", true]}
            ]
          }
        }
      ]
    }
    

    Connexions Peer

    Ajouter un peer programmatiquement

    @Autowired
    private PeerConnectionPool peerPool;
    
    public void connectToPeer() {
        // Ajouter et connecter
        boolean connected = peerPool.addPeer("socle-b", "192.168.1.100", 9400);
    
        if (connected) {
            // Ouvrir un stream
            peerPool.openStream("socle-b", message -> {
                // Handler pour messages entrants
                System.out.println("Received: " + message.getPayload());
            });
    
            // Envoyer un message
            SessionMessage msg = SessionMessage.newBuilder()
                .setSessionId("...")
                .setSenderId("local-participant")
                .setKind(MessageKind.DATA)
                .setPayload("{\"text\":\"Hello\"}")
                .build();
    
            peerPool.sendToPeer("socle-b", msg);
        }
    }
    

    Broadcast vers tous les peers

    SessionMessage msg = SessionMessage.newBuilder()
        .setSessionId(sessionId)
        .setSenderId(myId)
        .setKind(MessageKind.DATA)
        .setPayload(jsonPayload)
        .build();
    
    int sent = peerPool.broadcast(msg, null);  // null = inclure tous
    

    Worker gRPC

    Priorites

    Methode Valeur Description
    getStartPriority() 800 Demarre apres Janino (25) et JDM (30)
    getStopPriority() 10 S’arrete tot pour drain des connexions

    Statistiques

    @Autowired
    private GrpcServerWorker grpcWorker;
    
    Map<String, Object> stats = grpcWorker.getStats();
    // {
    //   "running": true,
    //   "port": 9400,
    //   "sessions": { "cached_sessions": 5, "active_streams": 12 },
    //   "messaging": { "messages_received": 1234, "messages_sent": 5678 },
    //   "peers": { "connected_peers": 2, "total_messages_sent": 100 }
    // }
    

    Types de sessions supportes

    Type Description
    chat Communication temps reel
    sync Synchronisation de donnees
    broadcast Diffusion un-vers-plusieurs
    pipeline Traitement en chaine
    custom Type personnalise

    Securite

    TLS (a venir)

    Le support TLS sera ajoute dans une version future :

    socle:
      grpc:
        tls:
          enabled: true
          cert-path: /path/to/server.crt
          key-path: /path/to/server.key
          ca-path: /path/to/ca.crt  # Pour mTLS
    

    Authentification

    L’authentification peut etre implementee via :

    • Metadata gRPC (tokens dans headers)
    • Intercepteurs personnalises
    • Integration avec le module Auth existant

    Monitoring

    Metriques exposees

    Les metriques sont disponibles via l’endpoint Prometheus /actuator/prometheus :

    • grpc_sessions_active : Nombre de sessions actives
    • grpc_messages_received_total : Total messages recus
    • grpc_messages_sent_total : Total messages envoyes
    • grpc_peers_connected : Nombre de peers connectes

    Logs

    [grpc_server] Started on port 9400
    [grpc_session] Session created: abc-123 (type=chat, owner=user1)
    [grpc_session] Participant joined: user2 -> abc-123 (total: 2)
    [grpc_comm] Message received: session=abc-123, sender=user1, kind=DATA
    [grpc_pipeline] Executed for session abc-123 (targets=broadcast)
    

    Troubleshooting

    Le serveur ne demarre pas

    1. Verifiez que le port 9400 n’est pas utilise
    2. Verifiez GRPC_ENABLED=true
    3. Consultez les logs pour les erreurs d’initialisation

    Sessions expirent trop vite

    Augmentez le TTL :

    socle:
      grpc:
        session:
          ttl-seconds: 7200  # 2 heures
    

    Messages non delivres

    1. Verifiez que le destinataire a un stream actif
    2. Verifiez les logs du pipeline pour les erreurs
    3. Verifiez la configuration du routing dans grpc_pipeline_configs

    Erreurs de connexion peer

    1. Verifiez la connectivite reseau
    2. Verifiez que le peer a gRPC active
    3. Augmentez le timeout de connexion :
    socle:
      grpc:
        peer:
          connection-timeout-ms: 10000
    

    Structure des fichiers

    src/main/java/eu/lmvi/socle/grpc/
    ├── GrpcConfiguration.java        # Configuration Spring
    ├── GrpcServerWorker.java         # Worker principal
    ├── session/
    │   ├── Session.java              # Record session
    │   ├── Participant.java          # Record participant
    │   ├── SessionStatus.java        # Enum ACTIVE/CLOSING/CLOSED/EXPIRED
    │   ├── SessionManager.java       # Gestion sessions + streams
    │   └── SessionRedisRepository.java  # Persistance Redis
    ├── pipeline/
    │   ├── PipelineConfig.java       # Config pipeline
    │   ├── PipelineResult.java       # Resultat pipeline
    │   └── PipelineExecutor.java     # Execution Janino + JDM
    ├── peer/
    │   ├── PeerConnection.java       # Connexion unique
    │   └── PeerConnectionPool.java   # Pool avec reconnexion
    └── service/
        ├── SocleCommService.java     # Streaming bidirectionnel
        ├── SessionServiceImpl.java   # CRUD sessions
        └── DiscoveryServiceImpl.java # Discovery + Ping
    
    src/main/proto/
    └── socle_comm.proto              # Definition Protocol Buffers
    
  • Socle V004 – Démarrage Rapide

    Socle V004 – Démarrage Rapide

    03 – Guide de Démarrage Rapide

    Version : 4.0.0 Date : 2025-01-25 Temps estimé : 5 minutes

    1. Prérequis

    • JDK 21+ installé
    • Maven 3.9+ installé
    • Git (optionnel)

    Vérification :

    java -version   # java version "21.x.x"
    mvn -version    # Maven 3.9.x
    

    2. Installation

    Option A : Cloner le projet

    git clone <repo>/socle-v004.git
    cd socle-v004
    

    Option B : Créer un nouveau projet

    mvn archetype:generate \
      -DgroupId=com.mycompany \
      -DartifactId=my-socle-app \
      -DarchetypeArtifactId=maven-archetype-quickstart \
      -DinteractiveMode=false
    
    cd my-socle-app
    # Ajouter la dépendance socle-v004 dans pom.xml
    

    3. Configuration minimale

    3.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
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>eu.lmvi</groupId>
        <artifactId>socle-v004</artifactId>
        <version>4.0.0</version>
        <packaging>jar</packaging>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.2.1</version>
        </parent>
    
        <properties>
            <java.version>21</java.version>
        </properties>
    
        <dependencies>
            <!-- Spring Boot Web (sans Logback) -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-logging</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!-- Log4j2 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-log4j2</artifactId>
            </dependency>
    
            <!-- LMAX Disruptor (AsyncLoggers) -->
            <dependency>
                <groupId>com.lmax</groupId>
                <artifactId>disruptor</artifactId>
                <version>4.0.0</version>
            </dependency>
    
            <!-- H2 Database -->
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <version>2.2.224</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    3.2 application.yml

    spring:
      application:
        name: my-socle-app
    
    socle:
      app_name: ${APP_NAME:my-socle-app}
      version: 4.0.0
      env_name: ${ENV_NAME:DEV}
      region: ${REGION:local}
    
      # HTTP
      http_port: ${HTTP_PORT:8080}
    
      # KV Bus
      kv_impl: in_memory
    
      # Supervisor
      supervisor_enabled: true
    
      # H2 TechDB
      techdb:
        enabled: true
        url: jdbc:h2:file:./data/techdb;MODE=PostgreSQL
        console:
          enabled: true
          path: /h2-console
    
    logging:
      config: classpath:log4j2.xml
    

    3.3 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>
    

    4. Créer un Worker simple

    package com.mycompany.worker;
    
    import eu.lmvi.socle.worker.Worker;
    import eu.lmvi.socle.supervisor.Supervisor;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.util.Map;
    
    @Component
    public class HelloWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(HelloWorker.class);
    
        @Autowired
        private Supervisor supervisor;  // Injection du Supervisor
    
        private int counter = 0;
        private volatile boolean running = false;
    
        @Override
        public String getName() {
            return "hello-worker";
        }
    
        @Override
        public void initialize() {
            log.info("HelloWorker initializing...");
        }
    
        @Override
        public void start() {
            running = true;
            log.info("HelloWorker started!");
        }
    
        @Override
        public void doWork() {
            if (!running) return;  // Vérifier le flag d'arrêt
    
            try {
                counter++;
                log.info("Hello from worker! Count: {}", counter);
    
                // IMPORTANT: Envoyer un heartbeat avec métriques
                supervisor.heartbeat(getName(), getStats());
    
            } catch (Exception e) {
                log.error("Erreur dans doWork", e);
                // NE PAS propager l'exception
            }
        }
    
        @Override
        public void stop() {
            running = false;
            log.info("HelloWorker stopped. Final count: {}", counter);
        }
    
        @Override
        public boolean isHealthy() {
            return running;
        }
    
        @Override
        public Map<String, Object> getStats() {
            return Map.of(
                "counter", counter,
                "running", running
            );
        }
    
        @Override
        public long getCycleIntervalMs() {
            return 5000; // doWork() toutes les 5 secondes
        }
    }
    

    5. Build et Run

    # Build
    mvn clean package -DskipTests
    
    # Run
    java -jar target/socle-v004-4.0.0.jar
    
    # Ou avec variables d'environnement
    APP_NAME=my-app ENV_NAME=PROD java -jar target/socle-v004-4.0.0.jar
    

    6. Vérification

    Health check

    curl http://localhost:8080/health
    

    Réponse attendue :

    {
      "status": "UP",
      "app": "my-socle-app",
      "version": "4.0.0"
    }
    

    H2 Console (dev)

    Ouvrir : http://localhost:8080/h2-console

    • JDBC URL : jdbc:h2:file:./data/techdb
    • User : socle
    • Password : socle

    Logs

    Vous devriez voir :

    INFO  [main] MainOrchestratorProcess - MOP démarré
    INFO  [main] HelloWorker - HelloWorker started!
    INFO  [scheduler-1] HelloWorker - Hello from worker! Count: 1
    INFO  [scheduler-1] HelloWorker - Hello from worker! Count: 2
    

    StatusDashboard (supervision)

    Ouvrir : http://localhost:9374/

    Ce dashboard affiche en temps réel :

    • L’état de chaque worker (RUNNING, DEGRADED, UNHEALTHY)
    • Les métriques transmises par les heartbeats
    • L’historique des changements d’état

    7. Prochaines étapes

    Document Description
    04-CONFIGURATION Configuration complète
    05-WORKERS Guide des Workers
    08-SUPERVISOR Supervision et heartbeats
    21-H2-TECHDB Base H2 TechDB
    22-LOG4J2-LOGFORWARDER Logging centralisé
    27-STATUS-DASHBOARD Dashboard de supervision
    GUIDE-METHODOLOGIQUE Bonnes pratiques

    8. Troubleshooting

    Port déjà utilisé

    # Changer le port
    HTTP_PORT=9090 java -jar target/socle-v004-4.0.0.jar
    

    H2 Console inaccessible

    Vérifier que socle.techdb.console.enabled: true dans application.yml.

    Logs non visibles

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

  • Socle V004 – Client Authentification

    Socle V004 – Client Authentification

    23 – Client Authentification JWT (Nouveauté V4)

    Version : 4.0.0 Date : 2025-12-09

    1. Introduction

    Le SocleAuthClient est un client d’authentification JWT intégré au Socle V4 pour communiquer avec les services centraux (LogHub, Registry, etc.).

    Pattern d’authentification

    ┌─────────────────┐                    ┌─────────────────┐
    │   Application   │                    │  Auth Server    │
    │   Socle V4      │                    │  (central)      │
    └────────┬────────┘                    └────────┬────────┘
             │                                      │
             │  1. POST /auth/login                 │
             │     {sourceName, apiKey}             │
             │─────────────────────────────────────►│
             │                                      │
             │  2. {accessToken, refreshToken}      │
             │◄─────────────────────────────────────│
             │                                      │
             │  3. Requêtes avec Bearer token       │
             │  Authorization: Bearer <accessToken> │
             │─────────────────────────────────────►│ Services
             │                                      │
             │  4. POST /auth/refresh (auto)        │
             │     {refreshToken}                   │
             │─────────────────────────────────────►│
             │                                      │
             │  5. {accessToken (new)}              │
             │◄─────────────────────────────────────│
    

    2. Configuration

    2.1 application.yml

    socle:
      auth:
        enabled: ${AUTH_ENABLED:false}
        server-url: ${AUTH_SERVER_URL:https://auth.lmvi.org}
        source-name: ${SOURCE_NAME:${socle.app_name}}
        api-key: ${API_KEY:}
        access-token-buffer-seconds: 60
        connect-timeout-ms: 10000
        read-timeout-ms: 30000
    

    2.2 Variables d’environnement

    Variable Description Défaut
    AUTH_ENABLED Activer l’authentification false
    AUTH_SERVER_URL URL du serveur d’auth
    SOURCE_NAME Identifiant du client ${APP_NAME}
    API_KEY Clé API (secret)

    3. Interface SocleAuthClient

    package eu.lmvi.socle.client.auth;
    
    /**
     * Client d'authentification Socle V4
     */
    public interface SocleAuthClient {
    
        /**
         * Login initial avec API Key
         * @return Tokens d'accès et de refresh
         * @throws AuthenticationException si échec
         */
        AuthTokens login() throws AuthenticationException;
    
        /**
         * Refresh du token d'accès
         * @param refreshToken Token de refresh
         * @return Nouveaux tokens
         * @throws AuthenticationException si échec
         */
        AuthTokens refresh(String refreshToken) throws AuthenticationException;
    
        /**
         * Obtenir un token d'accès valide (avec refresh auto si nécessaire)
         * @return Token d'accès valide
         * @throws AuthenticationException si échec
         */
        String getValidAccessToken() throws AuthenticationException;
    
        /**
         * Vérifie si le client est authentifié
         * @return true si un token valide existe
         */
        boolean isAuthenticated();
    
        /**
         * Invalide les tokens courants
         */
        void logout();
    }
    

    4. DTOs

    4.1 AuthTokens

    package eu.lmvi.socle.client.auth;
    
    public record AuthTokens(
        String accessToken,
        String refreshToken,
        Instant accessTokenExpiry,
        Instant refreshTokenExpiry
    ) {
        public boolean isAccessTokenExpired() {
            return Instant.now().isAfter(accessTokenExpiry);
        }
    
        public boolean isAccessTokenExpiringSoon(int bufferSeconds) {
            return Instant.now().plusSeconds(bufferSeconds).isAfter(accessTokenExpiry);
        }
    
        public boolean isRefreshTokenExpired() {
            return Instant.now().isAfter(refreshTokenExpiry);
        }
    }
    

    4.2 LoginRequest / LoginResponse

    // Request
    public record LoginRequest(
        String sourceName,
        String apiKey
    ) {}
    
    // Response
    public record LoginResponse(
        String accessToken,
        String refreshToken,
        int expiresIn,        // secondes
        int refreshExpiresIn  // secondes
    ) {}
    

    5. Implémentation AuthTokenManager

    package eu.lmvi.socle.client.auth;
    
    @Component
    @ConditionalOnProperty(name = "socle.auth.enabled", havingValue = "true")
    public class AuthTokenManager implements SocleAuthClient {
    
        private static final Logger log = LoggerFactory.getLogger(AuthTokenManager.class);
    
        private final SocleConfiguration config;
        private final OkHttpClient httpClient;
        private final ObjectMapper objectMapper;
    
        private volatile AuthTokens currentTokens;
        private final ReentrantLock refreshLock = new ReentrantLock();
    
        public AuthTokenManager(SocleConfiguration config) {
            this.config = config;
            this.objectMapper = new ObjectMapper();
            this.objectMapper.registerModule(new JavaTimeModule());
    
            this.httpClient = new OkHttpClient.Builder()
                .connectTimeout(config.getAuthConnectTimeoutMs(), TimeUnit.MILLISECONDS)
                .readTimeout(config.getAuthReadTimeoutMs(), TimeUnit.MILLISECONDS)
                .build();
        }
    
        @Override
        public AuthTokens login() throws AuthenticationException {
            log.info("Login to auth server: {}", config.getAuthServerUrl());
    
            LoginRequest request = new LoginRequest(
                config.getSourceName(),
                config.getApiKey()
            );
    
            try {
                String json = objectMapper.writeValueAsString(request);
    
                Request httpRequest = new Request.Builder()
                    .url(config.getAuthServerUrl() + "/api/v1/auth/login")
                    .post(RequestBody.create(json, MediaType.parse("application/json")))
                    .build();
    
                try (Response response = httpClient.newCall(httpRequest).execute()) {
                    if (!response.isSuccessful()) {
                        throw new AuthenticationException("Login failed: " + response.code());
                    }
    
                    LoginResponse loginResponse = objectMapper.readValue(
                        response.body().string(),
                        LoginResponse.class
                    );
    
                    currentTokens = new AuthTokens(
                        loginResponse.accessToken(),
                        loginResponse.refreshToken(),
                        Instant.now().plusSeconds(loginResponse.expiresIn()),
                        Instant.now().plusSeconds(loginResponse.refreshExpiresIn())
                    );
    
                    log.info("Login successful, token expires in {} seconds", loginResponse.expiresIn());
                    return currentTokens;
                }
            } catch (IOException e) {
                throw new AuthenticationException("Login failed", e);
            }
        }
    
        @Override
        public AuthTokens refresh(String refreshToken) throws AuthenticationException {
            log.debug("Refreshing access token");
    
            RefreshRequest request = new RefreshRequest(refreshToken);
    
            try {
                String json = objectMapper.writeValueAsString(request);
    
                Request httpRequest = new Request.Builder()
                    .url(config.getAuthServerUrl() + "/api/v1/auth/refresh")
                    .post(RequestBody.create(json, MediaType.parse("application/json")))
                    .build();
    
                try (Response response = httpClient.newCall(httpRequest).execute()) {
                    if (!response.isSuccessful()) {
                        // Refresh failed, need to re-login
                        log.warn("Refresh failed, attempting re-login");
                        return login();
                    }
    
                    RefreshResponse refreshResponse = objectMapper.readValue(
                        response.body().string(),
                        RefreshResponse.class
                    );
    
                    currentTokens = new AuthTokens(
                        refreshResponse.accessToken(),
                        currentTokens.refreshToken(),  // Keep same refresh token
                        Instant.now().plusSeconds(refreshResponse.expiresIn()),
                        currentTokens.refreshTokenExpiry()
                    );
    
                    log.debug("Token refreshed, new expiry in {} seconds", refreshResponse.expiresIn());
                    return currentTokens;
                }
            } catch (IOException e) {
                throw new AuthenticationException("Refresh failed", e);
            }
        }
    
        @Override
        public String getValidAccessToken() throws AuthenticationException {
            // First time - login
            if (currentTokens == null) {
                login();
                return currentTokens.accessToken();
            }
    
            // Refresh token expired - need full re-login
            if (currentTokens.isRefreshTokenExpired()) {
                log.info("Refresh token expired, re-login required");
                login();
                return currentTokens.accessToken();
            }
    
            // Access token expiring soon - refresh
            int bufferSeconds = config.getAccessTokenBufferSeconds();
            if (currentTokens.isAccessTokenExpiringSoon(bufferSeconds)) {
                refreshLock.lock();
                try {
                    // Double-check after acquiring lock
                    if (currentTokens.isAccessTokenExpiringSoon(bufferSeconds)) {
                        refresh(currentTokens.refreshToken());
                    }
                } finally {
                    refreshLock.unlock();
                }
            }
    
            return currentTokens.accessToken();
        }
    
        @Override
        public boolean isAuthenticated() {
            return currentTokens != null && !currentTokens.isAccessTokenExpired();
        }
    
        @Override
        public void logout() {
            currentTokens = null;
            log.info("Logged out");
        }
    }
    

    6. Utilisation

    6.1 Injection

    @Service
    public class MonService {
    
        @Autowired(required = false)
        private SocleAuthClient authClient;
    
        public void callSecuredApi() throws Exception {
            if (authClient == null || !authClient.isAuthenticated()) {
                throw new IllegalStateException("Auth not configured");
            }
    
            String token = authClient.getValidAccessToken();
    
            // Utiliser le token
            Request request = new Request.Builder()
                .url("https://api.mycompany.com/secured")
                .header("Authorization", "Bearer " + token)
                .build();
    
            // ...
        }
    }
    

    6.2 Intégration avec LogForwarder

    // Dans HttpLogTransport
    public class HttpLogTransport implements LogTransport {
    
        private final SocleAuthClient authClient;
    
        @Override
        public void send(List<LogEntry> entries) throws Exception {
            String token = authClient.getValidAccessToken();
    
            Request request = new Request.Builder()
                .url(logHubUrl)
                .header("Authorization", "Bearer " + token)
                .post(RequestBody.create(toJson(entries), JSON))
                .build();
    
            // ...
        }
    }
    

    6.3 Intégration avec MOP

    // Dans MainOrchestratorProcess.start()
    if (authClient != null && config.isAuthEnabled()) {
        log.info("[step:auth] Login auprès du serveur d'auth");
        try {
            authClient.login();
        } catch (AuthenticationException e) {
            log.error("Auth failed, continuing without auth", e);
        }
    }
    

    7. Gestion des erreurs

    7.1 AuthenticationException

    public class AuthenticationException extends Exception {
        public AuthenticationException(String message) {
            super(message);
        }
    
        public AuthenticationException(String message, Throwable cause) {
            super(message, cause);
        }
    }
    

    7.2 Retry automatique

    Le AuthTokenManager gère automatiquement :

    • Le refresh avant expiration
    • Le re-login si le refresh échoue
    • Le re-login si le refresh token expire

    7.3 Fallback sans auth

    if (authClient == null) {
        log.warn("Auth not configured, proceeding without authentication");
        // Continuer sans auth (pour dev local)
    }
    

    8. Sécurité

    8.1 Stockage de l’API Key

    # NE PAS mettre dans le code
    # Utiliser des variables d'environnement
    export API_KEY="xxx-secret-key"
    
    # Ou un gestionnaire de secrets
    # Kubernetes Secret, AWS Secrets Manager, etc.
    

    8.2 Tokens en mémoire

    Les tokens sont stockés en mémoire uniquement :

    • Jamais persistés sur disque
    • Invalidés au restart
    • Thread-safe

    8.3 HTTPS obligatoire

    socle:
      auth:
        server-url: https://auth.mycompany.com  # HTTPS obligatoire
    

    9. Monitoring

    9.1 Métriques

    // Exposées via /metrics
    socle_auth_login_total          # Nombre de logins
    socle_auth_login_errors_total   # Erreurs de login
    socle_auth_refresh_total        # Nombre de refresh
    socle_auth_token_expiry_seconds # Temps avant expiration
    

    9.2 Logs

    INFO  - Login to auth server: https://auth.lmvi.org
    INFO  - Login successful, token expires in 900 seconds
    DEBUG - Refreshing access token
    DEBUG - Token refreshed, new expiry in 900 seconds
    WARN  - Refresh failed, attempting re-login
    

    10. Troubleshooting

    Connection refused

    AuthenticationException: Login failed: Connection refused
    

    Vérifier :

    • AUTH_SERVER_URL est correct
    • Le serveur d’auth est accessible
    • Les ports sont ouverts

    Invalid API Key

    AuthenticationException: Login failed: 401
    

    Vérifier :

    • API_KEY est correct
    • SOURCE_NAME est enregistré côté serveur

    Token expired

    Si les tokens expirent trop vite :

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

    11. Références

  • Socle V004 – Sécurité

    Socle V004 – Sécurité

    10 – Security

    Version : 4.0.0 Date : 2025-12-09

    1. Introduction

    Le Socle V4 intègre plusieurs mécanismes de sécurité :

    • Authentification Admin API (Basic Auth)
    • Client JWT pour services externes (nouveauté V4)
    • Filtrage des endpoints sensibles
    • Gestion sécurisée des secrets

    2. Authentification Admin API

    2.1 Configuration

    socle:
      admin:
        enabled: true
        auth:
          enabled: ${ADMIN_AUTH_ENABLED:false}
          username: ${ADMIN_USERNAME:admin}
          password: ${ADMIN_PASSWORD:}
    

    2.2 AdminAuthFilter

    package eu.lmvi.socle.security;
    
    @Component
    @Order(1)
    public class AdminAuthFilter implements Filter {
    
        private final SocleConfiguration config;
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
    
            // Skip si auth désactivée
            if (!config.getAdmin().getAuth().isEnabled()) {
                chain.doFilter(request, response);
                return;
            }
    
            // Skip les endpoints publics
            String path = httpRequest.getRequestURI();
            if (isPublicEndpoint(path)) {
                chain.doFilter(request, response);
                return;
            }
    
            // Vérifier l'authentification pour /admin/*
            if (path.startsWith("/admin")) {
                String authHeader = httpRequest.getHeader("Authorization");
    
                if (!isValidAuth(authHeader)) {
                    httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"Admin API\"");
                    return;
                }
            }
    
            chain.doFilter(request, response);
        }
    
        private boolean isPublicEndpoint(String path) {
            return path.equals("/admin/health") || path.equals("/admin/health/live");
        }
    
        private boolean isValidAuth(String authHeader) {
            if (authHeader == null || !authHeader.startsWith("Basic ")) {
                return false;
            }
    
            String base64Credentials = authHeader.substring("Basic ".length());
            String credentials = new String(Base64.getDecoder().decode(base64Credentials));
            String[] parts = credentials.split(":", 2);
    
            if (parts.length != 2) {
                return false;
            }
    
            return parts[0].equals(config.getAdmin().getAuth().getUsername())
                && parts[1].equals(config.getAdmin().getAuth().getPassword());
        }
    }
    

    2.3 Utilisation

    # Sans auth
    curl http://localhost:8080/admin/health
    
    # Avec auth
    curl -u admin:secret http://localhost:8080/admin/workers
    

    3. JWT Auth Client (Nouveauté V4)

    3.1 Principe

    Le SocleAuthClient permet aux applications Socle de s’authentifier auprès de services centraux (LogHub, Registry, etc.).

    Application Socle  ──────►  Auth Server  ──────►  Services sécurisés
          │                          │                      │
          │  1. Login (API Key)      │                      │
          │─────────────────────────►│                      │
          │                          │                      │
          │  2. JWT tokens           │                      │
          │◄─────────────────────────│                      │
          │                          │                      │
          │  3. Requêtes avec Bearer │                      │
          │──────────────────────────────────────────────────►
    

    3.2 Configuration

    socle:
      auth:
        enabled: ${AUTH_ENABLED:false}
        server-url: ${AUTH_SERVER_URL:https://auth.mycompany.com}
        source-name: ${SOURCE_NAME:${socle.app_name}}
        api-key: ${API_KEY:}
        access-token-buffer-seconds: 60
    

    3.3 Utilisation

    @Service
    public class SecuredApiClient {
    
        @Autowired(required = false)
        private SocleAuthClient authClient;
    
        public void callSecuredApi() {
            if (authClient == null || !authClient.isAuthenticated()) {
                throw new SecurityException("Authentication required");
            }
    
            String token = authClient.getValidAccessToken();
    
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.mycompany.com/data"))
                .header("Authorization", "Bearer " + token)
                .build();
    
            // ...
        }
    }
    

    Voir 23-AUTH-CLIENT pour la documentation complète.

    4. Gestion des secrets

    4.1 Variables d’environnement

    # JAMAIS dans le code ou les fichiers committes
    export ADMIN_PASSWORD="super-secret-password"
    export API_KEY="my-api-key"
    export REDIS_PASSWORD="redis-password"
    export TECHDB_PASSWORD="techdb-password"
    

    4.2 Docker Secrets

    # docker-compose.yml
    services:
      app:
        image: socle-v4:latest
        secrets:
          - admin_password
          - api_key
        environment:
          - ADMIN_PASSWORD_FILE=/run/secrets/admin_password
          - API_KEY_FILE=/run/secrets/api_key
    
    secrets:
      admin_password:
        file: ./secrets/admin_password.txt
      api_key:
        file: ./secrets/api_key.txt
    

    4.3 Kubernetes Secrets

    apiVersion: v1
    kind: Secret
    metadata:
      name: socle-secrets
    type: Opaque
    stringData:
      ADMIN_PASSWORD: "super-secret"
      API_KEY: "my-api-key"
    ---
    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          containers:
            - name: app
              envFrom:
                - secretRef:
                    name: socle-secrets
    

    4.4 Configuration sécurisée

    @Configuration
    public class SecretsConfiguration {
    
        @Value("${ADMIN_PASSWORD:#{null}}")
        private String adminPassword;
    
        @Value("${ADMIN_PASSWORD_FILE:#{null}}")
        private String adminPasswordFile;
    
        @PostConstruct
        public void loadSecrets() {
            // Charger depuis fichier si spécifié
            if (adminPasswordFile != null) {
                try {
                    adminPassword = Files.readString(Path.of(adminPasswordFile)).trim();
                } catch (IOException e) {
                    throw new RuntimeException("Failed to load secret from file", e);
                }
            }
        }
    
        public String getAdminPassword() {
            return adminPassword;
        }
    }
    

    5. CORS Configuration

    @Configuration
    public class CorsConfiguration implements WebMvcConfigurer {
    
        @Value("${socle.security.cors.allowed-origins:*}")
        private String allowedOrigins;
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/api/**")
                .allowedOrigins(allowedOrigins.split(","))
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .exposedHeaders("X-Total-Count")
                .allowCredentials(true)
                .maxAge(3600);
        }
    }
    

    6. Rate Limiting

    6.1 Implémentation simple

    @Component
    public class RateLimitFilter implements Filter {
    
        private final KvBus kvBus;
        private final int maxRequests = 100;
        private final Duration window = Duration.ofMinutes(1);
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
    
            String clientId = getClientId(httpRequest);
            String key = "ratelimit:" + clientId + ":" + Instant.now().truncatedTo(ChronoUnit.MINUTES);
    
            long count = kvBus.increment(key);
            if (count == 1) {
                kvBus.setTtl(key, window);
            }
    
            if (count > maxRequests) {
                httpResponse.setStatus(429);
                httpResponse.setHeader("Retry-After", "60");
                httpResponse.getWriter().write("Rate limit exceeded");
                return;
            }
    
            httpResponse.setHeader("X-RateLimit-Limit", String.valueOf(maxRequests));
            httpResponse.setHeader("X-RateLimit-Remaining", String.valueOf(maxRequests - count));
    
            chain.doFilter(request, response);
        }
    
        private String getClientId(HttpServletRequest request) {
            String apiKey = request.getHeader("X-API-Key");
            if (apiKey != null) {
                return apiKey;
            }
            return request.getRemoteAddr();
        }
    }
    

    7. Input Validation

    7.1 Validation des DTOs

    public record CreateOrderRequest(
        @NotNull @Size(min = 1, max = 100)
        String customerId,
    
        @NotEmpty
        List<@Valid OrderItem> items,
    
        @Email
        String notificationEmail
    ) {}
    
    public record OrderItem(
        @NotNull @Size(min = 1, max = 50)
        String productId,
    
        @Min(1) @Max(1000)
        int quantity
    ) {}
    

    7.2 Controller avec validation

    @RestController
    @RequestMapping("/api/orders")
    public class OrderController {
    
        @PostMapping
        public ResponseEntity<Order> createOrder(@Valid @RequestBody CreateOrderRequest request) {
            // request est validé automatiquement
            return ResponseEntity.ok(orderService.create(request));
        }
    }
    

    7.3 Sanitization

    public class InputSanitizer {
    
        private static final Pattern SAFE_STRING = Pattern.compile("^[a-zA-Z0-9-_]+$");
    
        public static String sanitizeId(String input) {
            if (input == null) return null;
            if (!SAFE_STRING.matcher(input).matches()) {
                throw new IllegalArgumentException("Invalid ID format");
            }
            return input;
        }
    
        public static String sanitizeForLog(String input) {
            if (input == null) return null;
            return input.replaceAll("[\n\r\t]", "_");
        }
    }
    

    8. Logging sécurisé

    8.1 Ne pas logger les secrets

    // MAUVAIS
    log.info("Connecting with password: {}", password);
    log.info("API Key: {}", apiKey);
    log.info("Request: {}", requestWithSensitiveData);
    
    // BON
    log.info("Connecting to database");
    log.info("Using API Key: {}...", apiKey.substring(0, 4));
    log.info("Request received for user: {}", sanitizeForLog(userId));
    

    8.2 Pattern pour masquer les données sensibles

    public class SecureLogger {
    
        private static final Set<String> SENSITIVE_FIELDS = Set.of(
            "password", "apiKey", "token", "secret", "credential"
        );
    
        public static String maskSensitiveData(Map<String, Object> data) {
            Map<String, Object> masked = new HashMap<>();
            for (Map.Entry<String, Object> entry : data.entrySet()) {
                if (SENSITIVE_FIELDS.contains(entry.getKey().toLowerCase())) {
                    masked.put(entry.getKey(), "***MASKED***");
                } else {
                    masked.put(entry.getKey(), entry.getValue());
                }
            }
            return masked.toString();
        }
    }
    

    9. Headers de sécurité

    @Component
    public class SecurityHeadersFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletResponse httpResponse = (HttpServletResponse) response;
    
            // Prevent clickjacking
            httpResponse.setHeader("X-Frame-Options", "DENY");
    
            // XSS protection
            httpResponse.setHeader("X-Content-Type-Options", "nosniff");
            httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
    
            // HSTS (si HTTPS)
            httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
    
            // Content Security Policy
            httpResponse.setHeader("Content-Security-Policy", "default-src 'self'");
    
            chain.doFilter(request, response);
        }
    }
    

    10. Audit logging

    @Aspect
    @Component
    public class AuditAspect {
    
        private static final Logger auditLog = LoggerFactory.getLogger("AUDIT");
    
        @Around("@annotation(Audited)")
        public Object audit(ProceedingJoinPoint joinPoint) throws Throwable {
            String method = joinPoint.getSignature().getName();
            String user = getCurrentUser();
            Instant start = Instant.now();
    
            try {
                Object result = joinPoint.proceed();
                auditLog.info("SUCCESS | user={} | method={} | duration={}ms",
                    user, method, Duration.between(start, Instant.now()).toMillis());
                return result;
            } catch (Exception e) {
                auditLog.warn("FAILURE | user={} | method={} | error={}",
                    user, method, e.getMessage());
                throw e;
            }
        }
    
        private String getCurrentUser() {
            // Récupérer l'utilisateur du contexte
            return "system";
        }
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Audited {}
    

    11. Checklist de sécurité

    Configuration

    • [ ] ADMIN_AUTH_ENABLED=true en production
    • [ ] Mots de passe forts et uniques
    • [ ] Secrets via variables d’environnement ou secret manager
    • [ ] HTTPS activé
    • [ ] CORS configuré correctement

    Code

    • [ ] Validation de tous les inputs
    • [ ] Pas de secrets dans les logs
    • [ ] Headers de sécurité activés
    • [ ] Rate limiting en place
    • [ ] Audit logging activé

    Infrastructure

    • [ ] Firewall configuré
    • [ ] Ports non nécessaires fermés
    • [ ] H2 Console désactivée en production
    • [ ] Accès admin restreint

    12. Références

  • Socle V004 – Plan de Documentation

    Socle V004 – Plan de Documentation

    Plan de Documentation – Socle V4

    Version : 4.0.0 Date : 2025-01-25

    Structure de la documentation

    # Document Description Statut
    00 PLAN-DOCUMENTATION Ce document – Index de la documentation Done
    01 INTRODUCTION Présentation du Socle V4 et philosophie Done
    02 ARCHITECTURE Architecture technique et composants Done
    03 QUICKSTART Guide de démarrage rapide (5 min) Done
    04 CONFIGURATION Référence complète de configuration Done
    05 WORKERS Guide des Workers et lifecycle Done
    06 KV-BUS Guide du Key-Value Bus Done
    07 SHARED-DATA Guide du SharedDataRegistry Done
    08 SUPERVISOR Supervision et heartbeats Done
    09 PIPELINE Pipeline Engine V1 et V2 (Queue/Claim/Ack, DLQ) Done
    10 SECURITY Sécurité, Auth, Rate Limiting Done
    11 RESILIENCE Circuit Breaker et Retry Done
    12 SCHEDULER Scheduling cron et interval Done
    13 TLS-HTTPS Configuration TLS/HTTPS Done
    14 ADMIN-API API REST d’administration Done
    15 METRICS Métriques et Prometheus Done
    16 KUBERNETES Déploiement Kubernetes Done
    17 HOWTO Guides pratiques Done
    18 TROUBLESHOOTING Résolution de problèmes Done
    19 EXEMPLES Exemples de code Done
    20 PLUGINS Système de plugins Done
    21 H2-TECHDB Base technique H2 (V4) Done
    22 LOG4J2-LOGFORWARDER Log4j2 et LogForwarder (V4) Done
    23 AUTH-CLIENT Client authentification JWT (V4) Done
    24 WORKER-REGISTRY Client Worker Registry (V4) Done
    25 MIGRATION-V3-V4 Guide de migration V3 → V4 Done
    26 GRAALVM-JAVASCRIPT GraalVM CE et GraalJS pour scripts JS Done
    27 STATUS-DASHBOARD Dashboard HTML de supervision (port 9374) Done
    29 JANINO Scripts Java compiles dynamiquement Done
    30 EVENTBUS-WORKERS Workers event-driven Done
    31 GRPC-INTER-SOCLES Communication gRPC entre Socles Done

    Nouveautés V4

    Les documents 21 à 30 sont spécifiques au Socle V4 :

    • 21-H2-TECHDB : Base embarquée H2 pour état technique
    • 22-LOG4J2-LOGFORWARDER : Migration Logback → Log4j2 + centralisation logs
    • 23-AUTH-CLIENT : Client JWT pour services centraux
    • 24-WORKER-REGISTRY : Auto-enregistrement des workers
    • 25-MIGRATION-V3-V4 : Guide de migration depuis V3
    • 26-GRAALVM-JAVASCRIPT : GraalVM CE 21 et GraalJS pour exécution JavaScript
    • 27-STATUS-DASHBOARD : Dashboard HTML de supervision temps réel sur port 9374
    • 29-JANINO : Compilation dynamique de scripts Java
    • 30-EVENTBUS-WORKERS : Workers orientés événements
    • 31-GRPC-INTER-SOCLES : Communication gRPC bidirectionnelle entre Socles

    Guide Méthodologique

    Un guide méthodologique complet est disponible pour aider les développeurs à implémenter leurs solutions :

    Ce document répond à la question : « Je dois implémenter X, comment je fais ? »

    Conventions

    • Tous les fichiers sont en Markdown
    • Les exemples de code sont en Java 21
    • Les configurations sont en YAML
    • Les commandes sont pour Linux/macOS (adaptables Windows)
  • Socle V004 – Résilience

    Socle V004 – Résilience

    11 – Resilience (Circuit Breaker & Retry)

    Version : 4.0.0 Date : 2025-12-09

    1. Introduction

    Le Socle V4 intègre des patterns de résilience pour gérer les défaillances des systèmes externes :

    • Retry : Réessayer les opérations échouées
    • Circuit Breaker : Protéger contre les cascades de défaillances

    2. Retry Pattern

    2.1 Configuration

    socle:
      resilience:
        retry:
          max-attempts: ${RETRY_MAX_ATTEMPTS:3}
          initial-delay-ms: ${RETRY_INITIAL_DELAY_MS:1000}
          max-delay-ms: ${RETRY_MAX_DELAY_MS:30000}
          multiplier: ${RETRY_MULTIPLIER:2.0}
    

    2.2 Interface RetryTemplate

    package eu.lmvi.socle.resilience;
    
    public interface RetryTemplate {
    
        /**
         * Exécute une opération avec retry
         */
        <T> T execute(Supplier<T> operation) throws RetryExhaustedException;
    
        /**
         * Exécute une opération avec retry et fallback
         */
        <T> T executeWithFallback(Supplier<T> operation, Supplier<T> fallback);
    
        /**
         * Exécute une opération void avec retry
         */
        void executeVoid(Runnable operation) throws RetryExhaustedException;
    }
    

    2.3 Implémentation

    package eu.lmvi.socle.resilience;
    
    @Component
    public class ExponentialBackoffRetryTemplate implements RetryTemplate {
    
        private static final Logger log = LoggerFactory.getLogger(ExponentialBackoffRetryTemplate.class);
    
        private final int maxAttempts;
        private final long initialDelayMs;
        private final long maxDelayMs;
        private final double multiplier;
    
        public ExponentialBackoffRetryTemplate(SocleConfiguration config) {
            this.maxAttempts = config.getResilience().getRetry().getMaxAttempts();
            this.initialDelayMs = config.getResilience().getRetry().getInitialDelayMs();
            this.maxDelayMs = config.getResilience().getRetry().getMaxDelayMs();
            this.multiplier = config.getResilience().getRetry().getMultiplier();
        }
    
        @Override
        public <T> T execute(Supplier<T> operation) throws RetryExhaustedException {
            Exception lastException = null;
            long delay = initialDelayMs;
    
            for (int attempt = 1; attempt <= maxAttempts; attempt++) {
                try {
                    return operation.get();
                } catch (Exception e) {
                    lastException = e;
                    log.warn("Attempt {}/{} failed: {}", attempt, maxAttempts, e.getMessage());
    
                    if (attempt < maxAttempts) {
                        sleep(delay);
                        delay = Math.min((long) (delay * multiplier), maxDelayMs);
                    }
                }
            }
    
            throw new RetryExhaustedException(
                "Operation failed after " + maxAttempts + " attempts",
                lastException
            );
        }
    
        @Override
        public <T> T executeWithFallback(Supplier<T> operation, Supplier<T> fallback) {
            try {
                return execute(operation);
            } catch (RetryExhaustedException e) {
                log.warn("All retries exhausted, using fallback");
                return fallback.get();
            }
        }
    
        @Override
        public void executeVoid(Runnable operation) throws RetryExhaustedException {
            execute(() -> {
                operation.run();
                return null;
            });
        }
    
        private void sleep(long ms) {
            try {
                Thread.sleep(ms);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Retry interrupted", e);
            }
        }
    }
    

    2.4 Utilisation

    @Service
    public class ExternalApiService {
    
        @Autowired
        private RetryTemplate retryTemplate;
    
        public Data fetchData(String id) {
            return retryTemplate.execute(() -> {
                // Appel qui peut échouer
                return httpClient.get("/data/" + id);
            });
        }
    
        public Data fetchDataWithFallback(String id) {
            return retryTemplate.executeWithFallback(
                () -> httpClient.get("/data/" + id),
                () -> getCachedData(id)  // Fallback vers le cache
            );
        }
    }
    

    3. Circuit Breaker Pattern

    3.1 États

            succès
         ┌─────────────────┐
         │                 │
         ▼                 │
    ┌─────────┐      ┌─────┴─────┐      ┌─────────┐
    │  CLOSED │─────►│   OPEN    │─────►│HALF_OPEN│
    └────┬────┘      └───────────┘      └────┬────┘
         │  échecs        │ timeout          │
         │  threshold     │                  │
         │                │                  │
         └────────────────┴──────────────────┘
                  retour succès
    
    • CLOSED : Fonctionnement normal, les requêtes passent
    • OPEN : Circuit ouvert, les requêtes échouent immédiatement
    • HALF_OPEN : Test de reprise, quelques requêtes passent

    3.2 Configuration

    socle:
      resilience:
        circuit-breaker:
          failure-threshold: ${CB_FAILURE_THRESHOLD:5}
          success-threshold: ${CB_SUCCESS_THRESHOLD:3}
          timeout-ms: ${CB_TIMEOUT_MS:60000}
          half-open-requests: ${CB_HALF_OPEN_REQUESTS:3}
    

    3.3 Interface CircuitBreaker

    package eu.lmvi.socle.resilience;
    
    public interface CircuitBreaker {
    
        /**
         * Nom du circuit
         */
        String getName();
    
        /**
         * État actuel
         */
        CircuitState getState();
    
        /**
         * Exécute une opération protégée
         */
        <T> T execute(Supplier<T> operation) throws CircuitBreakerOpenException;
    
        /**
         * Exécute avec fallback
         */
        <T> T executeWithFallback(Supplier<T> operation, Supplier<T> fallback);
    
        /**
         * Force l'ouverture
         */
        void forceOpen();
    
        /**
         * Force la fermeture
         */
        void forceClose();
    
        /**
         * Reset les compteurs
         */
        void reset();
    
        /**
         * Métriques
         */
        CircuitBreakerMetrics getMetrics();
    }
    
    public enum CircuitState {
        CLOSED,
        OPEN,
        HALF_OPEN
    }
    

    3.4 Implémentation

    package eu.lmvi.socle.resilience;
    
    public class DefaultCircuitBreaker implements CircuitBreaker {
    
        private static final Logger log = LoggerFactory.getLogger(DefaultCircuitBreaker.class);
    
        private final String name;
        private final int failureThreshold;
        private final int successThreshold;
        private final long timeoutMs;
        private final int halfOpenRequests;
    
        private volatile CircuitState state = CircuitState.CLOSED;
        private final AtomicInteger failureCount = new AtomicInteger(0);
        private final AtomicInteger successCount = new AtomicInteger(0);
        private final AtomicInteger halfOpenCount = new AtomicInteger(0);
        private volatile Instant lastFailureTime;
        private final ReentrantLock lock = new ReentrantLock();
    
        @Override
        public <T> T execute(Supplier<T> operation) throws CircuitBreakerOpenException {
            if (!allowRequest()) {
                throw new CircuitBreakerOpenException(name);
            }
    
            try {
                T result = operation.get();
                onSuccess();
                return result;
            } catch (Exception e) {
                onFailure();
                throw e;
            }
        }
    
        @Override
        public <T> T executeWithFallback(Supplier<T> operation, Supplier<T> fallback) {
            try {
                return execute(operation);
            } catch (CircuitBreakerOpenException e) {
                log.debug("[{}] Circuit open, using fallback", name);
                return fallback.get();
            } catch (Exception e) {
                log.warn("[{}] Operation failed, using fallback", name, e);
                return fallback.get();
            }
        }
    
        private boolean allowRequest() {
            switch (state) {
                case CLOSED:
                    return true;
    
                case OPEN:
                    // Vérifier si le timeout est passé
                    if (lastFailureTime != null &&
                        Duration.between(lastFailureTime, Instant.now()).toMillis() > timeoutMs) {
                        transitionTo(CircuitState.HALF_OPEN);
                        return true;
                    }
                    return false;
    
                case HALF_OPEN:
                    // Limiter les requêtes en half-open
                    return halfOpenCount.incrementAndGet() <= halfOpenRequests;
    
                default:
                    return false;
            }
        }
    
        private void onSuccess() {
            lock.lock();
            try {
                switch (state) {
                    case CLOSED:
                        failureCount.set(0);
                        break;
    
                    case HALF_OPEN:
                        if (successCount.incrementAndGet() >= successThreshold) {
                            transitionTo(CircuitState.CLOSED);
                        }
                        break;
                }
            } finally {
                lock.unlock();
            }
        }
    
        private void onFailure() {
            lock.lock();
            try {
                lastFailureTime = Instant.now();
    
                switch (state) {
                    case CLOSED:
                        if (failureCount.incrementAndGet() >= failureThreshold) {
                            transitionTo(CircuitState.OPEN);
                        }
                        break;
    
                    case HALF_OPEN:
                        transitionTo(CircuitState.OPEN);
                        break;
                }
            } finally {
                lock.unlock();
            }
        }
    
        private void transitionTo(CircuitState newState) {
            if (state != newState) {
                log.info("[{}] Circuit state: {} -> {}", name, state, newState);
                state = newState;
                failureCount.set(0);
                successCount.set(0);
                halfOpenCount.set(0);
            }
        }
    
        @Override
        public CircuitState getState() {
            return state;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public void forceOpen() {
            transitionTo(CircuitState.OPEN);
        }
    
        @Override
        public void forceClose() {
            transitionTo(CircuitState.CLOSED);
        }
    
        @Override
        public void reset() {
            lock.lock();
            try {
                state = CircuitState.CLOSED;
                failureCount.set(0);
                successCount.set(0);
                halfOpenCount.set(0);
                lastFailureTime = null;
            } finally {
                lock.unlock();
            }
        }
    }
    

    3.5 CircuitBreakerRegistry

    @Component
    public class CircuitBreakerRegistry {
    
        private final ConcurrentHashMap<String, CircuitBreaker> circuits = new ConcurrentHashMap<>();
        private final SocleConfiguration config;
    
        public CircuitBreaker getOrCreate(String name) {
            return circuits.computeIfAbsent(name, this::createCircuitBreaker);
        }
    
        public CircuitBreaker get(String name) {
            return circuits.get(name);
        }
    
        public Map<String, CircuitState> getAllStates() {
            return circuits.entrySet().stream()
                .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    e -> e.getValue().getState()
                ));
        }
    
        private CircuitBreaker createCircuitBreaker(String name) {
            return new DefaultCircuitBreaker(
                name,
                config.getResilience().getCircuitBreaker().getFailureThreshold(),
                config.getResilience().getCircuitBreaker().getSuccessThreshold(),
                config.getResilience().getCircuitBreaker().getTimeoutMs(),
                config.getResilience().getCircuitBreaker().getHalfOpenRequests()
            );
        }
    }
    

    3.6 Utilisation

    @Service
    public class PaymentService {
    
        @Autowired
        private CircuitBreakerRegistry cbRegistry;
    
        public PaymentResult processPayment(Payment payment) {
            CircuitBreaker cb = cbRegistry.getOrCreate("payment-gateway");
    
            return cb.executeWithFallback(
                () -> paymentGateway.process(payment),
                () -> {
                    // Fallback: mettre en queue pour traitement ultérieur
                    paymentQueue.enqueue(payment);
                    return PaymentResult.pending("Queued for later processing");
                }
            );
        }
    }
    

    4. Combinaison Retry + Circuit Breaker

    @Service
    public class ResilientApiClient {
    
        @Autowired
        private RetryTemplate retryTemplate;
    
        @Autowired
        private CircuitBreakerRegistry cbRegistry;
    
        public Data fetchData(String endpoint) {
            CircuitBreaker cb = cbRegistry.getOrCreate("api-" + endpoint);
    
            return cb.executeWithFallback(
                () -> retryTemplate.execute(() -> httpClient.get(endpoint)),
                () -> getCachedData(endpoint)
            );
        }
    }
    

    Ordre d’exécution

    1. CircuitBreaker vérifie si le circuit est ouvert
       → Si OPEN: fallback immédiat
       → Si CLOSED/HALF_OPEN: continue
    
    2. RetryTemplate essaie l'opération
       → Retry avec backoff exponentiel
       → Si tous les retries échouent: exception
    
    3. CircuitBreaker compte l'échec
       → Si seuil atteint: passe en OPEN
    
    4. Fallback si échec
    

    5. Annotations (optionnel)

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Resilient {
        String circuitBreaker() default "";
        int maxRetries() default 3;
        long retryDelay() default 1000;
        Class<? extends Throwable>[] retryOn() default {Exception.class};
    }
    
    @Aspect
    @Component
    public class ResilienceAspect {
    
        @Around("@annotation(resilient)")
        public Object handleResilience(ProceedingJoinPoint pjp, Resilient resilient) throws Throwable {
            String cbName = resilient.circuitBreaker();
            if (cbName.isEmpty()) {
                cbName = pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName();
            }
    
            CircuitBreaker cb = cbRegistry.getOrCreate(cbName);
    
            return cb.execute(() -> {
                return retryTemplate.execute(() -> {
                    try {
                        return pjp.proceed();
                    } catch (Throwable t) {
                        throw new RuntimeException(t);
                    }
                });
            });
        }
    }
    

    Utilisation

    @Service
    public class UserService {
    
        @Resilient(circuitBreaker = "user-api", maxRetries = 5)
        public User getUser(String id) {
            return userApiClient.fetchUser(id);
        }
    }
    

    6. Bulkhead Pattern

    Le pattern Bulkhead limite le nombre d’appels concurrents pour éviter l’épuisement des ressources.

    @Component
    public class BulkheadRegistry {
    
        private final ConcurrentHashMap<String, Semaphore> bulkheads = new ConcurrentHashMap<>();
    
        public <T> T execute(String name, int maxConcurrent, Supplier<T> operation)
                throws BulkheadFullException {
            Semaphore semaphore = bulkheads.computeIfAbsent(name,
                k -> new Semaphore(maxConcurrent));
    
            if (!semaphore.tryAcquire()) {
                throw new BulkheadFullException(name);
            }
    
            try {
                return operation.get();
            } finally {
                semaphore.release();
            }
        }
    }
    
    // Utilisation
    @Service
    public class ApiService {
    
        @Autowired
        private BulkheadRegistry bulkheadRegistry;
    
        public Data callExternalApi() {
            return bulkheadRegistry.execute("external-api", 10, () -> {
                // Max 10 appels concurrents
                return httpClient.get("/api/data");
            });
        }
    }
    

    7. Timeout Pattern

    @Component
    public class TimeoutTemplate {
    
        private final ScheduledExecutorService scheduler =
            Executors.newScheduledThreadPool(4);
    
        public <T> T executeWithTimeout(Supplier<T> operation, Duration timeout)
                throws TimeoutException {
    
            CompletableFuture<T> future = CompletableFuture.supplyAsync(operation);
    
            try {
                return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
            } catch (java.util.concurrent.TimeoutException e) {
                future.cancel(true);
                throw new TimeoutException("Operation timed out after " + timeout);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    8. API Admin

    @RestController
    @RequestMapping("/admin/resilience")
    public class ResilienceController {
    
        @Autowired
        private CircuitBreakerRegistry cbRegistry;
    
        @GetMapping("/circuits")
        public Map<String, CircuitState> getCircuits() {
            return cbRegistry.getAllStates();
        }
    
        @PostMapping("/circuits/{name}/reset")
        public void resetCircuit(@PathVariable String name) {
            CircuitBreaker cb = cbRegistry.get(name);
            if (cb != null) {
                cb.reset();
            }
        }
    
        @PostMapping("/circuits/{name}/open")
        public void openCircuit(@PathVariable String name) {
            CircuitBreaker cb = cbRegistry.get(name);
            if (cb != null) {
                cb.forceOpen();
            }
        }
    
        @PostMapping("/circuits/{name}/close")
        public void closeCircuit(@PathVariable String name) {
            CircuitBreaker cb = cbRegistry.get(name);
            if (cb != null) {
                cb.forceClose();
            }
        }
    }
    

    9. Métriques

    @Component
    public class ResilienceMetrics {
    
        private final MeterRegistry registry;
    
        public void recordRetry(String operation, int attempt, boolean success) {
            Counter.builder("socle_retry_attempts")
                .tag("operation", operation)
                .tag("attempt", String.valueOf(attempt))
                .tag("success", String.valueOf(success))
                .register(registry)
                .increment();
        }
    
        public void recordCircuitBreakerState(String name, CircuitState state) {
            Gauge.builder("socle_circuit_breaker_state", () ->
                state == CircuitState.CLOSED ? 0 :
                state == CircuitState.HALF_OPEN ? 1 : 2)
                .tag("name", name)
                .register(registry);
        }
    }
    

    10. Bonnes pratiques

    DO

    • Utiliser le circuit breaker pour les appels réseau externes
    • Configurer des timeouts appropriés
    • Toujours prévoir un fallback
    • Monitorer l’état des circuits
    • Logger les transitions d’état

    DON’T

    • Ne pas utiliser le retry pour les erreurs non récupérables (400, 401)
    • Ne pas configurer des seuils trop bas (faux positifs)
    • Ne pas oublier les timeouts (risque de blocage)
    • Ne pas ignorer les métriques

    11. Références

  • Socle V004 – Standard TechDB

    Socle V004 – Standard TechDB

    28 – Standard de Creation de Table H2

    Version : 4.0.1 Date : 2026-01-13 Package : eu.lmvi.socle.techdb

    Introduction

    Ce document definit le standard de creation des tables dans la base technique H2 du Socle V004. Ce standard garantit la coherence, la tracabilite et la compatibilite PostgreSQL/H2.

    Structure de Base

    Toutes les tables TechDB doivent suivre cette structure:

    Champ Type Description
    x_id BIGINT GENERATED BY DEFAULT AS IDENTITY Identifiant technique auto-genere
    x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP Date creation
    x_dateChanged TIMESTAMP WITH TIME ZONE Date modification (NULL a l’insert, MAJ par appli)
    x_sub VARCHAR(255) Sujet/categorie
    x_partition VARCHAR(30) Partition logique
    x_comment CLOB Commentaires/historiques JSON texte
    [champs metier] Champs specifiques a la table
    datas CLOB Donnees metier JSON texte (toujours en fin)

    Regles

    Identite

    • Utiliser GENERATED BY DEFAULT AS IDENTITY, pas AUTO_INCREMENT (MySQL)
    • Pas de sequence explicite
    • La colonne x_id est toujours la cle primaire
    -- Correct (SQL standard / H2 / PostgreSQL)
    x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
    
    -- Incorrect (MySQL uniquement)
    id BIGINT AUTO_INCREMENT PRIMARY KEY
    

    Timestamps

    • x_dateCreated: Toujours NOT NULL DEFAULT CURRENT_TIMESTAMP
    • x_dateChanged: NULL a l’insertion, mis a jour par l’application
    • Utiliser TIMESTAMP WITH TIME ZONE pour la compatibilite
    x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    x_dateChanged TIMESTAMP WITH TIME ZONE,
    

    Triggers

    • Aucun trigger dans H2
    • L’audit, l’historisation et la mise a jour de x_dateChanged sont geres par l’application

    Cle Primaire

    • Si une cle existante est presente (ex: worker_name), la conserver comme UNIQUE
    • x_id reste la PK technique
    x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    worker_name VARCHAR(255) NOT NULL UNIQUE,
    

    Conventions de Nommage

    Element Convention Exemple
    Champs techniques Prefixe x_ x_id, x_dateCreated
    Cles etrangeres id_<table_cible> id_user, id_order
    Champ id existant Renommer en x_id
    Donnees JSON datas en derniere position

    Permissions

    • Droits geres au niveau utilisateur H2
    • Proprietaire = utilisateur createur (socle par defaut)

    Contraintes H2 vs PostgreSQL

    PostgreSQL H2
    JSONB CLOB
    Triggers natifs Logique applicative
    Validation JSON DB Validation applicative
    SERIAL GENERATED BY DEFAULT AS IDENTITY
    Index GIN sur JSON Non supporte

    Exemple DDL Complet

    CREATE TABLE IF NOT EXISTS techdb_example (
        -- Champs techniques (standard)
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
    
        -- Champs metier specifiques
        example_key VARCHAR(255) NOT NULL UNIQUE,
        status VARCHAR(50) NOT NULL,
        counter INT DEFAULT 0,
        last_activity TIMESTAMP WITH TIME ZONE,
    
        -- Donnees JSON (toujours en dernier)
        datas CLOB
    );
    
    -- Index recommandes
    CREATE INDEX IF NOT EXISTS idx_example_key ON techdb_example(example_key);
    CREATE INDEX IF NOT EXISTS idx_example_status ON techdb_example(status);
    CREATE INDEX IF NOT EXISTS idx_example_created ON techdb_example(x_dateCreated);
    

    Tables TechDB du Socle

    Le Socle V004 definit 5 tables techniques:

    techdb_offsets

    Stockage des offsets de consommation (Kafka, NATS, etc.)

    CREATE TABLE IF NOT EXISTS techdb_offsets (
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
        offset_key VARCHAR(255) NOT NULL UNIQUE,
        topic VARCHAR(255) NOT NULL,
        partition_id INT DEFAULT 0,
        offset_value BIGINT NOT NULL,
        consumer_group VARCHAR(255),
        datas CLOB
    );
    

    techdb_worker_state

    Etat persistant des Workers

    CREATE TABLE IF NOT EXISTS techdb_worker_state (
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
        worker_name VARCHAR(255) NOT NULL UNIQUE,
        state VARCHAR(50) NOT NULL,
        last_run_at TIMESTAMP WITH TIME ZONE,
        next_run_at TIMESTAMP WITH TIME ZONE,
        error_count INT DEFAULT 0,
        last_error CLOB,
        datas CLOB
    );
    

    techdb_events

    Evenements techniques

    CREATE TABLE IF NOT EXISTS techdb_events (
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
        event_type VARCHAR(100) NOT NULL,
        source VARCHAR(255) NOT NULL,
        processed BOOLEAN DEFAULT FALSE,
        processed_at TIMESTAMP WITH TIME ZONE,
        datas CLOB
    );
    

    techdb_log_buffer

    Buffer de logs pour LogForwarder

    CREATE TABLE IF NOT EXISTS techdb_log_buffer (
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
        log_level VARCHAR(20) NOT NULL,
        logger_name VARCHAR(255),
        message CLOB NOT NULL,
        thread_name VARCHAR(255),
        log_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
        forwarded BOOLEAN DEFAULT FALSE,
        forwarded_at TIMESTAMP WITH TIME ZONE,
        datas CLOB
    );
    

    techdb_kv

    Stockage cle-valeur generique

    CREATE TABLE IF NOT EXISTS techdb_kv (
        x_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        x_dateCreated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        x_dateChanged TIMESTAMP WITH TIME ZONE,
        x_sub VARCHAR(255),
        x_partition VARCHAR(30),
        x_comment CLOB,
        kv_key VARCHAR(512) NOT NULL UNIQUE,
        value_type VARCHAR(50) DEFAULT 'string',
        expires_at TIMESTAMP WITH TIME ZONE,
        datas CLOB
    );
    

    Bonnes Pratiques

    DO

    • Toujours utiliser le format standard x_ pour les champs techniques
    • Mettre datas en derniere colonne
    • Creer des index sur les colonnes frequemment requetees
    • Utiliser TIMESTAMP WITH TIME ZONE pour tous les timestamps
    • Documenter le contenu JSON attendu dans datas

    DON’T

    • Ne pas utiliser AUTO_INCREMENT (syntaxe MySQL)
    • Ne pas creer de triggers (gestion applicative)
    • Ne pas stocker de BLOBs volumineux (utiliser stockage externe)
    • Ne pas utiliser JSONB (non supporte par H2)
    • Ne pas omettre les index sur les colonnes de recherche

    Migration depuis l’ancien format

    Si vous avez des tables existantes avec l’ancien format:

    -- 1. Sauvegarder les donnees
    CREATE TABLE techdb_events_backup AS SELECT * FROM techdb_events;
    
    -- 2. Supprimer l'ancienne table
    DROP TABLE techdb_events;
    
    -- 3. Recreer avec le nouveau format
    -- (voir DDL ci-dessus)
    
    -- 4. Migrer les donnees
    INSERT INTO techdb_events (event_type, source, processed, processed_at, datas)
    SELECT event_type, source, processed, processed_at, payload
    FROM techdb_events_backup;
    
    -- 5. Supprimer la sauvegarde
    DROP TABLE techdb_events_backup;
    

    Voir aussi

    H2 = dev/tests/outillage – Structure compatible PostgreSQL/H2

    Socle V004 – Standard TechDB

  • Pour






    Pour Piloter Docker à distance, Quels ports docker ouvrir sur le serveur linux OpenSuse 15.5, et comment faire?



    Pour Piloter Docker à distance, Quels ports docker ouvrir sur le serveur linux OpenSuse 15.5, et comment faire?

    Pour permettre la gestion à distance de Docker sur un serveur Linux, vous devez généralement ouvrir le port TCP 2375 ou 2376. Voici les détails :

    • Port 2375 : C’est le port par défaut pour la communication non chiffrée avec le daemon Docker. Il est important de noter que l’utilisation de ce port sans chiffrement peut présenter des risques de sécurité, car les informations sont transmises en clair.
    • Port 2376 : C’est le port par défaut pour la communication chiffrée (TLS) avec le daemon Docker. L’utilisation de ce port avec TLS est recommandée pour sécuriser les communications entre le client Docker et le serveur.

    Configuration pour la Gestion à Distance

    Pour configurer Docker afin d’écouter sur l’un de ces ports, suivez ces étapes :

    1. Éditez le fichier de configuration de Docker (généralement situé à /etc/docker/daemon.json). Si ce fichier n’existe pas, vous pouvez le créer.
    1. Dans un en environnement clos : Ajoutez ou modifiez les options de configuration pour inclure l’écoute sur le port désiré. Par exemple, pour écouter sur le port 2375 votre fichier daemon.json pourrait ressembler à ceci :
    1. Ajoutez ou modifiez les options de configuration pour inclure l’écoute sur le port désiré. Par exemple, pour écouter sur le port 2376 avec TLS activé, votre fichier daemon.json pourrait ressembler à ceci :
    1. Redémarrez le service Docker pour appliquer les changements :sudo systemctl restart docker
    1. Recharger la configuration du daemon et redémarrer Docker:
    1. Configurer le Pare-feu:
    1. Sécuriser la Communication (pour le port 2376) :

    Sécurité

    • L’ouverture du port Docker pour une gestion à distance doit être effectuée avec une attention particulière à la sécurité. Utilisez toujours des connexions chiffrées (TLS) si possible et limitez l’accès au port aux adresses IP connues et fiables.
    • Pensez à d’autres mesures de sécurité, comme l’utilisation de VPNs ou de SSH tunneling, pour un accès plus sécurisé.


  • TEST






    Nouveau Test avec image V2



    Nouveau Test avec image V2

    Avec un fichier

    Slot_Car_Street.pdf

    Debut de Code en javascript

    Fichier Jar :

    registry-handler-1.0.0.jar.zip

    javascript
    import { Block } from '../../../../data/protocols/blocks';
    import { ToHtml } from '../../../../domain/use-cases/to-html';
    import { blockToInnerText } from '../../../helpers/block-to-inner-text';
    import hljs from '../../../../config/highlightConfig';
    import prettier from 'prettier';
    
    export class CodeBlockToHtml implements ToHtml {
      private readonly _block: Block;
    
      constructor(block: Block) {
        this._block = block;
      }
    
      async convert(): Promise<string> {
        const language = this._language ? this._language : 'plaintext';  // Fallback au texte brut si pas de langue spécifiée
        const languageClass = `language-${language}`;
    
        // Obtenir le texte du bloc et formater avec Prettier
        let code = blockToInnerText(this._block).replace(/(\s{4}|\t)/g, '  ');
        try {
          code = prettier.format(code, { semi: false, parser: "babel" }); // Assure-toi de choisir le bon parser selon le langage
        } catch (error) {
          console.error("Prettier formatting failed:", error);
        }
    
        // Appliquer la coloration syntaxique avec Highlight.js
        const highlightedCode = hljs.highlight(code, { language }).value;
    
        // Retourner le code HTML formaté
        return Promise.resolve(
            `<pre><code class="${languageClass}">${highlightedCode}</code></pre>`
        );
      }
    
      private get _language(): string {
        return this._block.properties?.language?.toLowerCase().replace(/ /g, '');
      }
    }

    Image

    Notion Image

    Autres images

    Notion Image

    Tableau

    Resolving The Problem

    The following table lists the ports that IBM i Access and related functions use for communication with the IBM i OS System:

    PC Function Server Name Port Non-SSL Port SSL
    • Server Mapper • as-svrmap • 449 • —
    • License Management • as-central • 8470 • 9470
    • Database Access • as-database • 8471 • 9471
    • Data Queues • as-dtaq • 8472 • 9472
    • IFS Access usingAccess/Navigator • as-file • 8473 • 9473
    • Network Printers • as-netprt • 8474 • 9474
    • Remote Command • as-rmtcmd • 8475 • 9475
    • Signon Verification • as-signon • 8476 • 9476
    • Telnet (5250 Emulation) • telnet • 23 • 992
    • Navigator for i (Heritage version) • as-nav • 2004 • 2005
    • New Navigator for i • as-new-nav • 2002 • 2003
    • Digital Certificate Manager • as-admin3-http • 2006 • 2007
    • HTTP Administration • as-admin • 2001 • 2010
    • DDM/DRDA • DDM/DRDA • 446 • 448
    • NetServer • netbios > • 137 • —
    • NetServer • netbios > • 139 • —
    • NetServer (CIFS) • CIFS • 445 • —
    • Service Tools Server • as-sts • 3000 • —

    If any of the above ports are restricted by using a firewall or any other mechanism, IBM i Access or related functions might fail to operate. For assistance with configuring ports or working with a firewall beyond the above information, contact the firewall provider or obtain a consulting agreement.

    Note:

    The following ports are common to most IBM i Access Client products such as ODBC, Telnet, and other specific functions:

    Port 449 is used to look up service by name and return the port number.

    Ports 8470 and 9470(TLS/SSL) are used for host code page translation tables and licensing functions.

    Ports 8475 and 9475(TLS/SSL) are used to check for application administration restrictions.

    Ports 8476 and 9476(TLS/SSL) are used for checking signon verification to authenticate.

    depending on your needs you may only need the above ports and the port(s) for your function/application.For the ports used by the Console, see document N1015344, IBM iSeries Port Assignments with Operations Console.

    For a list of currently listening IPv4 daemons on the IBM i, run the following SQL statement:

    sql
    SELECT LOCAL_PORT, LOCAL_PORT_NAME, BIND_USER, IDLE_TIME
    FROM QSYS2.NETSTAT_INFO
    where LOCAL_ADDRESS = '0.0.0.0';

    Other options are the CL command NETSTAT OPTION(*CNN) or to review the output from WRKSRVTBLE OUTPUT(*PRINT).

    Tache 1
    Tache 2
    Tache 3

    • Puce 1
    • Puce 2

    La maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombrée

    Test Unique

    ℹ️

    Avec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignes

    Test de titre 1

    Test de titre 2

    Test de titre 3

    en gras, en italique, sous ligné, barré

    Champs de l’entité « Person »

    Voici les principaux champs disponibles pour l’entité « Person » selon Schema.org :

    1. additionalName: Un nom supplémentaire pour la personne, souvent un deuxième prénom.
    1. address: L’adresse postale de la personne.
    1. affiliation: Une organisation à laquelle la personne est affiliée, comme une entreprise ou une institution.
    1. alumniOf: Une organisation éducative ou une école dont la personne est un ancien élève.
    1. award: Une récompense ou un prix que la personne a reçu.
    1. birthDate: La date de naissance de la personne.
    1. birthPlace: Le lieu de naissance de la personne.
    1. brand: La marque associée à la personne, souvent utilisée pour des célébrités ou des entrepreneurs.
    1. children: Les enfants de la personne.
    1. colleague: Les collègues de la personne.
    1. contactPoint: Un point de contact pour la personne.
    1. deathDate: La date de décès de la personne.
    1. deathPlace: Le lieu de décès de la personne.
    1. duns: Le numéro DUNS de la personne.
    1. email: L’adresse email de la personne.
    1. familyName: Le nom de famille de la personne.
    1. faxNumber: Le numéro de fax de la personne.
    1. follows: Les personnes que cette personne suit (par exemple sur les réseaux sociaux).
    1. funder: Une entité qui finance cette personne.
    1. gender: Le genre de la personne (masculin, féminin, etc.).
    1. givenName: Le prénom de la personne.
    1. globalLocationNumber: Le numéro de localisation global pour la personne.
    1. hasCredential: Les informations d’identification de la personne.
    1. hasOccupation: La profession de la personne.
    1. hasOfferCatalog: Un catalogue d’offres que la personne possède.
    1. hasPOS: Un point de vente que la personne possède.
    1. height: La taille de la personne.
    1. homeLocation: L’emplacement de la résidence principale de la personne.
    1. honorificPrefix: Un préfixe honorifique pour la personne (par exemple, Dr., M., Mme).
    1. honorificSuffix: Un suffixe honorifique pour la personne (par exemple, Jr., Sr.).
    1. interactionStatistic: Les statistiques d’interaction pour la personne.
    1. isicV4: Le code ISIC pour la personne.
    1. jobTitle: Le titre du poste de la personne.
    1. knows: Les connaissances de la
    mermaid
    graph LR
    SymmetricDS_OVH[(« SymmetricDS-OVH »)]
    db-prod[(« PostgreSQL (db-reflet)
    Port: 5432″)]
    pgadmin[(« pgAdmin
    Port: 8888″)]
    redis[(« Redis (cache)
    Port: 6379″)]
    zookeeper[(« Zookeeper
    Port: 2181″)]
    kafka[(« Kafka
    Port: 9092″)]
    kafka_manager[(« Kafka Manager
    Port: 9000″)]
    directus[(« Directus
    Port: 8055″)]
    budibase[(« Budibase
    Port: 10000″)]
    n8n[(« n8n
    Port: 443″)]
    me[(« MONDE \n Extérieur
    Port: 443″)]
    cosmo[(« PostgreSQL (Cosmo)
    Port: 5432″)]
    directus-c[(« Directus
    4 Cosmo
    Port: 8055″)]

    db-prod –>|Cache| redis
    db-prod –>|Publie| kafka
    directus –>|Cache| redis
    pgadmin –>|Administre| db-prod
    budibase –>|CRUD| db-prod
    directus –>|Propose des APIS| db-prod
    kafka –>|Depends on| zookeeper
    kafka_manager –>|Depends on| zookeeper
    kafka_manager –>|Manage Topics| kafka

    SymmetricDS_OVH –>|Envoi| db-prod
    SymmetricDS_OVH –>|Envoi| kafka
    n8n –> |publie| kafka
    kafka –>|consomme| n8n
    db-prod –>|trigger| n8n
    n8n –> |publie| me
    directus-c –> |publie| cosmo
    cosmo –> |publie| directus-c
    n8n –> |GraphQL| directus-c

    python
    # flake8: noqa
    # This file is used for deploying replicate models
    # running: cog predict -i img=@inputs/whole_imgs/10045.png -i version='v1.4' -i scale=2
    # push: cog push r8.im/tencentarc/gfpgan
    # push (backup): cog push r8.im/xinntao/gfpgan
    
    import os
    
    os.system('python setup.py develop')
    os.system('pip install realesrgan')
    
    import cv2
    import shutil
    import tempfile
    import torch
    from basicsr.archs.srvgg_arch import SRVGGNetCompact
    
    from gfpgan import GFPGANer
    
    try:
        from cog import BasePredictor, Input, Path
        from realesrgan.utils import RealESRGANer
    except Exception:
        print('please install cog and realesrgan package')
    
    
    class Predictor(BasePredictor):
    
        def setup(self):
            os.makedirs('output', exist_ok=True)
            # download weights
            if not os.path.exists('gfpgan/weights/realesr-general-x4v3.pth'):
                os.system(
                    'wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth -P ./gfpgan/weights'
                )
            if not os.path.exists('gfpgan/weights/GFPGANv1.2.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.2.pth -P ./gfpgan/weights')
            if not os.path.exists('gfpgan/weights/GFPGANv1.3.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth -P ./gfpgan/weights')
            if not os.path.exists('gfpgan/weights/GFPGANv1.4.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P ./gfpgan/weights')
            if not os.path.exists('gfpgan/weights/RestoreFormer.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/RestoreFormer.pth -P ./gfpgan/weights'
                )
    
            # background enhancer with RealESRGAN
            model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
            model_path = 'gfpgan/weights/realesr-general-x4v3.pth'
            half = True if torch.cuda.is_available() else False
            self.upsampler = RealESRGANer(
                scale=4, model_path=model_path, model=model, tile=0, tile_pad=10, pre_pad=0, half=half)
    
            # Use GFPGAN for face enhancement
            self.face_enhancer = GFPGANer(
                model_path='gfpgan/weights/GFPGANv1.4.pth',
                upscale=2,
                arch='clean',
                channel_multiplier=2,
                bg_upsampler=self.upsampler)
            self.current_version = 'v1.4'
    
        def predict(
                self,
                img: Path = Input(description='Input'),
                version: str = Input(
                    description='GFPGAN version. v1.3: better quality. v1.4: more details and better identity.',
                    choices=['v1.2', 'v1.3', 'v1.4', 'RestoreFormer'],
                    default='v1.4'),
                scale: float = Input(description='Rescaling factor', default=2),
        ) -> Path:
            weight = 0.5
            print(img, version, scale, weight)
            try:
                extension = os.path.splitext(os.path.basename(str(img)))[1]
                img = cv2.imread(str(img), cv2.IMREAD_UNCHANGED)
                if len(img.shape) == 3 and img.shape[2] == 4:
                    img_mode = 'RGBA'
                elif len(img.shape) == 2:
                    img_mode = None
                    img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
                else:
                    img_mode = None
    
                h, w = img.shape[0:2]
                if h < 300:
                    img = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_LANCZOS4)
    
                if self.current_version != version:
                    if version == 'v1.2':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/GFPGANv1.2.pth',
                            upscale=2,
                            arch='clean',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
                        self.current_version = 'v1.2'
                    elif version == 'v1.3':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/GFPGANv1.3.pth',
                            upscale=2,
                            arch='clean',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
                        self.current_version = 'v1.3'
                    elif version == 'v1.4':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/GFPGANv1.4.pth',
                            upscale=2,
                            arch='clean',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
                        self.current_version = 'v1.4'
                    elif version == 'RestoreFormer':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/RestoreFormer.pth',
                            upscale=2,
                            arch='RestoreFormer',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
    
                try:
                    _, _, output = self.face_enhancer.enhance(
                        img, has_aligned=False, only_center_face=False, paste_back=True, weight=weight)
                except RuntimeError as error:
                    print('Error', error)
    
                try:
                    if scale != 2:
                        interpolation = cv2.INTER_AREA if scale < 2 else cv2.INTER_LANCZOS4
                        h, w = img.shape[0:2]
                        output = cv2.resize(output, (int(w * scale / 2), int(h * scale / 2)), interpolation=interpolation)
                except Exception as error:
                    print('wrong scale input.', error)
    
                if img_mode == 'RGBA':  # RGBA images should be saved in png format
                    extension = 'png'
                # save_path = f'output/out.{extension}'
                # cv2.imwrite(save_path, output)
                out_path = Path(tempfile.mkdtemp()) / f'out.{extension}'
                cv2.imwrite(str(out_path), output)
            except Exception as error:
                print('global exception: ', error)
            finally:
                clean_folder('output')
            return out_path
    
    
    def clean_folder(folder):
        for filename in os.listdir(folder):
            file_path = os.path.join(folder, filename)
            try:
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
            except Exception as e:
                print(f'Failed to delete {file_path}. Reason: {e}')
    


  • Réinventer l’utilisation de l’ordinateur avec l’interpréteur ouvert

    Réinventer l’utilisation de l’ordinateur avec l’interpréteur ouvert

    L’évolution du traitement du langage naturel a donné naissance à l’interpréteur ouvert, une nouvelle manière d’interagir avec les ordinateurs. Cette interface en langage naturel ouvre des horizons inexplorés pour les tâches informatiques quotidiennes, allant de la création de contenus multimédias à l’analyse complexe de données.

    Les fondements de l’interprétation en langage naturel

    Le Traitement du Langage Naturel (TAL) figure au centre de l’innovation permettant aux ordinateurs de comprendre et d’agir sur les commandes humaines. Cette technologie a connu une évolution remarquable depuis ses origines, marquées par des programmes comme ELIZA, qui simulait une conversation sans réelle compréhension du langage. Aujourd’hui, grâce aux avancées considérables en intelligence artificielle et en apprentissage machine, les systèmes de traitement du langage naturel peuvent non seulement comprendre le sens littéral des mots mais aussi saisir les nuances et le contexte d’une demande.

    Au cœur de ces progrès, la capacité des algorithmes à analyser et interpréter les données linguistiques s’est nettement améliorée. Les techniques comme l’analyse syntaxique et sémantique permettent aux machines de décomposer les phrases en éléments compréhensibles et de les relier à des actions spécifiques. Cette compréhension est renforcée par l’apprentissage profond, qui dote les ordinateurs de la capacité à tirer des enseignements de vastes ensembles de données textuelles, leur permettant d’améliorer continuellement leur performance.

    Les applications actuelles du TAL s’étendent bien au-delà de la compréhension textuelle. Elles englobent la traduction automatique, la reconnaissance vocale, et même l’analyse de sentiments. L’interpréteur ouvert s’inscrit dans cette évolution comme un outil révolutionnaire, offrant aux utilisateurs la possibilité de commander et contrôler leurs ordinateurs en utilisant simplement le langage naturel. Cette innovation s’appuie sur les principes fondamentaux du TAL pour interpréter les commandes des utilisateurs, que ce soit pour créer un document, manipuler des fichiers multimédias, ou naviguer sur le web, rendant la technologie plus accessible et intuitive pour tous.

    Les progrès accomplis depuis ELIZA jusqu’aux systèmes sophistiqués d’aujourd’hui illustrent non seulement l’évolution du TAL, mais aussi la manière dont cette technologie continue de réinventer l’interaction entre l’homme et la machine. Avec l’avènement de l’interpréteur ouvert, l’informatique entre dans une nouvelle ère où les barrières linguistiques entre l’humain et l’ordinateur s’estompent, ouvrant la voie à une utilisation plus naturelle et efficace des technologies de l’information.

    La programmation accessible par le langage naturel

    La programmation a toujours été perçue comme un domaine réservé à une élite versée dans le maniement complexe des langages de programmation. Des premiers langages de haut niveau comme FORTRAN et COBOL, inventés dans les années 1950 et 1960, jusqu’aux paradigmes modernes tels que la programmation orientée objet, l’évolution a été constante. La création des premiers compilateurs a marqué une révolution, permettant la traduction du code source en langage machine, rendant l’exécution des programmes plus efficace. Cependant, malgré ces avancées, l’accès à la programmation est resté limité par la barrière du langage technique.

    L’avènement de l’interpréteur ouvert repousse aujourd’hui ces limites, inaugurant une ère où les commandes en langage naturel permettent d’exécuter des tâches de programmation complexes. Cet outil transforme fondamentalement l’approche de la programmation en la rendant accessible à tous, sans nécessité de maîtriser les syntaxes codifiées des langages traditionnels. L’utilisateur dialogue en langage naturel avec son ordinateur, qui interprète ces commandes et exécute les tâches demandées, allant de la gestion de bases de données à la manipulation de fichiers, en passant par la création et l’édition de contenus multimédias.

    Cette transformation marque un tournant dans la programmation du futur. L’interpréteur ouvert démocratise l’accès à la programmation, permettant à un public bien plus large de participer à la création et à l’innovation technologique. Il encourage également une nouvelle manière de penser la résolution de problèmes informatiques, où la compréhension et l’expression en langage naturel priment sur la connaissance de syntaxes complexes.

    L’impact de cette innovation se mesure également dans l’enseignement et l’apprentissage de la programmation. En simplifiant l’accès au codage, l’interpréteur ouvert constitue un outil pédagogique précieux, ouvrant les portes de l’informatique à des publics jusqu’ici éloignés de ce domaine. Cette accessibilité accrue pourrait même remodeler les professions futures, où la capacité à interagir avec les machines en langage naturel deviendra aussi fondamentale que l’est aujourd’hui la maîtrise de l’outil informatique.

    Ainsi, en offrant une interface intuitive de programmation en langage naturel, l’interpréteur ouvert ne se contente pas de simplifier l’accès à la programmation ; il redéfinit les contours mêmes de ce que signifie « programmer ». Cette mutation promet de révolutionner la manière dont nous interagissons avec les technologies, facilitant une intégration encore plus profonde et personnalisée de l’informatique dans notre quotidien.

    Interprétation avancée pour l’analyse de données

    Après avoir exploré la révolution de la programmation accessible via le langage naturel, où l’interpréteur ouvert simplifie les processus de codage, nous plongeons maintenant dans le monde de l’analyse de données. La capacité à interpréter, nettoyer, analyser et visualiser des données complexe est cruciale dans le paysage actuel de l’information. Historiquement, cette discipline exigeait une compréhension approfondie des statistiques et une maîtrise de logiciels spécialisés.

    L’extraction de données, ou data mining, a évolué depuis ses débuts, s’appuyant d’abord sur des statistiques simples pour évoluer vers des modèles prédictifs et des algorithmes complexes d’apprentissage automatique. La visualisation des données, quant à elle, a transformé la manière dont nous interprétons les ensembles de données, permettant de déceler des tendances et des anomalies auparavant invisibles.

    L’introduction de l’interpréteur ouvert dans ce paysage change radicalement la donne. En utilisant des commandes en langage naturel, même sans expertise en statistiques, les utilisateurs peuvent effectuer des analyses prédictives, nettoyer des ensembles de données de grande taille et intégrer différentes sources de données pour une analyse plus holistique. Ce processus d’intégration, essentiel pour comprendre les tendances à travers divers secteurs ou phénomènes, devient ainsi plus accessible.

    La visualisation des données, un autre aspect crucial de l’analyse, bénéficie également de cette avancée. En commandant à l’interpréteur de générer des graphiques ou des cartes à partir d’un ensemble de données spécifique, les utilisateurs peuvent créer des visualisations complexes sans connaissances préalables en bibliothèques de visualisation comme Matplotlib ou D3.js.

    Par exemple, un utilisateur souhaitant comprendre la répartition géographique de ses clients pourrait simplement demander à l’interpréteur de créer une carte de chaleur à partir de ses données de vente. Ce qui aurait autrefois exigé des heures de travail manuel – nettoyage des données, apprentissage d’une bibliothèque de visualisation, écriture du code – se résume maintenant à une simple instruction en langage naturel.

    L’impact de l’interpréteur ouvert sur l’analyse de données est donc profond, démocratisant l’accès à des insights complexes et facilitant une prise de décision basée sur des données pour un éventail beaucoup plus large d’individus et d’organisations. Il marque une étape significative vers la réinvention de l’utilisation de l’ordinateur, où la complexité technique cède la place à l’intuitivité et à l’accessibilité.

    Conclusions

    L’interpréteur ouvert transforme radicalement notre approche de l’utilisation de l’ordinateur. À travers une simple conversation, il réduit la barrière technique et démocratise l’accès à des opérations complexes pour tout utilisateur.