Langläufige Operationen wie Massenverarbeitung, PDF-Generierung oder externe API-Aufrufe blockieren synchrone RAP-Requests und verschlechtern die User Experience. Das Background Processing Framework (bgPF) ermöglicht asynchrone Verarbeitung direkt aus RAP heraus – ohne klassische Background Jobs programmieren zu müssen.
Das Problem: Synchrone Verarbeitung
Blockierende Operations
" ❌ Synchron: Blockiert den Request für Sekunden/MinutenMETHOD generateReport FOR MODIFY IMPORTING keys FOR ACTION Travel~generateReport RESULT result.
" PDF generieren - dauert 5-10 Sekunden DATA(lv_pdf) = generate_pdf_for_travel( keys[ 1 ]-%tky ).
" Email versenden - dauert 2-3 Sekunden send_email_with_attachment( recipient = get_customer_email( keys[ 1 ]-%tky ) attachment = lv_pdf ).
" External System benachrichtigen - dauert 1-2 Sekunden notify_external_system( keys[ 1 ]-%tky ).
" → Benutzer wartet 8-15 Sekunden!ENDMETHOD.Probleme bei synchroner Verarbeitung:
| Problem | Auswirkung |
|---|---|
| Lange Wartezeit | Benutzer blockiert, schlechte UX |
| Timeout-Risiko | HTTP-Timeout bei > 60 Sekunden |
| Keine Skalierung | Ein Request = ein Worker Process |
| Fehleranfälligkeit | Externe Systeme können fehlschlagen |
Die Lösung: Background Processing Framework (bgPF)
Das bgPF ist das Cloud-native Framework für asynchrone Verarbeitung in ABAP Cloud. Es ersetzt klassische Background Jobs und bietet:
- Deklarative Konfiguration über Behavior Definition
- Automatische Wiederholung bei temporären Fehlern
- Monitoring über SAP Fiori Apps
- Integration mit RAP Business Events
Asynchrone Action in RAP
Schritt 1: Behavior Definition
managed implementation in class zbp_i_travel unique;strict ( 2 );
define behavior for ZI_Travel alias Travelpersistent table ztravellock masterauthorization master ( instance )etag master LastChangedAt{ create; update; delete;
" Synchrone Action - für schnelle Operationen action approve result [1] $self;
" Asynchrone Action - für langläufige Operationen action ( execution : background ) generateReport;
" Asynchrone Action mit Result (Callback) action ( execution : background ) processExpenses;
" Asynchrone Factory Action factory action ( execution : background ) createMassBookings [0..*];}Die Annotation ( execution : background ) markiert die Action als asynchron. Sie wird nicht im aktuellen Request ausgeführt, sondern in einen Background Worker ausgelagert.
Schritt 2: Implementation
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS generateReport FOR MODIFY IMPORTING keys FOR ACTION Travel~generateReport.
METHODS processExpenses FOR MODIFY IMPORTING keys FOR ACTION Travel~processExpenses.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION. METHOD generateReport. " Diese Methode läuft im Background!
" 1. Travel-Daten laden READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_travels).
LOOP AT lt_travels ASSIGNING FIELD-SYMBOL(<travel>). " 2. PDF generieren (zeitintensiv) DATA(lv_pdf) = NEW zcl_travel_pdf_generator( )->generate( travel_id = <travel>-TravelId ).
" 3. PDF in Attachment Service speichern zcl_attachment_service=>store( object_type = 'TRAVEL' object_id = <travel>-TravelId content = lv_pdf filename = |travel_{ <travel>-TravelId }.pdf| ).
" 4. Status aktualisieren MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( ReportStatus ReportGeneratedAt ) WITH VALUE #( ( %tky = <travel>-%tky ReportStatus = 'G' " Generated ReportGeneratedAt = cl_abap_context_info=>get_system_date( ) ) ).
" 5. Event auslösen für weitere Verarbeitung RAISE ENTITY EVENT zi_travel~ReportGenerated FROM VALUE #( ( %key = <travel>-%key %param-PdfUrl = |/attachments/travel_{ <travel>-TravelId }.pdf| ) ). ENDLOOP. ENDMETHOD.
METHOD processExpenses. " Expenses verarbeiten und Summen berechnen READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_travels).
LOOP AT lt_travels ASSIGNING FIELD-SYMBOL(<travel>). " Externe API aufrufen für Wechselkurse DATA(lo_http) = cl_web_http_client_manager=>create_by_http_destination( cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_CURRENCY_API' service_id = 'Z_CURRENCY_SRV' ) ).
" Expenses aus Child Entity laden READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel BY \_Expenses ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(lt_expenses).
" Summe berechnen und konvertieren DATA(lv_total) = REDUCE abap_dec15_2( INIT sum = 0 FOR expense IN lt_expenses NEXT sum = sum + convert_currency( amount = expense-Amount from = expense-Currency to = <travel>-Currency ) ).
" Travel aktualisieren MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( TotalExpenses ExpensesProcessedAt ) WITH VALUE #( ( %tky = <travel>-%tky TotalExpenses = lv_total ExpensesProcessedAt = cl_abap_context_info=>get_system_date( ) ) ). ENDLOOP. ENDMETHOD.ENDCLASS.Schritt 3: UI-Integration
Die asynchrone Action wird in der UI wie eine normale Action angezeigt:
define root view entity ZC_Travel provider contract transactional_query as projection on ZI_Travel{ @UI.lineItem: [ { position: 10 }, { type: #FOR_ACTION, dataAction: 'approve', label: 'Genehmigen' }, { type: #FOR_ACTION, dataAction: 'generateReport', label: 'Report erstellen' } ] key TravelUUID,
@UI.fieldGroup: [{ qualifier: 'Status', position: 10 }] ReportStatus,
@UI.fieldGroup: [{ qualifier: 'Status', position: 20 }] ReportGeneratedAt}Verhalten der asynchronen Action
Request-Flow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Client │ │ RAP/OData │ │ bgPF │└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ POST /action │ │ │──────────────────>│ │ │ │ │ │ │ Schedule Task │ │ │──────────────────>│ │ │ │ │ HTTP 202 │ │ │<──────────────────│ │ │ (Accepted) │ │ │ │ │ │ │ ┌─────────┴─────────┐ │ │ │ Background Worker │ │ │ │ │ │ │ │ generateReport() │ │ │ │ ...läuft... │ │ │ │ ...fertig! │ │ │ └───────────────────┘Wichtige Punkte:
- HTTP 202 Accepted: Der Client erhält sofort eine Bestätigung, dass die Action angenommen wurde
- Keine Blockierung: Der Request ist in Millisekunden beendet
- Asynchrone Ausführung: Die eigentliche Arbeit passiert im Background Worker
- Automatic Retry: Bei temporären Fehlern wird automatisch wiederholt
Status-Feedback an den Benutzer
Da die Action asynchron läuft, muss der Status über andere Wege kommuniziert werden:
" Option 1: Status-Feld aktualisierenMODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( ReportStatus ) WITH VALUE #( ( %tky = <travel>-%tky ReportStatus = 'P' " Processing ) ).
" Nach AbschlussMODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( ReportStatus ReportGeneratedAt ) WITH VALUE #( ( %tky = <travel>-%tky ReportStatus = 'C' " Completed ReportGeneratedAt = cl_abap_context_info=>get_system_date( ) ) ).
" Option 2: Business Event auslösenRAISE ENTITY EVENT zi_travel~ReportGenerated FROM VALUE #( ( %key = <travel>-%key ) ).Integration mit RAP Business Events
Das bgPF arbeitet optimal mit Business Events zusammen. Events ermöglichen eine lose Kopplung zwischen der asynchronen Action und nachfolgenden Verarbeitungsschritten.
Event Definition in Behavior
define behavior for ZI_Travel alias Travel{ " Asynchrone Action action ( execution : background ) generateReport;
" Events für Statusänderungen event ReportGenerated parameter ZA_ReportEvent; event ReportFailed parameter ZA_ErrorEvent;}Event Payload
define abstract entity ZA_ReportEvent{ TravelId : abap.numc( 8 ); PdfUrl : abap.string( 255 ); Timestamp : timestampl;}
define abstract entity ZA_ErrorEvent{ TravelId : abap.numc( 8 ); ErrorCode : abap.char( 10 ); ErrorMessage : abap.string( 255 );}Event Consumer für Benachrichtigung
CLASS zcl_travel_event_handler DEFINITION PUBLIC CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_rap_event_handler.ENDCLASS.
CLASS zcl_travel_event_handler IMPLEMENTATION. METHOD if_rap_event_handler~handle_event. " Event-Typ prüfen CASE io_event->get_event_name( ). WHEN 'REPORTGENERATED'. " Payload lesen DATA(ls_payload) = io_event->get_payload( ). DATA(lv_travel_id) = ls_payload-TravelId. DATA(lv_pdf_url) = ls_payload-PdfUrl.
" Kunden per Email benachrichtigen DATA(lo_travel) = NEW zcl_travel_service( ). DATA(ls_travel) = lo_travel->get_by_id( lv_travel_id ).
NEW zcl_email_service( )->send( to = ls_travel-CustomerEmail subject = |Ihr Reisebericht ist verfügbar| body = |Der Report für Ihre Reise ist fertig: { lv_pdf_url }| ).
WHEN 'REPORTFAILED'. " Administrator benachrichtigen DATA(ls_error) = io_event->get_payload( ).
NEW zcl_notification_service( )->notify_admin( subject = |Report-Generierung fehlgeschlagen| message = |Travel { ls_error-TravelId }: { ls_error-ErrorMessage }| ). ENDCASE. ENDMETHOD.ENDCLASS.Event Handler Registrierung
Der Event Handler wird über die Service Binding registriert:
" In der Service Binding oder über die Event Handler RegistryCLASS zcl_event_handler_registry DEFINITION PUBLIC FINAL.
PUBLIC SECTION. CLASS-METHODS register_handlers.ENDCLASS.
CLASS zcl_event_handler_registry IMPLEMENTATION. METHOD register_handlers. " Registrierung erfolgt deklarativ über Annotation " @EventHandler für entsprechende Events ENDMETHOD.ENDCLASS.Fehlerbehandlung und Retry
Transiente vs. Permanente Fehler
METHOD generateReport. TRY. " Externe API aufrufen DATA(lo_http) = cl_web_http_client_manager=>create_by_http_destination( cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_PDF_SERVICE' ) ).
DATA(lo_request) = lo_http->get_http_request( ). lo_request->set_uri_path( '/generate' ).
DATA(lo_response) = lo_http->execute( if_web_http_client=>post ). DATA(lv_status) = lo_response->get_status( )-code.
CASE lv_status. WHEN 200 TO 299. " Erfolg - PDF verarbeiten process_pdf_response( lo_response ).
WHEN 429 OR 503. " Transient Error - Retry durch Framework " Werfe retryable Exception RAISE EXCEPTION TYPE cx_bgpf_retryable EXPORTING textid = cx_bgpf_retryable=>service_temporarily_unavailable.
WHEN OTHERS. " Permanenter Fehler - Kein Retry RAISE ENTITY EVENT zi_travel~ReportFailed FROM VALUE #( ( %key = keys[ 1 ]-%key %param-ErrorCode = |HTTP_{ lv_status }| %param-ErrorMessage = lo_response->get_text( ) ) ). ENDCASE.
CATCH cx_http_dest_provider_error cx_web_http_client_error INTO DATA(lx_http). " Netzwerkfehler - normalerweise transient RAISE EXCEPTION TYPE cx_bgpf_retryable EXPORTING previous = lx_http. ENDTRY.ENDMETHOD.Retry-Konfiguration
Das bgPF bietet automatische Retry-Mechanismen:
| Parameter | Default | Beschreibung |
|---|---|---|
| max_retries | 3 | Maximale Anzahl Wiederholungen |
| retry_delay | 60s | Wartezeit zwischen Versuchen |
| backoff_factor | 2 | Exponentieller Backoff-Multiplikator |
| max_delay | 3600s | Maximale Wartezeit |
" Retry-Verhalten kann über Annotations gesteuert werdendefine behavior for ZI_Travel alias Travel{ " Action mit spezifischer Retry-Konfiguration action ( execution : background, retries : 5, delay : 120 ) processLargeDataset;}Massenverarbeitung mit bgPF
Parallele Verarbeitung
METHOD processAllOpenTravels. " Alle offenen Reisen laden SELECT * FROM ztravel WHERE status = 'O' INTO TABLE @DATA(lt_travels).
" Parallel verarbeiten durch Batch-Scheduling DATA(lv_batch_size) = 100. DATA(lv_offset) = 0.
WHILE lv_offset < lines( lt_travels ). " Batch extrahieren DATA(lt_batch) = VALUE ty_travel_tab( FOR i = lv_offset WHILE i < lv_offset + lv_batch_size ( lt_travels[ i ] ) ).
" Background Task für jeden Batch cl_bgpf_process_api=>schedule( EXPORTING iv_process_name = 'Z_PROCESS_TRAVEL_BATCH' it_parameters = VALUE #( ( name = 'BATCH_DATA' value = cl_abap_conv_out_ce=>create( )->write( lt_batch ) ) ) ).
lv_offset = lv_offset + lv_batch_size. ENDWHILE.ENDMETHOD.Batch-Prozess Implementation
CLASS zcl_travel_batch_processor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_bgpf_process.ENDCLASS.
CLASS zcl_travel_batch_processor IMPLEMENTATION. METHOD if_bgpf_process~execute. " Parameter auslesen DATA(lv_batch_data) = it_parameters[ name = 'BATCH_DATA' ]-value. DATA lt_travels TYPE TABLE OF ztravel.
" Daten deserialisieren cl_abap_conv_in_ce=>create( input = lv_batch_data )->read( IMPORTING data = lt_travels ).
" Batch verarbeiten LOOP AT lt_travels ASSIGNING FIELD-SYMBOL(<travel>). TRY. process_single_travel( <travel> ). CATCH cx_root INTO DATA(lx_error). " Fehler loggen, aber fortfahren log_error( travel = <travel> error = lx_error ). ENDTRY. ENDLOOP.
" Ergebnis rv_result = if_bgpf_process=>c_result-success. ENDMETHOD.ENDCLASS.Monitoring und Überwachung
Application Logging
CLASS zcl_bgpf_logger DEFINITION. PUBLIC SECTION. METHODS log_start IMPORTING iv_action TYPE string iv_object_id TYPE string.
METHODS log_success IMPORTING iv_action TYPE string iv_object_id TYPE string iv_details TYPE string OPTIONAL.
METHODS log_error IMPORTING iv_action TYPE string iv_object_id TYPE string ix_error TYPE REF TO cx_root.
PRIVATE SECTION. DATA mo_log TYPE REF TO if_bali_log.ENDCLASS.
CLASS zcl_bgpf_logger IMPLEMENTATION. METHOD log_start. mo_log = cl_bali_log=>create( ). mo_log->set_header( header = cl_bali_header_setter=>create( object = 'Z_BGPF' subobject = iv_action external_id = iv_object_id ) ).
mo_log->add_item( item = cl_bali_free_text_setter=>create( severity = if_bali_constants=>c_severity_information text = |Background Task gestartet: { iv_action }| ) ).
cl_bali_log_db=>get_instance( )->save_log( log = mo_log ). ENDMETHOD.
METHOD log_success. mo_log->add_item( item = cl_bali_free_text_setter=>create( severity = if_bali_constants=>c_severity_status text = |Erfolgreich abgeschlossen: { iv_details }| ) ).
cl_bali_log_db=>get_instance( )->save_log( log = mo_log ). ENDMETHOD.
METHOD log_error. mo_log->add_item( item = cl_bali_exception_setter=>create( severity = if_bali_constants=>c_severity_error exception = ix_error ) ).
cl_bali_log_db=>get_instance( )->save_log( log = mo_log ). ENDMETHOD.ENDCLASS.Monitoring-Fiori-App
Die Standard-Fiori-App Manage Background Tasks (App ID: F3840) zeigt:
- Laufende Background Tasks
- Fehlgeschlagene Tasks mit Details
- Retry-Historie
- Performance-Metriken
Programmatische Status-Abfrage
" Task-Status abfragenDATA(lo_monitor) = cl_bgpf_process_api=>get_monitor( ).
DATA(lt_tasks) = lo_monitor->get_tasks( iv_process_name = 'Z_GENERATE_REPORT' iv_from_date = sy-datum - 7 iv_to_date = sy-datum).
LOOP AT lt_tasks INTO DATA(ls_task). WRITE: / ls_task-task_id, ls_task-status, ls_task-started_at, ls_task-completed_at.ENDLOOP.
" Fehlgeschlagene TasksDATA(lt_failed) = lo_monitor->get_failed_tasks( iv_process_name = 'Z_GENERATE_REPORT').
LOOP AT lt_failed INTO DATA(ls_failed). WRITE: / |Task { ls_failed-task_id } fehlgeschlagen: { ls_failed-error_message }|.ENDLOOP.Best Practices
1. Idempotenz sicherstellen
Background Tasks können mehrfach ausgeführt werden (Retry). Die Logik muss idempotent sein:
" ✅ Idempotent: Prüft ob schon verarbeitetMETHOD generateReport. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( ReportStatus ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travels).
LOOP AT lt_travels ASSIGNING FIELD-SYMBOL(<travel>). " Bereits generiert? Skip! IF <travel>-ReportStatus = 'C'. CONTINUE. ENDIF.
" Report generieren... ENDLOOP.ENDMETHOD.2. Timeout-Bewusstsein
" Große Datenmengen in Chunks verarbeitenMETHOD processMassData. DATA(lv_chunk_size) = 1000. DATA(lv_processed) = 0.
SELECT * FROM zlarge_table INTO TABLE @DATA(lt_chunk) UP TO @lv_chunk_size ROWS OFFSET @lv_processed.
WHILE lt_chunk IS NOT INITIAL. process_chunk( lt_chunk ). lv_processed = lv_processed + lines( lt_chunk ).
" Checkpoint für lange Operationen IF lv_processed MOD 10000 = 0. COMMIT WORK. ENDIF.
SELECT * FROM zlarge_table INTO TABLE @lt_chunk UP TO @lv_chunk_size ROWS OFFSET @lv_processed. ENDWHILE.ENDMETHOD.3. Fehler kommunizieren
" Bei Fehler: Status und Event setzenTRY. " Verarbeitung... CATCH cx_root INTO DATA(lx_error). " Status auf Error setzen MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( ProcessingStatus ErrorMessage ) WITH VALUE #( ( %tky = keys[ 1 ]-%tky ProcessingStatus = 'E' " Error ErrorMessage = lx_error->get_text( ) ) ).
" Error-Event für Monitoring RAISE ENTITY EVENT zi_travel~ProcessingFailed FROM VALUE #( ( %key = keys[ 1 ]-%key %param-ErrorMessage = lx_error->get_text( ) ) ).ENDTRY.4. Transaktionsgrenzen beachten
" ❌ Falsch: COMMIT in RAP-HandlerMETHOD processData. COMMIT WORK. " Nicht erlaubt!ENDMETHOD.
" ✅ Richtig: Transaktionssteuerung dem Framework überlassen" bgPF macht automatisch COMMIT nach erfolgreicher AusführungbgPF vs. Klassische Background Jobs
| Aspekt | bgPF | JOB_OPEN/SUBMIT |
|---|---|---|
| Deklaration | Behavior Definition | ABAP Code |
| Integration | Native RAP-Integration | Separate Programmierung |
| Retry | Automatisch | Manuell implementieren |
| Monitoring | Standard Fiori App | SM37 |
| Cloud-ready | Ja | Nur On-Premise |
| Event-Integration | Native Unterstützung | Manuell |
| Transaktionskontext | RAP-managed | Selbst verwalten |
Weiterführende Themen
- RAP Actions und Functions - Synchrone Geschäftslogik
- RAP Business Events - Event-driven Architecture
- Background Jobs - Klassische Hintergrundverarbeitung
- Application Logging - Logs in ABAP Cloud