Programme in ABAP Cloud: Vom klassischen Report zur Executable Class

Kategorie
ABAP Cloud
Veröffentlicht
Autor
Johannes

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-View

Flugbuchungs-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

AspektClassic REPORTExecutable Class
AnlageREPORT zreport.Klasse mit Interface
ParameterPARAMETERS, SELECT-OPTIONSKeine (hardcoded oder Tabelle)
AusgabeWRITE, ALV, Dynproout->write( )
AusführungSE38, SA38, TransaktionADT Console
SchedulingSM36, Background JobApplication Job
TestingManuellUnit 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 Entry
Name: ZFB_BOOKING_CLEANUP
Beschreibung: Alte stornierte Buchungen löschen
Job-Klasse: ZCL_BOOKING_CLEANUP_JOB

Job-Template anlegen (optional)

Name: ZFB_CLEANUP_WEEKLY
Katalog-Eintrag: ZFB_BOOKING_CLEANUP
Parameter:
P_DAYS = 30
P_DRYRUN = (leer)

Jobs planen und überwachen

Die Planung erfolgt über die Fiori App Schedule Application Jobs:

FunktionApp-IDBeschreibung
PlanenF2640Jobs einplanen
ÜberwachenF2079Laufende/beendete Jobs
LogsF0399Application 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

AnforderungEmpfohlener Ansatz
Buchungsübersicht anzeigenFiori Elements List Report
Neue Buchung erfassenFiori Object Page
Tägliche Statistik erstellenApplication Job
Monatliche BereinigungApplication Job
PDF-Bericht generierenbgPF (asynchrone RAP Action)
Ad-hoc DatenanalyseExecutable 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 Methoden
CLASS 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 main
CLASS 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-Output
DATA(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 ABAPABAP Cloud
REPORTExecutable Class
PARAMETERSJob-Parameter / Fiori Filter
WRITEout->write( ) / Application Log
SM36/SM37Application Jobs (Fiori)
TransaktionFiori 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.