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
@ConditionalOnPropertypour 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
- 05-WORKERS – Workers
- 15-METRICS – Métriques
- Spring Boot Auto-configuration

Laisser un commentaire