In Classic ABAP sind Reports das Fundament der Anwendungsentwicklung. REPORT, PARAMETERS, SELECT-OPTIONS und WRITE - damit wurde jahrzehntelang entwickelt. In ABAP Cloud sieht die Welt anders aus: Keine klassischen Reports, keine Dynpros, keine Selection Screens. Stattdessen: Executable Classes, Application Jobs und Fiori Apps.
Das Problem: Reports in ABAP Cloud?
Wer in ABAP Cloud einen klassischen Report anlegen möchte, wird enttäuscht:
" ❌ In ABAP Cloud nicht möglich:REPORT zflight_report.PARAMETERS: p_carr TYPE s_carr_id.START-OF-SELECTION. SELECT * FROM sflight INTO TABLE @DATA(lt_flights) WHERE carrid = @p_carr. LOOP AT lt_flights INTO DATA(ls_flight). WRITE: / ls_flight-carrid, ls_flight-connid. ENDLOOP.Dieser Code wird vom Compiler abgelehnt - REPORT, PARAMETERS und WRITE sind keine gültigen ABAP Cloud Statements.
Die Alternativen im Überblick
ABAP Cloud bietet drei Hauptansätze für Programmausführung:
┌─────────────────────────────────────────────────────────────────┐│ Programme in ABAP Cloud │├─────────────────────────────────────────────────────────────────┤│ ││ 1. Executable Class (if_oo_adt_classrun) ││ → Schnelle Tests, Entwicklung, Debug ││ → Ausführung direkt in ADT ││ ││ 2. Application Job ││ → Geplante Hintergrundverarbeitung ││ → Parameter über Job-Katalog ││ → Monitoring in Fiori ││ ││ 3. Fiori App (RAP-basiert) ││ → Interaktive Benutzeroberfläche ││ → Filter und Actions ││ → Vollständige UX ││ │└─────────────────────────────────────────────────────────────────┘Executable Classes mit IF_OO_ADT_CLASSRUN
Das Interface IF_OO_ADT_CLASSRUN ermöglicht die direkte Ausführung einer Klasse in den ABAP Development Tools (ADT). Es ist der schnellste Weg, Code auszuführen und zu testen.
Grundstruktur
CLASS zcl_flight_report DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_flight_report IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " Hier kommt die Programmlogik out->write( 'Flugbuchungs-Report' ). out->write( '===================' ).
" Daten laden SELECT FROM zflight_book FIELDS booking_id, flight_id, customer_id, booking_date, price INTO TABLE @DATA(lt_bookings).
" Ausgabe out->write( lt_bookings ). ENDMETHOD.ENDCLASS.Ausführung in ADT
Rechtsklick auf Klasse → Run As → ABAP Application (Console)→ Ausgabe erscheint im Console-ViewFlugbuchungs-Report: Vollständiges Beispiel
CLASS zcl_booking_analysis DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_oo_adt_classrun.
PRIVATE SECTION. TYPES: BEGIN OF ty_summary, carrier_id TYPE s_carr_id, total_bookings TYPE i, total_revenue TYPE p LENGTH 15 DECIMALS 2, avg_price TYPE p LENGTH 10 DECIMALS 2, END OF ty_summary, tt_summary TYPE STANDARD TABLE OF ty_summary WITH EMPTY KEY.
METHODS analyze_bookings RETURNING VALUE(rt_summary) TYPE tt_summary.
METHODS get_top_routes RETURNING VALUE(rt_routes) TYPE string_table.
ENDCLASS.
CLASS zcl_booking_analysis IMPLEMENTATION. METHOD if_oo_adt_classrun~main. out->write( |Flugbuchungs-Analyse - { cl_abap_context_info=>get_system_date( ) }| ). out->write( |Erstellt von: { cl_abap_context_info=>get_user_technical_name( ) }| ). out->write( '=' && repeat( val = '=' occ = 50 ) ). out->write( `` ).
" Buchungsübersicht nach Carrier out->write( '1. Buchungen nach Fluggesellschaft:' ). out->write( '-' && repeat( val = '-' occ = 40 ) ).
DATA(lt_summary) = analyze_bookings( ). LOOP AT lt_summary INTO DATA(ls_summary). out->write( | { ls_summary-carrier_id }: { ls_summary-total_bookings } Buchungen, | && |Umsatz: { ls_summary-total_revenue } EUR, | && |Durchschnitt: { ls_summary-avg_price } EUR| ). ENDLOOP. out->write( `` ).
" Top-Routen out->write( '2. Beliebteste Routen:' ). out->write( '-' && repeat( val = '-' occ = 40 ) ). DATA(lt_routes) = get_top_routes( ). LOOP AT lt_routes INTO DATA(lv_route). out->write( | { sy-tabix }. { lv_route }| ). ENDLOOP.
out->write( `` ). out->write( 'Analyse abgeschlossen.' ). ENDMETHOD.
METHOD analyze_bookings. SELECT FROM zflight_book AS b INNER JOIN zflight AS f ON b~flight_id = f~flight_id FIELDS f~carrier_id, COUNT( * ) AS total_bookings, SUM( b~price ) AS total_revenue, AVG( b~price ) AS avg_price GROUP BY f~carrier_id ORDER BY total_bookings DESCENDING INTO TABLE @rt_summary. ENDMETHOD.
METHOD get_top_routes. SELECT FROM zflight_book AS b INNER JOIN zflight AS f ON b~flight_id = f~flight_id FIELDS f~carrier_id, f~connection_id, f~departure_city, f~arrival_city, COUNT( * ) AS booking_count GROUP BY f~carrier_id, f~connection_id, f~departure_city, f~arrival_city ORDER BY booking_count DESCENDING UP TO 5 ROWS INTO TABLE @DATA(lt_routes).
LOOP AT lt_routes INTO DATA(ls_route). APPEND |{ ls_route-departure_city } → { ls_route-arrival_city } | && |({ ls_route-carrier_id }/{ ls_route-connection_id }): | && |{ ls_route-booking_count } Buchungen| TO rt_routes. ENDLOOP. ENDMETHOD.ENDCLASS.Vergleich: REPORT vs. Executable Class
| Aspekt | Classic REPORT | Executable Class |
|---|---|---|
| Anlage | REPORT zreport. | Klasse mit Interface |
| Parameter | PARAMETERS, SELECT-OPTIONS | Keine (hardcoded oder Tabelle) |
| Ausgabe | WRITE, ALV, Dynpro | out->write( ) |
| Ausführung | SE38, SA38, Transaktion | ADT Console |
| Scheduling | SM36, Background Job | Application Job |
| Testing | Manuell | Unit Tests möglich |
Application Jobs: Hintergrundverarbeitung
Für geplante Ausführungen und Hintergrundverarbeitung bietet ABAP Cloud Application Jobs. Diese ersetzen die klassischen Background Jobs aus SM36/SM37.
Komponenten eines Application Jobs
┌─────────────────────────────────────────────────────────────────┐│ Application Job │├─────────────────────────────────────────────────────────────────┤│ ││ 1. Job Catalog Entry ││ → Registriert den Job im System ││ → Definiert Parameter-Interface ││ ││ 2. Job Template (optional) ││ → Vordefinierte Parameterwerte ││ → Wiederverwendbar ││ ││ 3. Job Class ││ → Implementiert IF_APJ_DT_EXEC_OBJECT ││ → Enthält Geschäftslogik ││ ││ 4. Scheduling ││ → Fiori App "Schedule Application Jobs" ││ → Einmalig, wiederkehrend, event-basiert ││ │└─────────────────────────────────────────────────────────────────┘Job-Klasse implementieren
CLASS zcl_booking_cleanup_job DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_apj_dt_exec_object.
PRIVATE SECTION. DATA mv_days_to_keep TYPE i. DATA mv_dry_run TYPE abap_bool.
METHODS delete_old_bookings RETURNING VALUE(rv_deleted) TYPE i.
METHODS log_result IMPORTING iv_deleted TYPE i.
ENDCLASS.
CLASS zcl_booking_cleanup_job IMPLEMENTATION.
METHOD if_apj_dt_exec_object~get_parameters. " Parameter für den Job-Katalog definieren et_parameter_def = VALUE #( ( selname = 'P_DAYS' kind = if_apj_dt_exec_object=>parameter datatype = 'INT4' length = 10 param_text = 'Aufbewahrungsdauer (Tage)' changeable_ind = abap_true mandatory_ind = abap_true )
( selname = 'P_DRYRUN' kind = if_apj_dt_exec_object=>parameter datatype = 'CHAR' length = 1 param_text = 'Testlauf (X = ja)' changeable_ind = abap_true mandatory_ind = abap_false ) ).
" Defaultwerte et_parameter_val = VALUE #( ( selname = 'P_DAYS' low = '90' ) ( selname = 'P_DRYRUN' low = 'X' ) ). ENDMETHOD.
METHOD if_apj_dt_exec_object~execute. " Parameter auslesen mv_days_to_keep = VALUE #( it_parameters[ selname = 'P_DAYS' ]-low DEFAULT 90 ). mv_dry_run = xsdbool( VALUE #( it_parameters[ selname = 'P_DRYRUN' ]-low DEFAULT '' ) = 'X' ).
" Ausführung DATA(lv_deleted) = delete_old_bookings( ).
" Logging log_result( lv_deleted ). ENDMETHOD.
METHOD delete_old_bookings. DATA(lv_cutoff_date) = cl_abap_context_info=>get_system_date( ) - mv_days_to_keep.
IF mv_dry_run = abap_true. " Nur zählen SELECT COUNT( * ) FROM zflight_book WHERE booking_status = 'C' " Cancelled AND booking_date < @lv_cutoff_date INTO @rv_deleted. ELSE. " Tatsächlich löschen DELETE FROM zflight_book WHERE booking_status = 'C' AND booking_date < @lv_cutoff_date. rv_deleted = sy-dbcnt. ENDIF. ENDMETHOD.
METHOD log_result. " Application Log verwenden DATA(lo_log) = cl_bali_log=>create_with_header( header = cl_bali_header_setter=>create( object = 'ZFB_LOG' subobject = 'CLEANUP' ) ).
DATA(lv_msg) = COND #( WHEN mv_dry_run = abap_true THEN |Testlauf: { iv_deleted } Buchungen würden gelöscht.| ELSE |{ iv_deleted } alte Buchungen wurden gelöscht.| ).
lo_log->add_item( cl_bali_free_text_setter=>create( text = lv_msg ) ). cl_bali_log_db=>get_instance( )->save_log( log = lo_log ). ENDMETHOD.
ENDCLASS.Job-Katalog-Eintrag anlegen
Der Job muss im Job Catalog registriert werden. In ADT:
Rechtsklick auf Paket → New → Other ABAP Repository Object→ Application Jobs → Application Job Catalog EntryName: ZFB_BOOKING_CLEANUPBeschreibung: Alte stornierte Buchungen löschenJob-Klasse: ZCL_BOOKING_CLEANUP_JOBJob-Template anlegen (optional)
Name: ZFB_CLEANUP_WEEKLYKatalog-Eintrag: ZFB_BOOKING_CLEANUPParameter: P_DAYS = 30 P_DRYRUN = (leer)Jobs planen und überwachen
Die Planung erfolgt über die Fiori App Schedule Application Jobs:
| Funktion | App-ID | Beschreibung |
|---|---|---|
| Planen | F2640 | Jobs einplanen |
| Überwachen | F2079 | Laufende/beendete Jobs |
| Logs | F0399 | Application Log |
Wann Fiori App vs. Application Job?
Die Wahl zwischen Fiori App und Application Job hängt vom Anwendungsfall ab:
┌─────────────────────────────────────────────────────────────────┐│ Entscheidungsmatrix │├─────────────────────────────────────────────────────────────────┤│ ││ Benutzer-Interaktion erforderlich? ││ ├── Ja → Fiori App (RAP-basiert) ││ │ • Filter setzen ││ │ • Daten bearbeiten ││ │ • Sofortiges Feedback ││ │ ││ └── Nein → Application Job ││ • Geplante Ausführung ││ • Lange Laufzeit ││ • Batch-Verarbeitung ││ ││ Laufzeit > 60 Sekunden? ││ ├── Ja → Application Job oder bgPF ││ └── Nein → Synchron möglich ││ │└─────────────────────────────────────────────────────────────────┘Beispiele für den richtigen Ansatz
| Anforderung | Empfohlener Ansatz |
|---|---|
| Buchungsübersicht anzeigen | Fiori Elements List Report |
| Neue Buchung erfassen | Fiori Object Page |
| Tägliche Statistik erstellen | Application Job |
| Monatliche Bereinigung | Application Job |
| PDF-Bericht generieren | bgPF (asynchrone RAP Action) |
| Ad-hoc Datenanalyse | Executable Class |
Flugbuchungs-Beispiel: Komplettes Programm
Hier ein vollständiges Beispiel für ein ABAP Cloud “Programm”, das verschiedene Ansätze kombiniert:
1. Executable Class für Entwicklung/Test
CLASS zcl_flight_util DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_oo_adt_classrun.
" Wiederverwendbare Methoden METHODS get_bookings_by_carrier IMPORTING iv_carrier TYPE s_carr_id RETURNING VALUE(rt_bookings) TYPE ztt_flight_booking.
METHODS calculate_revenue IMPORTING it_bookings TYPE ztt_flight_booking RETURNING VALUE(rv_revenue) TYPE p LENGTH 15 DECIMALS 2.
ENDCLASS.
CLASS zcl_flight_util IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " Test-Ausführung out->write( 'Flugbuchungs-Utilities - Test' ).
DATA(lt_lh_bookings) = get_bookings_by_carrier( 'LH' ). out->write( |Lufthansa: { lines( lt_lh_bookings ) } Buchungen| ).
DATA(lv_revenue) = calculate_revenue( lt_lh_bookings ). out->write( |Umsatz: { lv_revenue } EUR| ). ENDMETHOD.
METHOD get_bookings_by_carrier. SELECT FROM zflight_book AS b INNER JOIN zflight AS f ON b~flight_id = f~flight_id FIELDS b~booking_id, b~flight_id, b~customer_id, b~booking_date, b~price, b~currency_code WHERE f~carrier_id = @iv_carrier INTO TABLE @rt_bookings. ENDMETHOD.
METHOD calculate_revenue. rv_revenue = REDUCE #( INIT sum = CONV p( 0 ) FOR booking IN it_bookings NEXT sum = sum + booking-price ). ENDMETHOD.ENDCLASS.2. Application Job für Batch-Verarbeitung
CLASS zcl_flight_stats_job DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_apj_dt_exec_object.
ENDCLASS.
CLASS zcl_flight_stats_job IMPLEMENTATION. METHOD if_apj_dt_exec_object~get_parameters. et_parameter_def = VALUE #( ( selname = 'P_PERIOD' kind = if_apj_dt_exec_object=>parameter datatype = 'CHAR' length = 7 param_text = 'Periode (YYYYMM)' mandatory_ind = abap_true ) ).
" Default: Vormonat DATA(lv_date) = cl_abap_context_info=>get_system_date( ) - 30. et_parameter_val = VALUE #( ( selname = 'P_PERIOD' low = |{ lv_date(6) }| ) ). ENDMETHOD.
METHOD if_apj_dt_exec_object~execute. DATA(lv_period) = VALUE #( it_parameters[ selname = 'P_PERIOD' ]-low DEFAULT '' ).
" Statistik berechnen und speichern DATA(lo_util) = NEW zcl_flight_util( ).
" ... Verarbeitungslogik ... ENDMETHOD.ENDCLASS.3. RAP Service für Fiori App
Die interaktive Oberfläche wird über RAP realisiert:
" CDS View für Buchungsanzeige@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Flugbuchungen'define root view entity ZC_FlightBooking provider contract transactional_query as projection on ZI_FlightBooking{ @UI.facet: [{ type: #IDENTIFICATION_REFERENCE }]
@UI.lineItem: [{ position: 10 }] @UI.identification: [{ position: 10 }] key BookingId,
@UI.lineItem: [{ position: 20 }] FlightId,
@UI.lineItem: [{ position: 30 }] @UI.selectionField: [{ position: 10 }] CustomerId,
@UI.lineItem: [{ position: 40 }] @UI.selectionField: [{ position: 20 }] BookingDate,
@UI.lineItem: [{ position: 50 }] Price,
CurrencyCode, BookingStatus}Best Practices
1. Strukturiere deinen Code
" ✅ Gut: Logik in separaten MethodenCLASS zcl_booking_report DEFINITION. PUBLIC SECTION. INTERFACES if_oo_adt_classrun. PRIVATE SECTION. METHODS fetch_data RETURNING VALUE(rt_data) TYPE tt_data. METHODS process_data IMPORTING it_data TYPE tt_data. METHODS output_results IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.ENDCLASS.
" ❌ Schlecht: Alles in mainCLASS zcl_bad_example IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " 500 Zeilen Code hier... ENDMETHOD.ENDCLASS.2. Nutze Context-Informationen
" Statt SY-UNAME, SY-DATUM etc.DATA(lv_user) = cl_abap_context_info=>get_user_technical_name( ).DATA(lv_date) = cl_abap_context_info=>get_system_date( ).DATA(lv_time) = cl_abap_context_info=>get_system_time( ).Details dazu im Artikel System-Felder in ABAP Cloud.
3. Application Logging nutzen
" Statt WRITE für Job-OutputDATA(lo_log) = cl_bali_log=>create_with_header( header = cl_bali_header_setter=>create( object = 'ZFB_LOG' subobject = 'REPORTS' )).lo_log->add_item( cl_bali_free_text_setter=>create( text = 'Verarbeitung gestartet' ) ).cl_bali_log_db=>get_instance( )->save_log( log = lo_log ).4. Unit-Testbarkeit berücksichtigen
" Die Klasse mit if_oo_adt_classrun ist auch unit-testbar!CLASS ltc_flight_util DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. METHODS test_calculate_revenue FOR TESTING.ENDCLASS.
CLASS ltc_flight_util IMPLEMENTATION. METHOD test_calculate_revenue. DATA(lo_cut) = NEW zcl_flight_util( ). DATA(lt_bookings) = VALUE ztt_flight_booking( ( price = 100 ) ( price = 200 ) ). cl_abap_unit_assert=>assert_equals( act = lo_cut->calculate_revenue( lt_bookings ) exp = 300 ). ENDMETHOD.ENDCLASS.Fazit
Programme in ABAP Cloud erfordern ein Umdenken:
| Classic ABAP | ABAP Cloud |
|---|---|
REPORT | Executable Class |
PARAMETERS | Job-Parameter / Fiori Filter |
WRITE | out->write( ) / Application Log |
| SM36/SM37 | Application Jobs (Fiori) |
| Transaktion | Fiori App |
Der Wechsel von Reports zu Klassen fördert besseres Design: Wiederverwendbare Methoden, Unit-Testbarkeit und klare Trennung von Logik und Ausgabe.
Für asynchrone Verarbeitung siehe RAP mit bgPF, für die Entscheidung Fiori Elements vs. Freestyle siehe Fiori Elements vs. Freestyle. Die Grundlagen zur Migration findest du im ABAP Cloud Migration Guide.