Event-Driven Architecture mit ABAP Cloud

kategorie
Architektur
Veröffentlicht
autor
Johannes

Event-Driven Architecture (EDA) ist ein Architekturmuster, das Systeme durch asynchrone Events entkoppelt. In ABAP Cloud bilden RAP Business Events die Basis für EDA – kombiniert mit SAP Event Mesh entstehen leistungsfähige, skalierbare und wartbare Architekturen.

Was ist Event-Driven Architecture?

Event-Driven Architecture (EDA) ist ein Designparadigma, bei dem Komponenten über Events kommunizieren statt über direkte Methodenaufrufe:

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

Kernprinzipien

PrinzipBeschreibung
Loose CouplingProducer kennt Consumer nicht – beide sind unabhängig
AsynchronitätProducer wartet nicht auf Consumer-Verarbeitung
Fire & ForgetEvent wird ausgelöst, Verarbeitung ist Aufgabe des Consumers
Eventual ConsistencyDaten werden letztlich konsistent, nicht sofort
ResilienzConsumer-Fehler beeinflussen Producer nicht

EDA vs. Request-Response

AspektRequest-ResponseEvent-Driven
KommunikationSynchronAsynchron
KopplungEngLose
VerfügbarkeitBeide müssen online seinEntkoppelt durch Broker
FehlertoleranzCascading FailuresIsolierte Fehler
SkalierbarkeitLimitiert durch EngpässeHorizontal skalierbar
LatenzDeterminiertVariabel
DebuggingEinfacher (Stack Trace)Komplexer (Event Trace)

EDA-Bausteine in ABAP Cloud

ABAP Cloud bietet mehrere Mechanismen für EDA:

1. RAP Business Events (Lokal)

RAP Business Events ermöglichen event-basierte Kommunikation innerhalb eines ABAP-Systems:

define behavior for ZI_Order alias Order
persistent table zorder
lock master
authorization master ( instance )
{
create;
update;
delete;
// Event-Definitionen
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;
}

Vorteile:

  • Einfache Definition in BDEF
  • Typsichere Parameter über Abstract Entities
  • Framework-gestützte Auslieferung

2. SAP Event Mesh (Verteilt)

SAP Event Mesh erweitert EDA auf verteilte Systeme:

┌─────────────────────────────────────────────────────────────────────────┐
│ SAP Event Mesh │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ABAP Cloud Event Mesh Consumer Systeme │
│ ┌───────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ RAP │ │ Message │ │ ABAP Cloud (anderes) │ │
│ │ Business │─────>│ Broker │──────>│ CAP Application │ │
│ │ Events │ │ │ │ S/4HANA Cloud │ │
│ └───────────┘ └──────────────┘ │ Integration Suite │ │
│ │ Externe Systeme │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

Vorteile:

  • System-übergreifende Kommunikation
  • Persistente Queues (Retry bei Fehlern)
  • CloudEvents-Standard
  • Monitoring und Alerting

Event Producer implementieren

Ein Event Producer ist ein Business Object, das Events bei Zustandsänderungen auslöst.

Event-Parameter definieren

@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);
}

Events in Actions auslösen

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.
" Status ändern
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.
" Aktualisierte Daten lesen
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Event auslösen
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 ) ) ).
" Result zurückgeben
result = VALUE #( FOR order IN orders
( %tky = order-%tky
%param = order ) ).
ENDMETHOD.
METHOD ship.
" Tracking-Nummer generieren
DATA(lv_tracking) = |TRK-{ sy-datum }-{ sy-uzeit }|.
" Status ändern
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).
" Shipping Event auslösen
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.
" Status ändern
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).
" Cancel Event auslösen
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.

Events in Determinations auslösen

Für automatische Events bei Feldänderungen:

define behavior for ZI_Order alias Order
persistent table zorder
{
create;
update;
// Event bei Create
event orderCreated;
// Event bei Betragsänderung
event amountChanged parameter ZA_AmountChangedEvent;
// Determinations für automatische Events
determination raiseCreatedEvent on save { create; }
determination raiseAmountChangedEvent on save { field TotalAmount; }
}
CLASS lhc_order IMPLEMENTATION.
METHOD raiseCreatedEvent.
" Neue Entities lesen
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Created Event für alle neuen Orders
RAISE ENTITY EVENT zi_order~orderCreated
FROM VALUE #( FOR order IN orders
( %key = order-%key ) ).
ENDMETHOD.
METHOD raiseAmountChangedEvent.
" Geänderte Entities lesen
READ ENTITIES OF zi_order IN LOCAL MODE
ENTITY Order
FIELDS ( OrderId TotalAmount Currency )
WITH CORRESPONDING #( keys )
RESULT DATA(orders).
" Amount Changed Event
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.

Event Consumer implementieren

Consumer reagieren auf Events und führen Folgeaktionen aus.

Lokaler Event Handler

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. Kundenbenachrichtigung
TRY.
" Kundendaten laden
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 = |Bestellbestätigung { ls_event-%param-orderid }|
iv_body = |Sehr geehrte/r { ls_customer-name },|
&& |\n\nIhre Bestellung wurde bestätigt.|
&& |\nBestellnummer: { ls_event-%param-orderid }|
&& |\nBetrag: { 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 = |Email-Fehler: { lx_error->get_text( ) }|
).
ENDTRY.
" 2. Analytics tracken
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. Log-Eintrag
log_event(
iv_event_type = 'ORDERCONFIRMED'
iv_entity_key = ls_event-%param-orderid
iv_message = |Order confirmed by { ls_event-%param-confirmedby }|
).
ENDLOOP.
ENDMETHOD.
METHOD handle_order_shipped.
LOOP AT it_events INTO DATA(ls_event).
" Versandbenachrichtigung
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 = |Ihre Bestellung wurde versendet|
iv_body = |Tracking-Nummer: { 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 = |Versandbenachrichtigung fehlgeschlagen: { lx_error->get_text( ) }|
).
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD handle_order_cancelled.
LOOP AT it_events INTO DATA(ls_event).
" 1. Rückerstattung initiieren
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 = |Refund-Fehler: { lx_error->get_text( ) }|
).
ENDTRY.
" 2. Lagerbestand zurückbuchen
DATA(lo_inventory) = NEW zcl_inventory_service( ).
lo_inventory->release_reservation( ls_event-%param-orderid ).
ENDLOOP.
ENDMETHOD.
METHOD send_email.
" Email via cl_bcs_mail_message senden
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.
" Logging-Fehler ignorieren
ENDTRY.
ENDMETHOD.
ENDCLASS.

Mehrere Consumer für ein Event

EDA ermöglicht mehrere unabhängige Consumer für dasselbe Event:

" Consumer 1: Email-Benachrichtigungen
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: External System Sync
CLASS zcl_external_sync_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_event_handler.
ENDCLASS.

Jeder Consumer verarbeitet das Event unabhängig – Fehler in einem Consumer beeinflussen die anderen nicht.

Integration mit SAP Event Mesh

Für system-übergreifende EDA nutzt du SAP Event Mesh als Message Broker.

Event nach Event Mesh publizieren

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 basierend auf Event-Typ
DATA(lv_topic) = |sap/order/{ to_lower( lv_event_name ) }/v1|.
LOOP AT lt_events INTO DATA(ls_event).
" Event als CloudEvents-JSON formatieren
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).
" Fehler loggen, aber weiter verarbeiten
log_publish_error( lx_error ).
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD publish_to_event_mesh.
" Destination aus 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( ).
" CloudEvents Header
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 an Event Mesh Topic
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.

CloudEvents-Format

SAP Event Mesh nutzt den CloudEvents-Standard:

{
"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.

Events von Event Mesh konsumieren

Externe Events können über HTTP-Webhooks oder die Event Mesh REST API konsumiert werden.

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.
" Nur POST akzeptieren
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 ).
" Erfolg
response->set_status( i_code = 200 i_reason = 'OK' ).
CATCH cx_root INTO DATA(lx_error).
" Fehler loggen
response->set_status( i_code = 500 i_reason = lx_error->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD process_cloud_event.
" CloudEvent parsen
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
).
" Event-Typ auswerten
CASE ls_event-type.
WHEN 'sap.inventory.stockupdated.v1'.
" Lagerbestand aktualisieren
process_stock_update( ls_event-data ).
WHEN 'sap.customer.updated.v1'.
" Kundenstamm synchronisieren
sync_customer_data( ls_event-data ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

EDA-Patterns

Pattern 1: Event Sourcing

Alle Zustandsänderungen als Events speichern:

" Jede Änderung wird als Event persistiert
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.
" Alle Events für dieses Aggregat chronologisch laden
SELECT * FROM zorder_events
WHERE aggregate_id = @iv_aggregate_id
ORDER BY sequence_nr
INTO TABLE @DATA(lt_events).
" State durch Replay aufbauen
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: Saga Pattern

Koordination verteilter Transaktionen über Events:

" Order Saga: Bestellung → Zahlung → Versand
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'.
" Schritt 1: Zahlung initiieren
initiate_payment( io_event->get_business_data( ) ).
WHEN 'PAYMENTCOMPLETED'.
" Schritt 2: Versand initiieren
initiate_shipment( io_event->get_business_data( ) ).
WHEN 'PAYMENTFAILED'.
" Kompensation: Bestellung stornieren
compensate_order( io_event->get_business_data( ) ).
WHEN 'SHIPMENTCOMPLETED'.
" Saga abgeschlossen
complete_saga( io_event->get_business_data( ) ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

Pattern 3: Event Aggregation

Mehrere Events zu einem zusammenfassen:

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.
" Revenue aus Event-Daten addieren
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.
" Prüfen ob Summary-Event fällig
check_and_publish_summary( ).
ENDMETHOD.
METHOD check_and_publish_summary.
" Z.B. alle 100 Events oder einmal pro Stunde
IF gv_order_count MOD 100 = 0.
" Summary-Event auslösen
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

Fehlerhafte Events separat speichern:

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. " Erfolgreich, nächstes Event
CATCH cx_root INTO DATA(lx_error).
lv_retry_count = lv_retry_count + 1.
IF lv_retry_count >= c_max_retries.
" Max Retries erreicht → 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.
" Warten vor 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'
).
" Alert auslösen
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 = |Event failed after { iv_retry_count } retries: { iv_error_msg }|
)
)->save( ).
ENDMETHOD.
ENDCLASS.

Idempotenz sicherstellen

Events können mehrfach zugestellt werden. Consumer müssen idempotent sein:

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).
" Eindeutige Event-ID (z.B. aus CloudEvents)
DATA(lv_event_id) = ls_event-%param-event_id.
" Prüfen ob bereits verarbeitet
IF is_event_processed( lv_event_id ).
" Skip - bereits verarbeitet
CONTINUE.
ENDIF.
" Event verarbeiten
TRY.
process_event( ls_event ).
" Als verarbeitet markieren
mark_event_processed( lv_event_id ).
CATCH cx_root INTO DATA(lx_error).
" Fehlerbehandlung
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 und Troubleshooting

Event Monitoring

" Monitoring-Abfrage für Event-Verarbeitung
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).

Event Tracing

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.

Best Practices

BereichEmpfehlung
Event-DesignVergangenheitsform verwenden (orderCreated, nicht createOrder)
PayloadNur notwendige Daten, keine vollständigen Entitäten
IdempotenzJedes Event muss sicher mehrfach verarbeitbar sein
FehlertoleranzConsumer-Fehler dürfen Producer nicht beeinflussen
VersionierungEvent-Schema niemals brechen, nur erweitern (v1, v2)
MonitoringAlle Events loggen, Metriken erfassen
Dead Letter QueueFehlgeschlagene Events separat speichern
TestingEvents und Handler getrennt testen
DokumentationEvent-Katalog mit Schema und Beschreibung pflegen
SecurityEvent Mesh mit OAuth 2.0 absichern

Weiterführende Themen