Programmes en ABAP Cloud : Du report classique a l

Catégorie
ABAP Cloud
Publié
Auteur
Johannes

En Classic ABAP, les reports sont le fondement du developpement d’applications. REPORT, PARAMETERS, SELECT-OPTIONS et WRITE - c’est ainsi qu’on a developpe pendant des decennies. En ABAP Cloud, le monde est different : pas de reports classiques, pas de Dynpros, pas de Selection Screens. A la place : Executable Classes, Application Jobs et Apps Fiori.

Le probleme : Des reports en ABAP Cloud ?

Celui qui veut creer un report classique en ABAP Cloud sera decu :

" ❌ Pas possible en ABAP Cloud:
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.

Ce code sera rejete par le compilateur - REPORT, PARAMETERS et WRITE ne sont pas des instructions ABAP Cloud valides.

Les alternatives en apercu

ABAP Cloud offre trois approches principales pour l’execution de programmes :

┌─────────────────────────────────────────────────────────────────┐
│ Programmes en ABAP Cloud │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Executable Class (if_oo_adt_classrun) │
│ → Tests rapides, developpement, debug │
│ → Execution directement dans ADT │
│ │
│ 2. Application Job │
│ → Traitement planifie en arriere-plan │
│ → Parametres via catalogue de jobs │
│ → Monitoring dans Fiori │
│ │
│ 3. App Fiori (basee sur RAP) │
│ → Interface utilisateur interactive │
│ → Filtres et actions │
│ → UX complete │
│ │
└─────────────────────────────────────────────────────────────────┘

Executable Classes avec IF_OO_ADT_CLASSRUN

L’interface IF_OO_ADT_CLASSRUN permet l’execution directe d’une classe dans les ABAP Development Tools (ADT). C’est le moyen le plus rapide d’executer et de tester du code.

Structure de base

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.
" La logique du programme vient ici
out->write( 'Report de reservations de vol' ).
out->write( '===================' ).
" Charger les donnees
SELECT FROM zflight_book
FIELDS booking_id, flight_id, customer_id, booking_date, price
INTO TABLE @DATA(lt_bookings).
" Sortie
out->write( lt_bookings ).
ENDMETHOD.
ENDCLASS.

Execution dans ADT

Clic droit sur la classe → Run As → ABAP Application (Console)
→ La sortie apparait dans la vue Console

Report de reservations de vol : Exemple complet

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( |Analyse des reservations de vol - { cl_abap_context_info=>get_system_date( ) }| ).
out->write( |Cree par: { cl_abap_context_info=>get_user_technical_name( ) }| ).
out->write( '=' && repeat( val = '=' occ = 50 ) ).
out->write( `` ).
" Resume des reservations par transporteur
out->write( '1. Reservations par compagnie aerienne:' ).
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 } reservations, | &&
|CA: { ls_summary-total_revenue } EUR, | &&
|Moyenne: { ls_summary-avg_price } EUR| ).
ENDLOOP.
out->write( `` ).
" Top routes
out->write( '2. Routes les plus populaires:' ).
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 terminee.' ).
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 } reservations|
TO rt_routes.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Comparaison : REPORT vs. Executable Class

AspectREPORT classiqueExecutable Class
CreationREPORT zreport.Classe avec interface
ParametresPARAMETERS, SELECT-OPTIONSAucun (code en dur ou table)
SortieWRITE, ALV, Dynproout->write( )
ExecutionSE38, SA38, TransactionConsole ADT
PlanificationSM36, Background JobApplication Job
TestsManuelTests unitaires possibles

Application Jobs : Traitement en arriere-plan

Pour les executions planifiees et le traitement en arriere-plan, ABAP Cloud offre les Application Jobs. Ceux-ci remplacent les Background Jobs classiques de SM36/SM37.

Composants d’un Application Job

┌─────────────────────────────────────────────────────────────────┐
│ Application Job │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Job Catalog Entry │
│ → Enregistre le job dans le systeme │
│ → Definit l'interface des parametres │
│ │
│ 2. Job Template (optionnel) │
│ → Valeurs de parametres predefinies │
│ → Reutilisable │
│ │
│ 3. Job Class │
│ → Implemente IF_APJ_DT_EXEC_OBJECT │
│ → Contient la logique metier │
│ │
│ 4. Planification │
│ → App Fiori "Schedule Application Jobs" │
│ → Unique, recurrent, base sur evenement │
│ │
└─────────────────────────────────────────────────────────────────┘

Implementer une classe de job

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.
" Definir les parametres pour le catalogue de jobs
et_parameter_def = VALUE #(
( selname = 'P_DAYS"
kind = if_apj_dt_exec_object=>parameter
datatype = 'INT4"
length = 10
param_text = 'Duree de conservation (jours)"
changeable_ind = abap_true
mandatory_ind = abap_true )
( selname = 'P_DRYRUN"
kind = if_apj_dt_exec_object=>parameter
datatype = 'CHAR"
length = 1
param_text = 'Simulation (X = oui)"
changeable_ind = abap_true
mandatory_ind = abap_false )
).
" Valeurs par defaut
et_parameter_val = VALUE #(
( selname = 'P_DAYS' low = '90' )
( selname = 'P_DRYRUN' low = 'X' )
).
ENDMETHOD.
METHOD if_apj_dt_exec_object~execute.
" Lire les parametres
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' ).
" Execution
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.
" Compter seulement
SELECT COUNT( * ) FROM zflight_book
WHERE booking_status = 'C' " Annule
AND booking_date < @lv_cutoff_date
INTO @rv_deleted.
ELSE.
" Supprimer reellement
DELETE FROM zflight_book
WHERE booking_status = 'C"
AND booking_date < @lv_cutoff_date.
rv_deleted = sy-dbcnt.
ENDIF.
ENDMETHOD.
METHOD log_result.
" Utiliser Application Log
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 |Simulation: { iv_deleted } reservations seraient supprimees.|
ELSE |{ iv_deleted } anciennes reservations ont ete supprimees.|
).
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.

Creer une entree de catalogue de jobs

Le job doit etre enregistre dans le Job Catalog. Dans ADT :

Clic droit sur package → New → Other ABAP Repository Object
→ Application Jobs → Application Job Catalog Entry
Nom: ZFB_BOOKING_CLEANUP
Description: Supprimer les anciennes reservations annulees
Classe de job: ZCL_BOOKING_CLEANUP_JOB

Creer un template de job (optionnel)

Nom: ZFB_CLEANUP_WEEKLY
Entree catalogue: ZFB_BOOKING_CLEANUP
Parametres:
P_DAYS = 30
P_DRYRUN = (vide)

Planifier et surveiller les jobs

La planification s’effectue via l’app Fiori Schedule Application Jobs :

FonctionApp-IDDescription
PlanifierF2640Planifier des jobs
SurveillerF2079Jobs en cours/termines
LogsF0399Application Log

Quand App Fiori vs. Application Job ?

Le choix entre App Fiori et Application Job depend du cas d’utilisation :

┌─────────────────────────────────────────────────────────────────┐
│ Matrice de decision │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Interaction utilisateur necessaire ? │
│ ├── Oui → App Fiori (basee sur RAP) │
│ │ • Definir des filtres │
│ │ • Modifier des donnees │
│ │ • Feedback immediat │
│ │ │
│ └── Non → Application Job │
│ • Execution planifiee │
│ • Longue duree │
│ • Traitement par lots │
│ │
│ Duree d'execution > 60 secondes ? │
│ ├── Oui → Application Job ou bgPF │
│ └── Non → Synchrone possible │
│ │
└─────────────────────────────────────────────────────────────────┘

Exemples pour la bonne approche

ExigenceApproche recommandee
Afficher l’apercu des reservationsFiori Elements List Report
Saisir une nouvelle reservationFiori Object Page
Creer des statistiques quotidiennesApplication Job
Nettoyage mensuelApplication Job
Generer un rapport PDFbgPF (action RAP asynchrone)
Analyse de donnees ad-hocExecutable Class

Exemple de reservation de vol : Programme complet

Voici un exemple complet d’un “programme” ABAP Cloud qui combine differentes approches :

1. Executable Class pour developpement/test

CLASS zcl_flight_util DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
" Methodes reutilisables
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.
" Execution de test
out->write( 'Utilitaires de reservation de vol - Test' ).
DATA(lt_lh_bookings) = get_bookings_by_carrier( 'LH' ).
out->write( |Lufthansa: { lines( lt_lh_bookings ) } reservations| ).
DATA(lv_revenue) = calculate_revenue( lt_lh_bookings ).
out->write( |Chiffre d'affaires: { 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 pour traitement par lots

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: Mois precedent
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 '' ).
" Calculer et sauvegarder les statistiques
DATA(lo_util) = NEW zcl_flight_util( ).
" ... Logique de traitement ...
ENDMETHOD.
ENDCLASS.

3. Service RAP pour App Fiori

L’interface interactive est realisee via RAP :

" CDS View pour l'affichage des reservations
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Reservations de vol"
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
}

Bonnes pratiques

1. Structurez votre code

" ✅ Bien: Logique dans des methodes separees
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.
" ❌ Mauvais: Tout dans main
CLASS zcl_bad_example IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" 500 lignes de code ici...
ENDMETHOD.
ENDCLASS.

2. Utilisez les informations de contexte

" Au lieu de 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 dans l’article Champs systeme en ABAP Cloud.

3. Utilisez Application Logging

" Au lieu de WRITE pour la sortie des jobs
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 = 'Traitement demarre' ) ).
cl_bali_log_db=>get_instance( )->save_log( log = lo_log ).

4. Considerez la testabilite unitaire

" La classe avec if_oo_adt_classrun est aussi testable unitairement!
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.

Conclusion

Les programmes en ABAP Cloud necessitent un changement de mentalite :

Classic ABAPABAP Cloud
REPORTExecutable Class
PARAMETERSParametres de job / Filtres Fiori
WRITEout->write( ) / Application Log
SM36/SM37Application Jobs (Fiori)
TransactionApp Fiori

Le passage des reports aux classes favorise une meilleure conception : methodes reutilisables, testabilite unitaire et separation claire de la logique et de la sortie.

Pour le traitement asynchrone, voir RAP avec bgPF, pour le choix Fiori Elements vs. Freestyle voir Fiori Elements vs. Freestyle. Les bases de la migration se trouvent dans le Guide de migration ABAP Cloud.