Business Events in RAP enable event-driven architecture: Instead of direct method calls, components communicate via events. This decouples systems, enables asynchronous processing, and makes architectures more scalable and maintainable.
The Problem: Tight Coupling
Without Events (Tight Coupling)
" Bad: Direct dependency: Travel -> Email -> LoggingMETHOD approve_travel. " 1. Change status MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ) WITH VALUE #( ( %tky = ls_travel-%tky Status = 'A' ) ).
" 2. Send email (direct dependency!) TRY. cl_email_sender=>send( recipient = ls_travel-customer_email subject = 'Travel approved' ). CATCH cx_email_error. " What to do on error? ENDTRY.
" 3. Log (direct dependency!) cl_logger=>log( |Travel { ls_travel-TravelId } approved| ).
" 4. Update analytics (direct dependency!) zcl_analytics=>track_approval( ls_travel-TravelId ).
" 5. Notify external system (direct dependency!) zcl_external_api=>notify_approval( ls_travel ).ENDMETHOD.Problems:
- Tight Coupling: Approval logic knows all consumers
- Synchronous: All steps block the main process
- Not extensible: New consumer = change code
- Error-prone: One failed consumer breaks everything
- Slow: Email + API + Logging = 2-3 seconds
With Events (Loose Coupling)
" Good: Event-driven: Travel -> Event -> Consumers (decoupled)METHOD approve_travel. " 1. Change status MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ) WITH VALUE #( ( %tky = ls_travel-%tky Status = 'A' ) ).
" 2. Raise event (Fire & Forget) RAISE ENTITY EVENT zi_travel~TravelApproved FROM VALUE #( ( %key-TravelId = ls_travel-TravelId %param-ApprovedBy = sy-uname %param-ApprovedAt = sy-datum ) ).
" Done! Consumers react asynchronouslyENDMETHOD.Benefits:
- Loose Coupling: Approval logic knows no consumers
- Asynchronous: Consumers process in parallel
- Extensible: New consumers without code change
- Resilient: Consumer errors don’t affect main process
- Fast: Main process continues immediately (< 100ms)
Event Anatomy
+-----------------------------------------------------+| Event Publisher || (RAP Business Object) || || METHOD approve_travel. || RAISE ENTITY EVENT zi_travel~TravelApproved ... || ENDMETHOD. |+----------------+------------------------------------+ | | Event: TravelApproved | Payload: { TravelId, ApprovedBy, ApprovedAt } | v+-----------------------------------------------------+| Event Infrastructure || (RAP Framework / Event Mesh) |+--------+------------------------+-------------------+ | | v v+--------------------+ +------------------------+| Consumer 1 | | Consumer 2 || (Email) | | (Analytics) || | | || on_event( ). | | on_event( ). || send_email( ) | | track_approval( ) |+--------------------+ +------------------------+ | v+--------------------+| Consumer 3 || (External API) || || on_event( ). || notify_system( ) |+--------------------+Event Definition in BDEF
Simple Event
define behavior for ZI_Travel alias Travelpersistent table ztravellock masterauthorization master ( instance ){ create; update; delete;
// Event Definition event TravelApproved;
// Action that raises event action approve result [1] $self;}Event with Parameters
define behavior for ZI_Travel alias Travel{ // Event with parameter structure event TravelApproved parameter ZA_TravelApprovedParam;
action approve result [1] $self;}Parameter Structure (Abstract Entity):
@EndUserText.label: 'Travel Approved Event Parameters'define abstract entity ZA_TravelApprovedParam{ TravelId : /dmo/travel_id; ApprovedBy : syuname; ApprovedAt : sydatum; ApprovalNote : abap.string(255);}Event with Standard Parameter
define behavior for ZI_Travel alias Travel{ // Event with predefined %param event TravelApproved parameter ZA_TravelApprovedParam;
// Or: Only key fields (no explicit parameter) event TravelCancelled;}Event Raising (Publisher)
Raise Event in Action
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS approve FOR MODIFY IMPORTING keys FOR ACTION Travel~approve RESULT result.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD approve. " 1. Read data READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel) FAILED failed REPORTED reported.
" 2. Change status MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ApprovedBy ApprovedAt ) WITH VALUE #( FOR travel IN lt_travel ( %tky = travel-%tky Status = 'A' ApprovedBy = cl_abap_context_info=>get_user_name( ) ApprovedAt = cl_abap_context_info=>get_system_date( ) ) ) FAILED failed REPORTED reported.
" 3. Raise event RAISE ENTITY EVENT zi_travel~TravelApproved FROM VALUE #( FOR travel IN lt_travel ( %key-TravelId = travel-TravelId %param-ApprovedBy = cl_abap_context_info=>get_user_name( ) %param-ApprovedAt = cl_abap_context_info=>get_system_date( ) %param-ApprovalNote = 'Approved via action' ) ).
" 4. Return result READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT result. ENDMETHOD.
ENDCLASS.Event in Validation/Determination
METHOD validateDates. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel). IF ls_travel-EndDate < ls_travel-BeginDate. " Validation error APPEND VALUE #( %tky = ls_travel-%tky ) TO failed-travel. APPEND VALUE #( %tky = ls_travel-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Invalid date range' ) ) TO reported-travel.
" Event: ValidationFailed raised RAISE ENTITY EVENT zi_travel~ValidationFailed FROM VALUE #( ( %key-TravelId = ls_travel-TravelId %param-ErrorType = 'DATE_RANGE' %param-ErrorMessage = 'End date before begin date' ) ). ENDIF. ENDLOOP.ENDMETHOD.Multiple Events
define behavior for ZI_Travel alias Travel{ // Different events for different scenarios event TravelCreated; event TravelApproved; event TravelRejected parameter ZA_RejectionParam; event TravelCancelled; event TravelCompleted;}METHOD approve. " ... RAISE ENTITY EVENT zi_travel~TravelApproved FROM ...ENDMETHOD.
METHOD reject. " ... RAISE ENTITY EVENT zi_travel~TravelRejected FROM VALUE #( ( %key-TravelId = ls_travel-TravelId %param-RejectionReason = ls_key-%param-Reason ) ).ENDMETHOD.
METHOD cancel. " ... RAISE ENTITY EVENT zi_travel~TravelCancelled FROM ...ENDMETHOD.Event Consumption (Subscriber)
Implement Event Handler
CLASS zcl_travel_event_handler DEFINITION PUBLIC CREATE PUBLIC. PUBLIC SECTION. INTERFACES if_rap_entity_event_subscriber.ENDCLASS.
CLASS zcl_travel_event_handler IMPLEMENTATION.
METHOD if_rap_entity_event_subscriber~on_business_event. " Evaluate event key CASE event_key.
WHEN 'TravelApproved'. handle_travel_approved( event_data ).
WHEN 'TravelRejected'. handle_travel_rejected( event_data ).
WHEN 'TravelCancelled'. handle_travel_cancelled( event_data ).
ENDCASE. ENDMETHOD.
" Private methods for event handling METHODS: handle_travel_approved IMPORTING it_event_data TYPE STANDARD TABLE,
handle_travel_rejected IMPORTING it_event_data TYPE STANDARD TABLE,
handle_travel_cancelled IMPORTING it_event_data TYPE STANDARD TABLE.
ENDCLASS.Register Event Handler
Service Definition:
@EndUserText.label: 'Travel Event Handler'define service ZUI_TRAVEL_EVENTS { expose ZI_Travel as Travel;
// Activate event handler expose zcl_travel_event_handler as TravelEventHandler;}Or: Programmatic Registration:
" In initialization class or at system startupDATA(lo_event_handler) = NEW zcl_travel_event_handler( ).
" Register handlercl_rap_event_handler=>register( iv_event_name = 'TravelApproved' io_handler = lo_event_handler).Process Event Data
METHOD handle_travel_approved. " Event data is a table (can be multiple events) LOOP AT it_event_data INTO DATA(ls_event).
" Extract %key and %param DATA(lv_travel_id) = ls_event-%key-TravelId. DATA(lv_approved_by) = ls_event-%param-ApprovedBy. DATA(lv_approved_at) = ls_event-%param-ApprovedAt.
" 1. Send email send_approval_email( iv_travel_id = lv_travel_id iv_approved_by = lv_approved_by ).
" 2. Track analytics track_approval_analytics( iv_travel_id = lv_travel_id iv_approved_at = lv_approved_at ).
" 3. Notify external API notify_external_system( iv_travel_id = lv_travel_id ).
" 4. Log cl_bali_log=>create( )->add_item( cl_bali_free_text_setter=>create( severity = if_bali_constants=>c_severity_information text = |Travel { lv_travel_id } approved by { lv_approved_by }| ) )->save( ).
ENDLOOP.ENDMETHOD.Error Handling in Consumer
METHOD handle_travel_approved. LOOP AT it_event_data INTO DATA(ls_event).
" Email sending with error handling TRY. send_approval_email( ls_event-%key-TravelId ). CATCH cx_email_error INTO DATA(lx_email). " Log error, but DON'T abort event processing cl_bali_log=>create( )->add_item( cl_bali_message_setter=>create_from_exception( lx_email ) )->save( ).
" Optional: Retry logic add_to_retry_queue( iv_event_type = 'TravelApproved' iv_travel_id = ls_event-%key-TravelId ). ENDTRY.
" API call with error handling TRY. notify_external_system( ls_event-%key-TravelId ). CATCH cx_http_error INTO DATA(lx_http). " Log error cl_bali_log=>create( )->add_item( cl_bali_message_setter=>create_from_exception( lx_http ) )->save( ). ENDTRY.
ENDLOOP.ENDMETHOD.Practical Use Cases
Use Case 1: Multi-Channel Notification
" Event: TravelApproved" -> Consumer 1: Email" -> Consumer 2: SMS" -> Consumer 3: Push Notification" -> Consumer 4: Slack
CLASS zcl_notification_handler DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES if_rap_entity_event_subscriber.ENDCLASS.
CLASS zcl_notification_handler IMPLEMENTATION.
METHOD if_rap_entity_event_subscriber~on_business_event. CHECK event_key = 'TravelApproved'.
LOOP AT event_data INTO DATA(ls_event). DATA(lv_travel_id) = ls_event-%key-TravelId.
" Load customer preferences SELECT SINGLE * FROM zcustomer_prefs WHERE travel_id = @lv_travel_id INTO @DATA(ls_prefs).
" Multi-channel notification (parallel) IF ls_prefs-notify_email = abap_true. send_email( lv_travel_id ). ENDIF.
IF ls_prefs-notify_sms = abap_true. send_sms( lv_travel_id ). ENDIF.
IF ls_prefs-notify_push = abap_true. send_push_notification( lv_travel_id ). ENDIF.
IF ls_prefs-notify_slack = abap_true. send_slack_message( lv_travel_id ). ENDIF. ENDLOOP. ENDMETHOD.
ENDCLASS.Use Case 2: Audit Trail
CLASS zcl_audit_trail_handler DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES if_rap_entity_event_subscriber.ENDCLASS.
CLASS zcl_audit_trail_handler IMPLEMENTATION.
METHOD if_rap_entity_event_subscriber~on_business_event. " Audit all events LOOP AT event_data INTO DATA(ls_event).
" Create audit entry INSERT INTO zaudit_log VALUES ( audit_id = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( ) entity_type = 'TRAVEL' entity_key = ls_event-%key-TravelId event_type = event_key event_date = sy-datum event_time = sy-uzeit user_name = sy-uname event_payload = /ui2/cl_json=>serialize( ls_event ) ).
ENDLOOP.
COMMIT WORK. ENDMETHOD.
ENDCLASS.Use Case 3: Workflow Trigger
CLASS zcl_workflow_trigger_handler DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES if_rap_entity_event_subscriber.ENDCLASS.
CLASS zcl_workflow_trigger_handler IMPLEMENTATION.
METHOD if_rap_entity_event_subscriber~on_business_event. CHECK event_key = 'TravelApproved'.
LOOP AT event_data INTO DATA(ls_event). DATA(lv_travel_id) = ls_event-%key-TravelId.
" Load travel details SELECT SINGLE * FROM zi_travel WHERE TravelId = @lv_travel_id INTO @DATA(ls_travel).
" Trigger workflow for expensive travels IF ls_travel-TotalAmount > 10000.
" Start SAP Build Process Automation workflow DATA(lo_workflow) = cl_spa_workflow=>get_instance( ).
lo_workflow->start_workflow( workflow_id = 'HighValueTravelApproval' context = VALUE #( travel_id = lv_travel_id amount = ls_travel-TotalAmount approved_by = ls_event-%param-ApprovedBy ) ).
ENDIF. ENDLOOP. ENDMETHOD.
ENDCLASS.Use Case 4: Cache Invalidation
CLASS zcl_cache_handler DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES if_rap_entity_event_subscriber.ENDCLASS.
CLASS zcl_cache_handler IMPLEMENTATION.
METHOD if_rap_entity_event_subscriber~on_business_event. " Invalidate cache on all travel events CASE event_key. WHEN 'TravelApproved' OR 'TravelRejected' OR 'TravelCancelled'.
LOOP AT event_data INTO DATA(ls_event). " Delete cache for this travel cl_cache_manager=>invalidate( cache_area = 'TRAVEL' key = ls_event-%key-TravelId ).
" Optional: Also delete related caches " (e.g., Customer-Travel-List) SELECT SINGLE CustomerId FROM zi_travel WHERE TravelId = @ls_event-%key-TravelId INTO @DATA(lv_customer_id).
cl_cache_manager=>invalidate( cache_area = 'CUSTOMER_TRAVELS' key = lv_customer_id ). ENDLOOP.
ENDCASE. ENDMETHOD.
ENDCLASS.Integration with SAP Event Mesh
Event Mesh Setup
What is SAP Event Mesh?
- Cloud service for Enterprise Event Bus
- Decouples publishers and consumers via message queue
- Supports Pub/Sub pattern
- Multi-tenant, scalable, highly available
Publish Event to Event Mesh
CLASS zcl_event_mesh_publisher DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES if_rap_entity_event_subscriber.ENDCLASS.
CLASS zcl_event_mesh_publisher IMPLEMENTATION.
METHOD if_rap_entity_event_subscriber~on_business_event. " Forward RAP event to Event Mesh
LOOP AT event_data INTO DATA(ls_event).
" Serialize event as JSON DATA(lv_json_payload) = /ui2/cl_json=>serialize( data = ls_event compress = abap_false ).
" HTTP Client for Event Mesh DATA(lo_http) = cl_web_http_client_manager=>create_by_http_destination( i_destination = cl_http_destination_provider=>create_by_cloud_destination( i_name = 'EVENT_MESH' i_authn_mode = if_a4c_cp_service=>service_specific ) ).
" POST Request DATA(lo_request) = lo_http->get_http_request( ).
lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ).
lo_request->set_header_field( i_name = 'x-qos' " Quality of Service i_value = '1' " At least once ).
lo_request->set_text( lv_json_payload ).
" Event Topic DATA(lv_topic) = |sap/s4/travel/{ event_key }|.
TRY. DATA(lo_response) = lo_http->execute( i_method = if_web_http_client=>post i_uri = |/messagingrest/v1/topics/{ lv_topic }/messages| ).
IF lo_response->get_status( )-code = 204. " Successfully published cl_bali_log=>create( )->add_item( cl_bali_free_text_setter=>create( severity = if_bali_constants=>c_severity_information text = |Event { event_key } published to Event Mesh| ) )->save( ). ENDIF.
CATCH cx_web_http_client_error INTO DATA(lx_http). " Log error cl_bali_log=>create( )->add_item( cl_bali_message_setter=>create_from_exception( lx_http ) )->save( ). ENDTRY.
ENDLOOP. ENDMETHOD.
ENDCLASS.Consume Event from Event Mesh
" Webhook endpoint for Event Mesh callbacksCLASS zcl_event_mesh_consumer DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES if_http_service_extension.ENDCLASS.
CLASS zcl_event_mesh_consumer IMPLEMENTATION.
METHOD if_http_service_extension~handle_request. " Event Mesh sends events via HTTP POST
" Extract JSON payload DATA(lv_payload) = request->get_text( ).
" Deserialize DATA ls_event TYPE zi_travel_event. /ui2/cl_json=>deserialize( EXPORTING json = lv_payload CHANGING data = ls_event ).
" Process event CASE ls_event-event_type. WHEN 'TravelApproved'. " Local processing process_travel_approved( ls_event ).
WHEN 'TravelRejected'. process_travel_rejected( ls_event ). ENDCASE.
" Response response->set_status( i_code = 200 i_reason = 'OK' ).
ENDMETHOD.
ENDCLASS.Testing Events
Test Event Raising
CLASS ltc_travel_events DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_environment TYPE REF TO if_cds_test_environment.
METHODS: setup, teardown, test_approve_raises_event FOR TESTING.ENDCLASS.
CLASS ltc_travel_events IMPLEMENTATION.
METHOD setup. mo_environment = cl_cds_test_environment=>create( i_for_entity = 'ZI_Travel' ).
" Test data mo_environment->insert_test_data( i_data = VALUE zi_travel( ( TravelId = '00000001' Status = 'O' CustomerId = '000042' ) ) ). ENDMETHOD.
METHOD test_approve_raises_event. " Arrange: Register event spy DATA(lo_event_spy) = NEW lcl_event_spy( ). " (In real tests: Framework-specific spy)
" Act: Execute action (should raise event) MODIFY ENTITIES OF zi_travel ENTITY Travel EXECUTE approve FROM VALUE #( ( TravelId = '00000001' ) ) FAILED DATA(failed).
COMMIT ENTITIES.
" Assert: Event was raised " (Framework-dependent - here conceptual) cl_abap_unit_assert=>assert_equals( exp = 1 act = lo_event_spy->get_event_count( 'TravelApproved' ) msg = 'Event TravelApproved should be raised' ).
" Assert: Event parameters correct DATA(ls_event) = lo_event_spy->get_event( 'TravelApproved' ). cl_abap_unit_assert=>assert_equals( exp = '00000001' act = ls_event-%key-TravelId ). ENDMETHOD.
METHOD teardown. ROLLBACK ENTITIES. mo_environment->destroy( ). ENDMETHOD.
ENDCLASS.Test Event Handler
CLASS ltc_event_handler DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_cut TYPE REF TO zcl_travel_event_handler.
METHODS: setup, test_handle_approved_event FOR TESTING.ENDCLASS.
CLASS ltc_event_handler IMPLEMENTATION.
METHOD setup. mo_cut = NEW zcl_travel_event_handler( ). ENDMETHOD.
METHOD test_handle_approved_event. " Arrange: Mock event data DATA(lt_event_data) = VALUE zi_travel_event_tab( ( %key-TravelId = '00000001' %param-ApprovedBy = 'TEST_USER' %param-ApprovedAt = '20250101' ) ).
" Act mo_cut->if_rap_entity_event_subscriber~on_business_event( event_key = 'TravelApproved' event_data = lt_event_data ).
" Assert: Verify handler processed correctly " (e.g., email sent, log entry created) " -> Depends on implementation ENDMETHOD.
ENDCLASS.Event Patterns
Pattern 1: Event Chaining
" Event 1 triggers Event 2 triggers Event 3
" Publisher 1METHOD create_travel. " ... RAISE ENTITY EVENT zi_travel~TravelCreated FROM ...ENDMETHOD.
" Consumer 1 = Publisher 2CLASS zcl_handler_1 IMPLEMENTATION. METHOD on_business_event. CHECK event_key = 'TravelCreated'.
" Perform validation validate_travel( event_data ).
" Next event RAISE ENTITY EVENT zi_travel~TravelValidated FROM ... ENDMETHOD.ENDCLASS.
" Consumer 2 = Publisher 3CLASS zcl_handler_2 IMPLEMENTATION. METHOD on_business_event. CHECK event_key = 'TravelValidated'.
" Start approval process start_approval( event_data ).
" Next event RAISE ENTITY EVENT zi_travel~ApprovalStarted FROM ... ENDMETHOD.ENDCLASS.Pattern 2: Event Aggregation
" Aggregate multiple events to a summary event
CLASS zcl_event_aggregator IMPLEMENTATION. METHOD on_business_event. " Collect events CASE event_key. WHEN 'TravelApproved' OR 'TravelRejected' OR 'TravelCancelled'. " In-memory counter add_to_statistics( event_key ). ENDCASE.
" Every hour: Summary event IF hour_passed( ). RAISE EVENT DailySummary FROM VALUE #( ( approvals = mv_approval_count rejections = mv_rejection_count cancellations = mv_cancellation_count ) ).
reset_statistics( ). ENDIF. ENDMETHOD.ENDCLASS.Pattern 3: Event Filtering
" Only process certain events
CLASS zcl_high_value_handler IMPLEMENTATION. METHOD on_business_event. CHECK event_key = 'TravelCreated'.
LOOP AT event_data INTO DATA(ls_event). " Load travel details SELECT SINGLE TotalAmount FROM zi_travel WHERE TravelId = @ls_event-%key-TravelId INTO @DATA(lv_amount).
" Only high-value travels (> 10,000) CHECK lv_amount > 10000.
" Special handling notify_manager( ls_event-%key-TravelId ). require_additional_approval( ls_event-%key-TravelId ). ENDLOOP. ENDMETHOD.ENDCLASS.Important Notes / Best Practice
- Loose Coupling: Events decouple publishers and consumers
- Asynchronous: Events enable asynchronous processing
- Fire & Forget: Publisher doesn’t care about consumer success
- Idempotency: Event handlers should be idempotent (multiple processing = OK)
- Error Handling: Consumer errors must not affect publisher
- Event Naming: Past tense (
TravelApproved, notApproveTravel) - Event Payload: Only necessary data (no large objects)
- Versioning: Never break event structure (only extend)
- Testing: Explicitly test events (spy pattern)
- Monitoring: Log and monitor event processing
- Retry Logic: Queue failed events for retry
- Event Mesh: Use for cross-system events
- Documentation: Document events and their consumers
Further Resources
- RAP Basics: /en/rap-basics/
- EML Guide: /en/eml-entity-manipulation-language/
- RAP Determinations/Validations: /en/rap-determinations-validations/
- ABAP Cloud: /en/abap-cloud-definition/