Architecture Event-Driven avec ABAP Cloud

Catégorie
Architecture
Publié
Auteur
Johannes

L’architecture Event-Driven (EDA) est un pattern architectural qui découple les systèmes par des événements asynchrones. Dans ABAP Cloud, les RAP Business Events constituent la base de l’EDA – combinés avec SAP Event Mesh, ils permettent de créer des architectures performantes, évolutives et maintenables.

Qu’est-ce que l’Architecture Event-Driven ?

L’Architecture Event-Driven (EDA) est un paradigme de conception où les composants communiquent via des événements plutôt que par des appels de méthode directs :

┌─────────────────────────────────────────────────────────────────────────┐
│ Event-Driven Architecture │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ Event ┌─────────────────────────────────┐ │
│ │ Producer │─────────────────>│ Event Broker │ │
│ │ (ABAP BO) │ │ │ │
│ └──────────────┘ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Topic 1 │ │ Topic 2 │ ... │ │
│ │ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │
│ └───────┼─────────────┼───────────┘ │
│ │ │ │
│ ┌─────────────┼─────────────┼───────────┐ │
│ │ │ │ │ │
│ ▼ ▼ ▼ │ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │Consumer 1│ │Consumer 2│ │Consumer 3│ │ │
│ │ (Email) │ │(Analytics│ │ (Extern) │ │ │
│ └──────────┘ └──────────┘ └──────────┘ │ │
└─────────────────────────────────────────────────────────────────────────┘

Principes fondamentaux

PrincipeDescription
Couplage faibleLe Producer ne connaît pas le Consumer – les deux sont indépendants
AsynchronicitéLe Producer n’attend pas le traitement du Consumer
Fire & ForgetL’événement est déclenché, le traitement est la responsabilité du Consumer
Cohérence à termeLes données deviennent cohérentes ultérieurement, pas immédiatement
RésilienceLes erreurs du Consumer n’affectent pas le Producer

EDA vs. Request-Response

AspectRequest-ResponseEvent-Driven
CommunicationSynchroneAsynchrone
CouplageFortFaible
DisponibilitéLes deux doivent être en ligneDécouplé par le Broker
Tolérance aux pannesDéfaillances en cascadeErreurs isolées
ÉvolutivitéLimitée par les goulots d’étranglementÉvolutivité horizontale
LatenceDéterminéeVariable
DébogagePlus simple (Stack Trace)Plus complexe (Event Trace)

Composants EDA dans ABAP Cloud

ABAP Cloud offre plusieurs mécanismes pour l’EDA :

1. RAP Business Events (Local)

Les RAP Business Events permettent une communication basée sur les événements au sein d’un système ABAP :

define behavior for ZI_Order alias Order
persistent table zorder
lock master
authorization master ( instance )
{
create;
update;
delete;
// Définitions d'événements
event orderCreated;
event orderConfirmed parameter ZA_OrderConfirmedEvent;
event orderShipped parameter ZA_OrderShippedEvent;
event orderCancelled parameter ZA_OrderCancelledEvent;
action confirm result [1] $self;
action ship result [1] $self;
action cancel result [1] $self;
}

Avantages :

  • Définition simple dans le BDEF
  • Paramètres typés via Abstract Entities
  • Distribution gérée par le framework

2. SAP Event Mesh (Distribué)

SAP Event Mesh étend l’EDA aux systèmes distribués :

┌─────────────────────────────────────────────────────────────────────────┐
│ SAP Event Mesh │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ABAP Cloud Event Mesh Systèmes Consumer │
│ ┌───────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ RAP │ │ Message │ │ ABAP Cloud (autre) │ │
│ │ Business │─────>│ Broker │──────>│ Application CAP │ │
│ │ Events │ │ │ │ S/4HANA Cloud │ │
│ └───────────┘ └──────────────┘ │ Integration Suite │ │
│ │ Systèmes externes │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

Avantages :

  • Communication inter-systèmes
  • Files d’attente persistantes (retry en cas d’erreur)
  • Standard CloudEvents
  • Monitoring et alerting

Implémenter un Event Producer

Un Event Producer est un Business Object qui déclenche des événements lors de changements d’état.

Définir les paramètres d’événement

@EndUserText.label: 'Order Confirmed Event"
define abstract entity ZA_OrderConfirmedEvent
{
OrderId : abap.char(10);
CustomerId : abap.char(10);
ConfirmedBy : abap.uname;
ConfirmedAt : timestampl;
TotalAmount : abap.dec(15,2);
Currency : abap.cuky;
}
@EndUserText.label: 'Order Shipped Event"
define abstract entity ZA_OrderShippedEvent
{
OrderId : abap.char(10);
CustomerId : abap.char(10);
ShippedAt : timestampl;
TrackingNumber : abap.char(30);
CarrierId : abap.char(10);
}
@EndUserText.label: 'Order Cancelled Event"
define abstract entity ZA_OrderCancelledEvent
{
OrderId : abap.char(10);
CustomerId : abap.char(10);
CancelledBy : abap.uname;
CancelledAt : timestampl;
CancellationReason: abap.char(255);
}

Déclencher des événements dans les Actions

CLASS lhc_order DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS confirm FOR MODIFY
IMPORTING keys FOR ACTION Order~confirm RESULT result.
METHODS ship FOR MODIFY
IMPORTING keys FOR ACTION Order~ship RESULT result.
METHODS cancel FOR MODIFY
IMPORTING keys FOR ACTION Order~cancel RESULT result.
ENDCLASS.
CLASS lhc_order IMPLEMENTATION.
METHOD confirm.
" Changer le statut
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( Status ConfirmedBy ConfirmedAt )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'CF"
ConfirmedBy = cl_abap_context_info=>get_user_name( )
ConfirmedAt = utclong_current( ) ) )
FAILED failed
REPORTED reported.
" Lire les données mises à jour
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Déclencher l'événement
RAISE ENTITY EVENT zi_order~orderConfirmed
FROM VALUE #( FOR order IN orders
( %key = order-%key
%param = VALUE #(
orderid = order-OrderId
customerid = order-CustomerId
confirmedby = order-ConfirmedBy
confirmedat = order-ConfirmedAt
totalamount = order-TotalAmount
currency = order-Currency ) ) ).
" Retourner le résultat
result = VALUE #( FOR order IN orders
( %tky = order-%tky
%param = order ) ).
ENDMETHOD.
METHOD ship.
" Générer le numéro de suivi
DATA(lv_tracking) = |TRK-{ sy-datum }-{ sy-uzeit }|.
" Changer le statut
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( Status ShippedAt TrackingNumber )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'SH"
ShippedAt = utclong_current( )
TrackingNumber = lv_tracking ) )
FAILED failed
REPORTED reported.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Déclencher l'événement d'expédition
RAISE ENTITY EVENT zi_order~orderShipped
FROM VALUE #( FOR order IN orders
( %key = order-%key
%param = VALUE #(
orderid = order-OrderId
customerid = order-CustomerId
shippedat = order-ShippedAt
trackingnumber = order-TrackingNumber
carrierid = order-CarrierId ) ) ).
result = VALUE #( FOR order IN orders
( %tky = order-%tky
%param = order ) ).
ENDMETHOD.
METHOD cancel.
" Changer le statut
MODIFY ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
UPDATE FIELDS ( Status CancelledBy CancelledAt )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'CA"
CancelledBy = cl_abap_context_info=>get_user_name( )
CancelledAt = utclong_current( ) ) )
FAILED failed
REPORTED reported.
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Déclencher l'événement d'annulation
RAISE ENTITY EVENT zi_order~orderCancelled
FROM VALUE #( FOR order IN orders
( %key = order-%key
%param = VALUE #(
orderid = order-OrderId
customerid = order-CustomerId
cancelledby = order-CancelledBy
cancelledat = order-CancelledAt
cancellationreason = keys[ 1 ]-%param-reason ) ) ).
result = VALUE #( FOR order IN orders
( %tky = order-%tky
%param = order ) ).
ENDMETHOD.
ENDCLASS.

Déclencher des événements dans les Determinations

Pour les événements automatiques lors de modifications de champs :

define behavior for ZI_Order alias Order
persistent table zorder
{
create;
update;
// Événement à la création
event orderCreated;
// Événement lors de modification du montant
event amountChanged parameter ZA_AmountChangedEvent;
// Determinations pour les événements automatiques
determination raiseCreatedEvent on save { create; }
determination raiseAmountChangedEvent on save { field TotalAmount; }
}
CLASS lhc_order IMPLEMENTATION.
METHOD raiseCreatedEvent.
" Lire les nouvelles entités
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Événement Created pour toutes les nouvelles commandes
RAISE ENTITY EVENT zi_order~orderCreated
FROM VALUE #( FOR order IN orders
( %key = order-%key ) ).
ENDMETHOD.
METHOD raiseAmountChangedEvent.
" Lire les entités modifiées
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
FIELDS ( OrderId TotalAmount Currency )
WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Événement Amount Changed
RAISE ENTITY EVENT zi_order~amountChanged
FROM VALUE #( FOR order IN orders
( %key = order-%key
%param = VALUE #(
orderid = order-OrderId
newamount = order-TotalAmount
currency = order-Currency ) ) ).
ENDMETHOD.
ENDCLASS.

Implémenter un Event Consumer

Les Consumers réagissent aux événements et exécutent des actions de suivi.

Handler d’événement local

CLASS zcl_order_event_handler DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
PRIVATE SECTION.
METHODS handle_order_confirmed
IMPORTING it_events TYPE STANDARD TABLE.
METHODS handle_order_shipped
IMPORTING it_events TYPE STANDARD TABLE.
METHODS handle_order_cancelled
IMPORTING it_events TYPE STANDARD TABLE.
METHODS send_email
IMPORTING iv_recipient TYPE string
iv_subject TYPE string
iv_body TYPE string.
METHODS log_event
IMPORTING iv_event_type TYPE string
iv_entity_key TYPE string
iv_message TYPE string.
ENDCLASS.
CLASS zcl_order_event_handler IMPLEMENTATION.
METHOD if_rap_event_handler~handle.
CASE io_event->get_event_name( ).
WHEN 'ORDERCONFIRMED'.
handle_order_confirmed( io_event->get_business_data( ) ).
WHEN 'ORDERSHIPPED'.
handle_order_shipped( io_event->get_business_data( ) ).
WHEN 'ORDERCANCELLED'.
handle_order_cancelled( io_event->get_business_data( ) ).
ENDCASE.
ENDMETHOD.
METHOD handle_order_confirmed.
LOOP AT it_events INTO DATA(ls_event).
" 1. Notification client
TRY.
" Charger les données client
SELECT SINGLE email, name FROM zcustomer
WHERE customer_id = @ls_event-%param-customerid
INTO @DATA(ls_customer).
IF sy-subrc = 0.
send_email(
iv_recipient = ls_customer-email
iv_subject = |Confirmation de commande { ls_event-%param-orderid }|
iv_body = |Cher/Chère { ls_customer-name },|
&& |\n\nVotre commande a été confirmée.|
&& |\nNuméro de commande : { ls_event-%param-orderid }|
&& |\nMontant : { ls_event-%param-totalamount } { ls_event-%param-currency }|
).
ENDIF.
CATCH cx_root INTO DATA(lx_error).
log_event(
iv_event_type = 'ORDERCONFIRMED"
iv_entity_key = ls_event-%param-orderid
iv_message = |Erreur e-mail : { lx_error->get_text( ) }|
).
ENDTRY.
" 2. Tracking analytique
INSERT INTO zorder_analytics VALUES (
analytics_id = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( )
order_id = ls_event-%param-orderid
event_type = 'CONFIRMED"
event_time = utclong_current( )
amount = ls_event-%param-totalamount
).
" 3. Entrée de log
log_event(
iv_event_type = 'ORDERCONFIRMED"
iv_entity_key = ls_event-%param-orderid
iv_message = |Commande confirmée par { ls_event-%param-confirmedby }|
).
ENDLOOP.
ENDMETHOD.
METHOD handle_order_shipped.
LOOP AT it_events INTO DATA(ls_event).
" Notification d'expédition
TRY.
SELECT SINGLE email, name FROM zcustomer
WHERE customer_id = @ls_event-%param-customerid
INTO @DATA(ls_customer).
IF sy-subrc = 0.
send_email(
iv_recipient = ls_customer-email
iv_subject = |Votre commande a été expédiée|
iv_body = |Numéro de suivi : { ls_event-%param-trackingnumber }|
).
ENDIF.
CATCH cx_root INTO DATA(lx_error).
log_event(
iv_event_type = 'ORDERSHIPPED"
iv_entity_key = ls_event-%param-orderid
iv_message = |Échec de la notification d'expédition : { lx_error->get_text( ) }|
).
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD handle_order_cancelled.
LOOP AT it_events INTO DATA(ls_event).
" 1. Initier le remboursement
TRY.
DATA(lo_refund) = NEW zcl_refund_service( ).
lo_refund->initiate_refund( ls_event-%param-orderid ).
CATCH cx_root INTO DATA(lx_error).
log_event(
iv_event_type = 'ORDERCANCELLED"
iv_entity_key = ls_event-%param-orderid
iv_message = |Erreur de remboursement : { lx_error->get_text( ) }|
).
ENDTRY.
" 2. Libérer le stock réservé
DATA(lo_inventory) = NEW zcl_inventory_service( ).
lo_inventory->release_reservation( ls_event-%param-orderid ).
ENDLOOP.
ENDMETHOD.
METHOD send_email.
" Envoyer l'e-mail via cl_bcs_mail_message
DATA(lo_mail) = cl_bcs_mail_message=>create_instance( ).
lo_mail->set_sender( '[email protected]' ).
lo_mail->add_recipient( iv_recipient ).
lo_mail->set_subject( iv_subject ).
lo_mail->set_main( cl_bcs_mail_textpart=>create_instance(
iv_content = iv_body
iv_mimetype = 'text/plain"
) ).
lo_mail->send( ).
ENDMETHOD.
METHOD log_event.
TRY.
DATA(lo_log) = cl_bali_log=>create_with_header(
cl_bali_header_setter=>create(
iv_object = 'ZORDER_EVENTS"
iv_subobject = iv_event_type
iv_external_id = iv_entity_key
)
).
lo_log->add_item(
cl_bali_free_text_setter=>create(
severity = if_bali_constants=>c_severity_information
text = iv_message
)
).
cl_bali_log_db=>get_instance( )->save_log( lo_log ).
CATCH cx_bali_runtime.
" Ignorer les erreurs de logging
ENDTRY.
ENDMETHOD.
ENDCLASS.

Plusieurs Consumers pour un événement

L’EDA permet plusieurs Consumers indépendants pour le même événement :

" Consumer 1 : Notifications par e-mail
CLASS zcl_notification_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
ENDCLASS.
" Consumer 2 : Analytics
CLASS zcl_analytics_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
ENDCLASS.
" Consumer 3 : Audit Trail
CLASS zcl_audit_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
ENDCLASS.
" Consumer 4 : Synchronisation système externe
CLASS zcl_external_sync_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
ENDCLASS.

Chaque Consumer traite l’événement indépendamment – les erreurs dans un Consumer n’affectent pas les autres.

Intégration avec SAP Event Mesh

Pour une EDA inter-systèmes, utilisez SAP Event Mesh comme Message Broker.

Publier un événement vers Event Mesh

CLASS zcl_event_mesh_publisher DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
PRIVATE SECTION.
METHODS publish_to_event_mesh
IMPORTING iv_topic TYPE string
iv_payload TYPE string
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
ENDCLASS.
CLASS zcl_event_mesh_publisher IMPLEMENTATION.
METHOD if_rap_event_handler~handle.
DATA(lv_event_name) = io_event->get_event_name( ).
DATA(lt_events) = io_event->get_business_data( ).
" Topic basé sur le type d'événement
DATA(lv_topic) = |sap/order/{ to_lower( lv_event_name ) }/v1|.
LOOP AT lt_events INTO DATA(ls_event).
" Formater l'événement en JSON CloudEvents
DATA(lv_payload) = format_cloud_event(
iv_type = lv_event_name
is_data = ls_event
).
TRY.
publish_to_event_mesh(
iv_topic = lv_topic
iv_payload = lv_payload
).
CATCH cx_root INTO DATA(lx_error).
" Logger l'erreur mais continuer le traitement
log_publish_error( lx_error ).
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD publish_to_event_mesh.
" Destination depuis le Communication Arrangement
DATA(lo_dest) = cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = 'Z_EVENT_MESH"
service_id = 'Z_EVENT_MESH_PUBLISH"
).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_dest ).
DATA(lo_request) = lo_client->get_http_request( ).
" Header CloudEvents
lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/cloudevents+json' ).
lo_request->set_header_field( i_name = 'x-qos' i_value = '1' ). " At least once
lo_request->set_text( iv_payload ).
" POST vers le Topic Event Mesh
DATA(lo_response) = lo_client->execute(
i_method = if_web_http_client=>post
i_uri = |/messagingrest/v1/topics/{ iv_topic }/messages|
).
IF lo_response->get_status( )-code <> 204.
RAISE EXCEPTION TYPE cx_web_http_client_error.
ENDIF.
ENDMETHOD.
ENDCLASS.

Format CloudEvents

SAP Event Mesh utilise le standard CloudEvents :

{
"specversion": "1.0",
"type": "sap.order.orderconfirmed.v1",
"source": "/sap/abap/ZI_ORDER",
"id": "550e8400-e29b-41d4-a716-446655440000",
"time": "2026-02-14T10:30:00Z",
"datacontenttype": "application/json",
"data": {
"orderId": "0000000042",
"customerId": "CUST001",
"confirmedBy": "JSMITH",
"confirmedAt": "2026-02-14T10:30:00Z",
"totalAmount": 1500.00,
"currency": "EUR"
}
}
METHOD format_cloud_event.
DATA(lv_uuid) = cl_uuid_factory=>create_system_uuid( )->create_uuid_c32( ).
DATA(lv_timestamp) = format_timestamp( utclong_current( ) ).
DATA(lv_data_json) = /ui2/cl_json=>serialize( data = is_data ).
rv_json = |\{|
&& |"specversion":"1.0",|
&& |"type":"sap.order.{ to_lower( iv_type ) }.v1",|
&& |"source":"/sap/abap/ZI_ORDER",|
&& |"id":"{ lv_uuid }",|
&& |"time":"{ lv_timestamp }",|
&& |"datacontenttype":"application/json",|
&& |"data":{ lv_data_json }|
&& |\}|.
ENDMETHOD.

Consommer des événements depuis Event Mesh

Les événements externes peuvent être consommés via des webhooks HTTP ou l’API REST Event Mesh.

CLASS zcl_event_mesh_consumer DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_http_service_extension.
PRIVATE SECTION.
METHODS process_cloud_event
IMPORTING iv_payload TYPE string
RAISING cx_root.
ENDCLASS.
CLASS zcl_event_mesh_consumer IMPLEMENTATION.
METHOD if_http_service_extension~handle_request.
" Accepter uniquement POST
IF request->get_method( ) <> 'POST'.
response->set_status( i_code = 405 i_reason = 'Method Not Allowed' ).
RETURN.
ENDIF.
TRY.
DATA(lv_payload) = request->get_text( ).
process_cloud_event( lv_payload ).
" Succès
response->set_status( i_code = 200 i_reason = 'OK' ).
CATCH cx_root INTO DATA(lx_error).
" Logger l'erreur
response->set_status( i_code = 500 i_reason = lx_error->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD process_cloud_event.
" Parser le CloudEvent
DATA: BEGIN OF ls_event,
type TYPE string,
data TYPE string,
END OF ls_event.
/ui2/cl_json=>deserialize(
EXPORTING json = iv_payload
CHANGING data = ls_event
).
" Évaluer le type d'événement
CASE ls_event-type.
WHEN 'sap.inventory.stockupdated.v1'.
" Mettre à jour le stock
process_stock_update( ls_event-data ).
WHEN 'sap.customer.updated.v1'.
" Synchroniser les données client
sync_customer_data( ls_event-data ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

Patterns EDA

Pattern 1 : Event Sourcing

Stocker tous les changements d’état sous forme d’événements :

" Chaque modification est persistée comme événement
CLASS zcl_order_event_store DEFINITION PUBLIC.
PUBLIC SECTION.
METHODS append_event
IMPORTING iv_aggregate_id TYPE string
iv_event_type TYPE string
is_event_data TYPE any.
METHODS replay_events
IMPORTING iv_aggregate_id TYPE string
RETURNING VALUE(rs_state) TYPE zs_order_state.
ENDCLASS.
CLASS zcl_order_event_store IMPLEMENTATION.
METHOD append_event.
INSERT INTO zorder_events VALUES (
event_id = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( )
aggregate_id = iv_aggregate_id
event_type = iv_event_type
event_data = /ui2/cl_json=>serialize( is_event_data )
created_at = utclong_current( )
sequence_nr = get_next_sequence( iv_aggregate_id )
).
ENDMETHOD.
METHOD replay_events.
" Charger tous les événements pour cet agrégat chronologiquement
SELECT * FROM zorder_events
WHERE aggregate_id = @iv_aggregate_id
ORDER BY sequence_nr
INTO TABLE @DATA(lt_events).
" Reconstruire l'état par replay
LOOP AT lt_events INTO DATA(ls_event).
CASE ls_event-event_type.
WHEN 'CREATED'.
apply_created( CHANGING cs_state = rs_state is_event = ls_event ).
WHEN 'CONFIRMED'.
apply_confirmed( CHANGING cs_state = rs_state is_event = ls_event ).
WHEN 'SHIPPED'.
apply_shipped( CHANGING cs_state = rs_state is_event = ls_event ).
WHEN 'CANCELLED'.
apply_cancelled( CHANGING cs_state = rs_state is_event = ls_event ).
ENDCASE.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Pattern 2 : Pattern Saga

Coordination de transactions distribuées via événements :

" Order Saga : Commande → Paiement → Expédition
CLASS zcl_order_saga DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
PRIVATE SECTION.
METHODS handle_order_created.
METHODS handle_payment_completed.
METHODS handle_payment_failed.
METHODS handle_shipment_completed.
METHODS compensate_order.
ENDCLASS.
CLASS zcl_order_saga IMPLEMENTATION.
METHOD if_rap_event_handler~handle.
CASE io_event->get_event_name( ).
WHEN 'ORDERCREATED'.
" Étape 1 : Initier le paiement
initiate_payment( io_event->get_business_data( ) ).
WHEN 'PAYMENTCOMPLETED'.
" Étape 2 : Initier l'expédition
initiate_shipment( io_event->get_business_data( ) ).
WHEN 'PAYMENTFAILED'.
" Compensation : Annuler la commande
compensate_order( io_event->get_business_data( ) ).
WHEN 'SHIPMENTCOMPLETED'.
" Saga terminée
complete_saga( io_event->get_business_data( ) ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

Pattern 3 : Agrégation d’événements

Combiner plusieurs événements en un seul :

CLASS zcl_daily_summary_aggregator DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
PRIVATE SECTION.
CLASS-DATA: gv_order_count TYPE i,
gv_total_revenue TYPE p DECIMALS 2,
gv_cancellations TYPE i.
METHODS check_and_publish_summary.
ENDCLASS.
CLASS zcl_daily_summary_aggregator IMPLEMENTATION.
METHOD if_rap_event_handler~handle.
CASE io_event->get_event_name( ).
WHEN 'ORDERCONFIRMED'.
gv_order_count = gv_order_count + 1.
" Ajouter le chiffre d'affaires depuis les données d'événement
LOOP AT io_event->get_business_data( ) INTO DATA(ls_event).
gv_total_revenue = gv_total_revenue + ls_event-%param-totalamount.
ENDLOOP.
WHEN 'ORDERCANCELLED'.
gv_cancellations = gv_cancellations + 1.
ENDCASE.
" Vérifier si l'événement résumé est dû
check_and_publish_summary( ).
ENDMETHOD.
METHOD check_and_publish_summary.
" Par ex. tous les 100 événements ou une fois par heure
IF gv_order_count MOD 100 = 0.
" Déclencher l'événement résumé
RAISE EVENT daily_summary
EXPORTING
iv_order_count = gv_order_count
iv_total_revenue = gv_total_revenue
iv_cancellations = gv_cancellations.
ENDIF.
ENDMETHOD.
ENDCLASS.

Pattern 4 : Dead Letter Queue

Stocker séparément les événements en erreur :

CLASS zcl_event_handler_with_dlq DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
PRIVATE SECTION.
CONSTANTS c_max_retries TYPE i VALUE 3.
METHODS move_to_dead_letter_queue
IMPORTING is_event TYPE any
iv_error_msg TYPE string
iv_retry_count TYPE i.
ENDCLASS.
CLASS zcl_event_handler_with_dlq IMPLEMENTATION.
METHOD if_rap_event_handler~handle.
LOOP AT io_event->get_business_data( ) INTO DATA(ls_event).
DATA(lv_retry_count) = 0.
DO c_max_retries TIMES.
TRY.
process_event( ls_event ).
EXIT. " Succès, événement suivant
CATCH cx_root INTO DATA(lx_error).
lv_retry_count = lv_retry_count + 1.
IF lv_retry_count >= c_max_retries.
" Max retries atteint → Dead Letter Queue
move_to_dead_letter_queue(
is_event = ls_event
iv_error_msg = lx_error->get_text( )
iv_retry_count = lv_retry_count
).
ELSE.
" Attendre avant retry (exponential backoff)
WAIT UP TO ( 2 ** lv_retry_count ) SECONDS.
ENDIF.
ENDTRY.
ENDDO.
ENDLOOP.
ENDMETHOD.
METHOD move_to_dead_letter_queue.
INSERT INTO zdead_letter_queue VALUES (
dlq_id = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( )
event_type = io_event->get_event_name( )
event_data = /ui2/cl_json=>serialize( is_event )
error_msg = iv_error_msg
retry_count = iv_retry_count
created_at = utclong_current( )
status = 'PENDING"
).
" Déclencher une alerte
cl_bali_log=>create_with_header(
cl_bali_header_setter=>create( iv_object = 'ZDLQ' iv_subobject = 'ERROR' )
)->add_item(
cl_bali_free_text_setter=>create(
severity = if_bali_constants=>c_severity_error
text = |Événement échoué après { iv_retry_count } tentatives : { iv_error_msg }|
)
)->save( ).
ENDMETHOD.
ENDCLASS.

Garantir l’idempotence

Les événements peuvent être délivrés plusieurs fois. Les Consumers doivent être idempotents :

CLASS zcl_idempotent_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
PRIVATE SECTION.
METHODS is_event_processed
IMPORTING iv_event_id TYPE string
RETURNING VALUE(rv_result) TYPE abap_bool.
METHODS mark_event_processed
IMPORTING iv_event_id TYPE string.
ENDCLASS.
CLASS zcl_idempotent_handler IMPLEMENTATION.
METHOD if_rap_event_handler~handle.
LOOP AT io_event->get_business_data( ) INTO DATA(ls_event).
" ID d'événement unique (par ex. depuis CloudEvents)
DATA(lv_event_id) = ls_event-%param-event_id.
" Vérifier si déjà traité
IF is_event_processed( lv_event_id ).
" Ignorer - déjà traité
CONTINUE.
ENDIF.
" Traiter l'événement
TRY.
process_event( ls_event ).
" Marquer comme traité
mark_event_processed( lv_event_id ).
CATCH cx_root INTO DATA(lx_error).
" Gestion des erreurs
log_error( lx_error ).
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD is_event_processed.
SELECT SINGLE @abap_true FROM zprocessed_events
WHERE event_id = @iv_event_id
INTO @rv_result.
ENDMETHOD.
METHOD mark_event_processed.
INSERT INTO zprocessed_events VALUES (
event_id = iv_event_id
processed_at = utclong_current( )
).
ENDMETHOD.
ENDCLASS.

Monitoring et dépannage

Monitoring des événements

" Requête de monitoring pour le traitement des événements
SELECT
event_type,
COUNT(*) AS total_count,
SUM( CASE WHEN status = 'SUCCESS' THEN 1 ELSE 0 END ) AS success_count,
SUM( CASE WHEN status = 'FAILED' THEN 1 ELSE 0 END ) AS failed_count,
AVG( processing_time_ms ) AS avg_processing_time
FROM zevent_log
WHERE created_at >= @lv_today_start
GROUP BY event_type
INTO TABLE @DATA(lt_metrics).

Traçage des événements

CLASS zcl_event_tracer DEFINITION PUBLIC.
PUBLIC SECTION.
CLASS-METHODS trace
IMPORTING iv_event_id TYPE string
iv_event_type TYPE string
iv_phase TYPE string " PUBLISHED, RECEIVED, PROCESSING, COMPLETED, FAILED
iv_details TYPE string OPTIONAL.
ENDCLASS.
CLASS zcl_event_tracer IMPLEMENTATION.
METHOD trace.
INSERT INTO zevent_trace VALUES (
trace_id = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( )
event_id = iv_event_id
event_type = iv_event_type
phase = iv_phase
details = iv_details
timestamp = utclong_current( )
user_name = cl_abap_context_info=>get_user_name( )
).
ENDMETHOD.
ENDCLASS.

Bonnes pratiques

DomaineRecommandation
Conception d’événementUtiliser le passé (orderCreated, pas createOrder)
PayloadUniquement les données nécessaires, pas d’entités complètes
IdempotenceChaque événement doit pouvoir être traité plusieurs fois en toute sécurité
Tolérance aux pannesLes erreurs du Consumer ne doivent pas affecter le Producer
VersionnementNe jamais casser le schéma d’événement, uniquement l’étendre (v1, v2)
MonitoringLogger tous les événements, collecter des métriques
Dead Letter QueueStocker séparément les événements échoués
TestsTester les événements et les handlers séparément
DocumentationMaintenir un catalogue d’événements avec schéma et description
SécuritéSécuriser Event Mesh avec OAuth 2.0

Sujets connexes