Socle V004 – Plugins

Socle V004 - Plugins

20 – Plugins

Version : 4.0.0 Date : 2025-12-09

1. Introduction

Le Socle V4 supporte une architecture de plugins pour étendre les fonctionnalités de base. Les plugins sont des modules Spring Boot qui s’intègrent automatiquement.

2. Architecture des plugins

┌──────────────────────────────────────────────────────────┐
│                    Application                            │
│                                                           │
│  ┌─────────────────────────────────────────────────────┐ │
│  │                   Socle V4 Core                      │ │
│  │  MOP | Workers | KvBus | TechDB | Logging | etc.    │ │
│  └─────────────────────────────────────────────────────┘ │
│                          │                                │
│         ┌────────────────┼────────────────┐              │
│         ▼                ▼                ▼              │
│  ┌────────────┐   ┌────────────┐   ┌────────────┐       │
│  │   Plugin   │   │   Plugin   │   │   Plugin   │       │
│  │   Kafka    │   │   NATS     │   │   Custom   │       │
│  └────────────┘   └────────────┘   └────────────┘       │
│                                                           │
└──────────────────────────────────────────────────────────┘

3. Créer un plugin

3.1 Structure Maven

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mycompany</groupId>
    <artifactId>socle-plugin-myplugin</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!-- Dépendance Socle -->
        <dependency>
            <groupId>eu.lmvi</groupId>
            <artifactId>socle-v004</artifactId>
            <version>4.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

3.2 Auto-configuration

package com.mycompany.plugin;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.ComponentScan;

@AutoConfiguration
@ConditionalOnProperty(name = "socle.plugins.myplugin.enabled", havingValue = "true")
@ComponentScan(basePackages = "com.mycompany.plugin")
public class MyPluginAutoConfiguration {
    // Configuration automatique
}

3.3 Fichier spring.factories

# src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycompany.plugin.MyPluginAutoConfiguration

Ou pour Spring Boot 3.x :

# src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.mycompany.plugin.MyPluginAutoConfiguration

4. Types de plugins

4.1 Plugin Worker

package com.mycompany.plugin.worker;

import eu.lmvi.socle.worker.Worker;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(name = "socle.plugins.myplugin.enabled", havingValue = "true")
public class MyPluginWorker implements Worker {

    @Override
    public String getName() {
        return "my-plugin-worker";
    }

    @Override
    public void initialize() {
        // Initialisation
    }

    @Override
    public void start() {
        // Démarrage
    }

    @Override
    public void doWork() {
        // Traitement
    }

    @Override
    public void stop() {
        // Arrêt
    }

    @Override
    public boolean isHealthy() {
        return true;
    }

    @Override
    public Map<String, Object> getStats() {
        return Map.of();
    }
}

4.2 Plugin KvBus

package com.mycompany.plugin.kv;

import eu.lmvi.socle.kv.KvBus;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(name = "socle.kvbus.mode", havingValue = "custom")
public class CustomKvBus implements KvBus {

    @Override
    public void put(String key, String value) {
        // Implémentation custom
    }

    @Override
    public Optional<String> get(String key) {
        // Implémentation custom
        return Optional.empty();
    }

    // ... autres méthodes
}

4.3 Plugin Transport (LogForwarder)

package com.mycompany.plugin.logging;

import eu.lmvi.socle.logging.LogTransport;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(name = "socle.logging.forwarder.transport-mode", havingValue = "custom")
public class CustomLogTransport implements LogTransport {

    @Override
    public void send(List<LogEntry> entries) throws Exception {
        // Envoyer les logs vers votre système
    }

    @Override
    public boolean isAvailable() {
        return true;
    }

    @Override
    public void close() {
        // Cleanup
    }
}

5. Plugin Kafka (exemple complet)

5.1 Structure

socle-plugin-kafka/
├── pom.xml
├── src/main/java/eu/lmvi/socle/plugin/kafka/
│   ├── KafkaPluginAutoConfiguration.java
│   ├── KafkaPluginConfiguration.java
│   ├── KafkaConsumerWorker.java
│   ├── KafkaProducerService.java
│   └── KafkaHealthIndicator.java
└── src/main/resources/
    └── META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

5.2 Configuration

@ConfigurationProperties(prefix = "socle.plugins.kafka")
public class KafkaPluginConfiguration {
    private boolean enabled = false;
    private String bootstrapServers = "localhost:9092";
    private String groupId = "socle-group";
    private List<String> topics = new ArrayList<>();
    private Map<String, String> consumerProperties = new HashMap<>();
    private Map<String, String> producerProperties = new HashMap<>();

    // Getters/Setters
}

5.3 Auto-configuration

@AutoConfiguration
@ConditionalOnProperty(name = "socle.plugins.kafka.enabled", havingValue = "true")
@EnableConfigurationProperties(KafkaPluginConfiguration.class)
@ComponentScan(basePackages = "eu.lmvi.socle.plugin.kafka")
public class KafkaPluginAutoConfiguration {

    @Bean
    public KafkaConsumer<String, String> kafkaConsumer(KafkaPluginConfiguration config) {
        Properties props = new Properties();
        props.put("bootstrap.servers", config.getBootstrapServers());
        props.put("group.id", config.getGroupId());
        props.putAll(config.getConsumerProperties());
        return new KafkaConsumer<>(props);
    }

    @Bean
    public KafkaProducer<String, String> kafkaProducer(KafkaPluginConfiguration config) {
        Properties props = new Properties();
        props.put("bootstrap.servers", config.getBootstrapServers());
        props.putAll(config.getProducerProperties());
        return new KafkaProducer<>(props);
    }
}

5.4 Worker

@Component
@ConditionalOnProperty(name = "socle.plugins.kafka.enabled", havingValue = "true")
public class KafkaConsumerWorker extends AbstractWorker {

    private final KafkaConsumer<String, String> consumer;
    private final KafkaPluginConfiguration config;
    private final TechDbManager techDb;

    @Override
    public String getName() {
        return "kafka-consumer-plugin";
    }

    @Override
    protected void doInitialize() {
        consumer.subscribe(config.getTopics());
    }

    @Override
    protected void doProcess() {
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
        for (ConsumerRecord<String, String> record : records) {
            processRecord(record);
        }
    }

    @Override
    protected void doStop() {
        consumer.close();
    }
}

5.5 Utilisation

# application.yml
socle:
  plugins:
    kafka:
      enabled: true
      bootstrap-servers: kafka:9092
      group-id: my-app
      topics:
        - orders
        - events

6. Plugin NATS (exemple)

6.1 Configuration

@ConfigurationProperties(prefix = "socle.plugins.nats")
public class NatsPluginConfiguration {
    private boolean enabled = false;
    private String url = "nats://localhost:4222";
    private List<String> subjects = new ArrayList<>();
    private String streamName;
    private String consumerName;
}

6.2 Worker

@Component
@ConditionalOnProperty(name = "socle.plugins.nats.enabled", havingValue = "true")
public class NatsConsumerWorker extends AbstractWorker {

    private final NatsPluginConfiguration config;
    private Connection natsConnection;
    private JetStream jetStream;

    @Override
    protected void doInitialize() {
        natsConnection = Nats.connect(config.getUrl());
        jetStream = natsConnection.jetStream();
    }

    @Override
    protected void doProcess() {
        for (String subject : config.getSubjects()) {
            Message msg = jetStream.pullSubscribe(subject, config.getConsumerName())
                .fetch(100, Duration.ofSeconds(1))
                .stream()
                .findFirst()
                .orElse(null);

            if (msg != null) {
                processMessage(msg);
                msg.ack();
            }
        }
    }
}

7. Extension des APIs Admin

7.1 Controller additionnel

@RestController
@RequestMapping("/admin/plugins/kafka")
@ConditionalOnProperty(name = "socle.plugins.kafka.enabled", havingValue = "true")
public class KafkaAdminController {

    @Autowired
    private KafkaConsumerWorker worker;

    @GetMapping("/status")
    public Map<String, Object> status() {
        return Map.of(
            "connected", worker.isHealthy(),
            "stats", worker.getStats()
        );
    }

    @GetMapping("/offsets")
    public Map<String, Long> offsets() {
        return worker.getCurrentOffsets();
    }

    @PostMapping("/seek/{topic}/{partition}/{offset}")
    public void seek(
            @PathVariable String topic,
            @PathVariable int partition,
            @PathVariable long offset) {
        worker.seekTo(topic, partition, offset);
    }
}

8. Métriques du plugin

@Component
@ConditionalOnProperty(name = "socle.plugins.kafka.enabled", havingValue = "true")
public class KafkaPluginMetrics {

    private final Counter messagesReceived;
    private final Counter messagesProcessed;
    private final Timer processingTime;

    public KafkaPluginMetrics(MeterRegistry registry) {
        this.messagesReceived = Counter.builder("socle_kafka_messages_received_total")
            .description("Total Kafka messages received")
            .register(registry);

        this.messagesProcessed = Counter.builder("socle_kafka_messages_processed_total")
            .description("Total Kafka messages processed")
            .register(registry);

        this.processingTime = Timer.builder("socle_kafka_processing_duration_seconds")
            .description("Kafka message processing duration")
            .register(registry);
    }

    public void recordReceived() {
        messagesReceived.increment();
    }

    public void recordProcessed(Duration duration) {
        messagesProcessed.increment();
        processingTime.record(duration);
    }
}

9. Test du plugin

@SpringBootTest
@TestPropertySource(properties = {
    "socle.plugins.kafka.enabled=true",
    "socle.plugins.kafka.bootstrap-servers=localhost:9092"
})
class KafkaPluginTest {

    @Autowired
    private KafkaConsumerWorker worker;

    @Test
    void workerShouldBeRegistered() {
        assertNotNull(worker);
        assertEquals("kafka-consumer-plugin", worker.getName());
    }

    @Test
    void workerShouldStart() {
        worker.initialize();
        worker.start();
        assertTrue(worker.isHealthy());
    }
}

10. Publication du plugin

10.1 Maven deploy

<distributionManagement>
    <repository>
        <id>releases</id>
        <url>https://nexus.mycompany.com/repository/maven-releases/</url>
    </repository>
</distributionManagement>
mvn clean deploy

10.2 Utilisation dans une application

<dependency>
    <groupId>eu.lmvi</groupId>
    <artifactId>socle-plugin-kafka</artifactId>
    <version>1.0.0</version>
</dependency>

11. Bonnes pratiques

DO

  • Utiliser @ConditionalOnProperty pour activer/désactiver
  • Exposer la configuration via @ConfigurationProperties
  • Implémenter des health indicators
  • Exposer des métriques
  • Documenter les options de configuration

DON’T

  • Ne pas forcer l’activation par défaut
  • Ne pas dupliquer les fonctionnalités du core
  • Ne pas utiliser de dépendances en conflit avec le Socle
  • Ne pas bloquer le démarrage de l’application si le plugin échoue

12. Références

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *