Event Bus Applicatif (Spring) #12

Closed
opened 2025-12-22 20:29:29 +01:00 by ronan.quintin · 0 comments

Spécification – Event Bus applicatif (Spring)

1. Contexte

L’application Keryloo adopte une architecture orientée événements afin de :

  • Découpler les traitements métiers et techniques
  • Gérer des post-actions (PDF, GED, email, workflow)
  • Préparer l’introduction d’un moteur de workflow
  • Éviter les dépendances directes entre modules

Cette spécification décrit la mise en place d’un Event Bus interne, basé sur les mécanismes natifs de Spring Events.


2. Objectifs

  • Permettre l’émission d’événements métiers lors des opérations CRUD
  • Permettre l’abonnement via des listeners découplés
  • Garantir la cohérence transactionnelle
  • Servir de socle au moteur de workflow
  • Rester simple, testable et adapté à une application de petite taille

3. Périmètre

Inclus

  • Event Bus interne Spring
  • Événements métiers (Domain Events)
  • Publication depuis la couche service
  • Listeners synchrones et asynchrones
  • Intégration avec la gestion transactionnelle

Exclus

  • Kafka / RabbitMQ / messagerie externe
  • Event sourcing
  • Persistence des événements
  • Orchestration métier complexe (gérée par le workflow)

4. Concepts

4.1 Domain Event

Un Domain Event représente un fait métier déjà survenu.

Règles :

  • Nom au passé (PaymentSavedEvent)
  • Immuable
  • Sans logique métier
  • Transporte uniquement des données

Exemple d’interface :

public interface DomainEvent {

4.2 Événements concrets

public record PaymentSavedEvent(
    Payment payment
) implements DomainEvent {

    @Override
    public Instant occurredAt() {
        return Instant.now();
    }
}

Autres exemples :

  • PaymentDeletedEvent
  • ReceiptCreatedEvent
  • DocumentStoredEvent

5. Publication des événements

5.1 Abstraction de publication

Pour éviter le couplage direct à Spring :

public interface DomainEventPublisher {
    void publish(DomainEvent event);
}

5.2 Implémentation Spring

@Component
@RequiredArgsConstructor
public class SpringDomainEventPublisher implements DomainEventPublisher {

    private final ApplicationEventPublisher publisher;

    @Override
    public void publish(DomainEvent event) {
        publisher.publishEvent(event);
    }
}

6. Intégration dans les services métier

Les événements doivent être émis depuis la couche service, jamais depuis les controllers ou les repositories.

6.1 Service générique

@RequiredArgsConstructor
public abstract class GenericBusinessService<E> {

    protected final DomainEventPublisher eventPublisher;

    protected void publishAfterSave(E entity) {
        // implémentation optionnelle
    }

    protected void publishAfterDelete(E entity) {
        // implémentation optionnelle
    }
}

6.2 Exemple concret

@Service
public class PaymentService extends GenericBusinessService<Payment> {

    @Override
    protected void publishAfterSave(Payment payment) {
        eventPublisher.publish(new PaymentSavedEvent(payment));
    }
}

7. Listeners

7.1 Listener simple

@Component
public class PaymentEventListener {

    @EventListener
    public void onPaymentSaved(PaymentSavedEvent event) {
        // traitement simple
    }
}

7.2 Listener transactionnel (recommandé)

Tous les traitements techniques doivent être exécutés après commit.

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onPaymentSaved(PaymentSavedEvent event) {
    // traitement post-commit
}

7.3 Listener asynchrone

Pour PDF, GED, emails, etc.

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onPaymentSaved(PaymentSavedEvent event) {
}

Configuration requise :

@EnableAsync

8. Bonnes pratiques

À faire

  • Émettre les événements depuis les services métier
  • Utiliser @TransactionalEventListener(AFTER_COMMIT)
  • Préférer plusieurs listeners spécialisés
  • Rendre les listeners idempotents
  • Séparer événements métiers et traitements techniques
  • Brancher le moteur de workflow comme listener

À éviter

  • Logique métier complexe dans les listeners
  • Chaînage implicite entre listeners
  • Dépendances entre listeners
  • Utilisation excessive de @Order
  • Utiliser l’Event Bus comme système de commande
  1. Intégration avec le moteur de workflow
  • Le moteur de workflow est un consommateur d’événements.
@Component
@RequiredArgsConstructor
public class WorkflowEventListener {

    private final WorkflowEngine workflowEngine;

    @EventListener
    public void onDomainEvent(DomainEvent event) {
        workflowEngine.handle(event);
    }
}

10. Tests

  • Les événements doivent être testables indépendamment de Spring
  • DomainEventPublisher doit être mockable
  • Les listeners doivent être testés isolément
  • Aucun test ne doit dépendre de l’ordre d’exécution des listeners

11. Évolutions futures

  • Persistence optionnelle des événements
  • Dispatcher avancé
  • Migration possible vers Kafka si besoin de scalabilité

12. Mise à jour des agents Claude

Agent architect

  • Favoriser une architecture événementielle interne
  • Refuser Kafka sans justification explicite
  • Séparer strictement :
    • émission d’événements
    • logique métier
    • logique technique

Considérer le workflow comme un consommateur d’événements

Agent backend-developer

  • Émettre les événements dans les services métier
  • Utiliser @TransactionalEventListener(AFTER_COMMIT)
  • Utiliser @Async pour les traitements techniques
  • Ne pas implémenter de logique métier complexe dans les listeners
  • Respecter le nommage et la sémantique des Domain Events
# Spécification – Event Bus applicatif (Spring) ## 1. Contexte L’application **Keryloo** adopte une architecture orientée événements afin de : - Découpler les traitements métiers et techniques - Gérer des post-actions (PDF, GED, email, workflow) - Préparer l’introduction d’un moteur de workflow - Éviter les dépendances directes entre modules Cette spécification décrit la mise en place d’un **Event Bus interne**, basé sur les mécanismes natifs de **Spring Events**. --- ## 2. Objectifs - Permettre l’émission d’événements métiers lors des opérations CRUD - Permettre l’abonnement via des listeners découplés - Garantir la cohérence transactionnelle - Servir de socle au moteur de workflow - Rester simple, testable et adapté à une application de petite taille --- ## 3. Périmètre ### Inclus - Event Bus interne Spring - Événements métiers (Domain Events) - Publication depuis la couche service - Listeners synchrones et asynchrones - Intégration avec la gestion transactionnelle ### Exclus - Kafka / RabbitMQ / messagerie externe - Event sourcing - Persistence des événements - Orchestration métier complexe (gérée par le workflow) --- ## 4. Concepts ### 4.1 Domain Event Un **Domain Event** représente un fait métier déjà survenu. Règles : - Nom au passé (`PaymentSavedEvent`) - Immuable - Sans logique métier - Transporte uniquement des données Exemple d’interface : ```java public interface DomainEvent { ``` ### 4.2 Événements concrets ```java public record PaymentSavedEvent( Payment payment ) implements DomainEvent { @Override public Instant occurredAt() { return Instant.now(); } } ``` Autres exemples : - PaymentDeletedEvent - ReceiptCreatedEvent - DocumentStoredEvent # 5. Publication des événements ## 5.1 Abstraction de publication Pour éviter le couplage direct à Spring : ```java public interface DomainEventPublisher { void publish(DomainEvent event); } ``` ## 5.2 Implémentation Spring ```java @Component @RequiredArgsConstructor public class SpringDomainEventPublisher implements DomainEventPublisher { private final ApplicationEventPublisher publisher; @Override public void publish(DomainEvent event) { publisher.publishEvent(event); } } ``` # 6. Intégration dans les services métier Les événements doivent être émis depuis la couche service, jamais depuis les controllers ou les repositories. ## 6.1 Service générique ```java @RequiredArgsConstructor public abstract class GenericBusinessService<E> { protected final DomainEventPublisher eventPublisher; protected void publishAfterSave(E entity) { // implémentation optionnelle } protected void publishAfterDelete(E entity) { // implémentation optionnelle } } ``` ## 6.2 Exemple concret ```java @Service public class PaymentService extends GenericBusinessService<Payment> { @Override protected void publishAfterSave(Payment payment) { eventPublisher.publish(new PaymentSavedEvent(payment)); } } ``` # 7. Listeners ## 7.1 Listener simple ```java @Component public class PaymentEventListener { @EventListener public void onPaymentSaved(PaymentSavedEvent event) { // traitement simple } } ``` ## 7.2 Listener transactionnel (recommandé) Tous les traitements techniques doivent être exécutés après commit. ```java @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onPaymentSaved(PaymentSavedEvent event) { // traitement post-commit } ``` ## 7.3 Listener asynchrone Pour PDF, GED, emails, etc. ```java @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onPaymentSaved(PaymentSavedEvent event) { } ``` Configuration requise : ```java @EnableAsync ``` # 8. Bonnes pratiques À faire - Émettre les événements depuis les services métier - Utiliser @TransactionalEventListener(AFTER_COMMIT) - Préférer plusieurs listeners spécialisés - Rendre les listeners idempotents - Séparer événements métiers et traitements techniques - Brancher le moteur de workflow comme listener À éviter - Logique métier complexe dans les listeners - Chaînage implicite entre listeners - Dépendances entre listeners - Utilisation excessive de @Order - Utiliser l’Event Bus comme système de commande 9. Intégration avec le moteur de workflow - Le moteur de workflow est un consommateur d’événements. ```java @Component @RequiredArgsConstructor public class WorkflowEventListener { private final WorkflowEngine workflowEngine; @EventListener public void onDomainEvent(DomainEvent event) { workflowEngine.handle(event); } } ``` # 10. Tests - Les événements doivent être testables indépendamment de Spring - DomainEventPublisher doit être mockable - Les listeners doivent être testés isolément - Aucun test ne doit dépendre de l’ordre d’exécution des listeners # 11. Évolutions futures - Persistence optionnelle des événements - Dispatcher avancé - Migration possible vers Kafka si besoin de scalabilité # 12. Mise à jour des agents Claude **Agent architect** - Favoriser une architecture événementielle interne - Refuser Kafka sans justification explicite - Séparer strictement : - émission d’événements - logique métier - logique technique Considérer le workflow comme un consommateur d’événements **Agent backend-developer** - Émettre les événements dans les services métier - Utiliser @TransactionalEventListener(AFTER_COMMIT) - Utiliser @Async pour les traitements techniques - Ne pas implémenter de logique métier complexe dans les listeners - Respecter le nommage et la sémantique des Domain Events
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: ronan.quintin/Keryloo#12
No description provided.