Programas en ABAP Cloud: Del Report clásico a la Executable Class

Kategorie
ABAP Cloud
Veröffentlicht
Autor
Johannes

En Classic ABAP, los Reports son la base del desarrollo de aplicaciones. REPORT, PARAMETERS, SELECT-OPTIONS y WRITE - con esto se desarrolló durante décadas. En ABAP Cloud el mundo es diferente: Sin Reports clásicos, sin Dynpros, sin Selection Screens. En su lugar: Executable Classes, Application Jobs y Fiori Apps.

El problema: ¿Reports en ABAP Cloud?

Quien quiera crear un Report clásico en ABAP Cloud se decepcionará:

" ❌ No es posible 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.

Este código será rechazado por el compilador - REPORT, PARAMETERS y WRITE no son sentencias válidas de ABAP Cloud.

Las alternativas en resumen

ABAP Cloud ofrece tres enfoques principales para la ejecución de programas:

┌─────────────────────────────────────────────────────────────────┐
│ Programas en ABAP Cloud │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Executable Class (if_oo_adt_classrun) │
│ → Tests rápidos, desarrollo, debug │
│ → Ejecución directa en ADT │
│ │
│ 2. Application Job │
│ → Procesamiento en segundo plano planificado │
│ → Parámetros vía catálogo de jobs │
│ → Monitoreo en Fiori │
│ │
│ 3. Fiori App (basada en RAP) │
│ → Interfaz de usuario interactiva │
│ → Filtros y Actions │
│ → UX completa │
│ │
└─────────────────────────────────────────────────────────────────┘

Executable Classes con IF_OO_ADT_CLASSRUN

El interface IF_OO_ADT_CLASSRUN permite la ejecución directa de una clase en los ABAP Development Tools (ADT). Es la forma más rápida de ejecutar y probar código.

Estructura básica

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.
" Aquí va la lógica del programa
out->write( 'Report de reservas de vuelos' ).
out->write( '============================' ).
" Cargar datos
SELECT FROM zflight_book
FIELDS booking_id, flight_id, customer_id, booking_date, price
INTO TABLE @DATA(lt_bookings).
" Salida
out->write( lt_bookings ).
ENDMETHOD.
ENDCLASS.

Ejecución en ADT

Clic derecho en clase → Run As → ABAP Application (Console)
→ La salida aparece en la vista Console

Report de reserva de vuelos: Ejemplo completo

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( |Análisis de reservas de vuelos - { cl_abap_context_info=>get_system_date( ) }| ).
out->write( |Creado por: { cl_abap_context_info=>get_user_technical_name( ) }| ).
out->write( '=' && repeat( val = '=' occ = 50 ) ).
out->write( `` ).
" Resumen de reservas por aerolínea
out->write( '1. Reservas por aerolínea:' ).
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 } reservas, | &&
|Ingresos: { ls_summary-total_revenue } EUR, | &&
|Promedio: { ls_summary-avg_price } EUR| ).
ENDLOOP.
out->write( `` ).
" Rutas más populares
out->write( '2. Rutas más populares:' ).
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( 'Análisis completado.' ).
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 } reservas|
TO rt_routes.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Comparación: REPORT vs. Executable Class

AspectoREPORT clásicoExecutable Class
CreaciónREPORT zreport.Clase con Interface
ParámetrosPARAMETERS, SELECT-OPTIONSNinguno (hardcoded o tabla)
SalidaWRITE, ALV, Dynproout->write( )
EjecuciónSE38, SA38, TransacciónADT Console
SchedulingSM36, Background JobApplication Job
TestingManualUnit Tests posibles

Application Jobs: Procesamiento en segundo plano

Para ejecuciones programadas y procesamiento en segundo plano, ABAP Cloud ofrece Application Jobs. Estos reemplazan los Background Jobs clásicos de SM36/SM37.

Componentes de un Application Job

┌─────────────────────────────────────────────────────────────────┐
│ Application Job │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Job Catalog Entry │
│ → Registra el job en el sistema │
│ → Define interface de parámetros │
│ │
│ 2. Job Template (opcional) │
│ → Valores de parámetros predefinidos │
│ → Reutilizable │
│ │
│ 3. Job Class │
│ → Implementa IF_APJ_DT_EXEC_OBJECT │
│ → Contiene lógica de negocio │
│ │
│ 4. Scheduling │
│ → Fiori App "Schedule Application Jobs" │
│ → Único, recurrente, basado en eventos │
│ │
└─────────────────────────────────────────────────────────────────┘

Implementar clase 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 parámetros para el catálogo de jobs
et_parameter_def = VALUE #(
( selname = 'P_DAYS'
kind = if_apj_dt_exec_object=>parameter
datatype = 'INT4'
length = 10
param_text = 'Período de retención (días)'
changeable_ind = abap_true
mandatory_ind = abap_true )
( selname = 'P_DRYRUN'
kind = if_apj_dt_exec_object=>parameter
datatype = 'CHAR'
length = 1
param_text = 'Prueba (X = sí)'
changeable_ind = abap_true
mandatory_ind = abap_false )
).
" Valores por defecto
et_parameter_val = VALUE #(
( selname = 'P_DAYS' low = '90' )
( selname = 'P_DRYRUN' low = 'X' )
).
ENDMETHOD.
METHOD if_apj_dt_exec_object~execute.
" Leer parámetros
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' ).
" Ejecución
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.
" Solo contar
SELECT COUNT( * ) FROM zflight_book
WHERE booking_status = 'C' " Cancelled
AND booking_date < @lv_cutoff_date
INTO @rv_deleted.
ELSE.
" Realmente eliminar
DELETE FROM zflight_book
WHERE booking_status = 'C'
AND booking_date < @lv_cutoff_date.
rv_deleted = sy-dbcnt.
ENDIF.
ENDMETHOD.
METHOD log_result.
" Usar 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 |Prueba: { iv_deleted } reservas serían eliminadas.|
ELSE |{ iv_deleted } reservas antiguas fueron eliminadas.|
).
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.

Crear entrada de catálogo de Job

El Job debe registrarse en el Job Catalog. En ADT:

Clic derecho en paquete → New → Other ABAP Repository Object
→ Application Jobs → Application Job Catalog Entry
Nombre: ZFB_BOOKING_CLEANUP
Descripción: Eliminar reservas canceladas antiguas
Job-Class: ZCL_BOOKING_CLEANUP_JOB

Crear Job-Template (opcional)

Nombre: ZFB_CLEANUP_WEEKLY
Entrada de catálogo: ZFB_BOOKING_CLEANUP
Parámetros:
P_DAYS = 30
P_DRYRUN = (vacío)

Planificar y monitorear Jobs

La planificación se realiza a través de la Fiori App Schedule Application Jobs:

FunciónApp-IDDescripción
PlanificarF2640Programar jobs
MonitorearF2079Jobs en ejecución/completados
LogsF0399Application Log

¿Cuándo Fiori App vs. Application Job?

La elección entre Fiori App y Application Job depende del caso de uso:

┌─────────────────────────────────────────────────────────────────┐
│ Matriz de decisión │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ¿Se requiere interacción del usuario? │
│ ├── Sí → Fiori App (basada en RAP) │
│ │ • Establecer filtros │
│ │ • Editar datos │
│ │ • Feedback inmediato │
│ │ │
│ └── No → Application Job │
│ • Ejecución programada │
│ • Larga duración │
│ • Procesamiento batch │
│ │
│ ¿Tiempo de ejecución > 60 segundos? │
│ ├── Sí → Application Job o bgPF │
│ └── No → Posible síncronamente │
│ │
└─────────────────────────────────────────────────────────────────┘

Ejemplos para el enfoque correcto

RequisitoEnfoque recomendado
Mostrar resumen de reservasFiori Elements List Report
Crear nueva reservaFiori Object Page
Crear estadísticas diariasApplication Job
Limpieza mensualApplication Job
Generar informe PDFbgPF (RAP Action asíncrona)
Análisis de datos ad-hocExecutable Class

Ejemplo de reserva de vuelos: Programa completo

Aquí un ejemplo completo de un “programa” ABAP Cloud que combina diferentes enfoques:

1. Executable Class para desarrollo/test

CLASS zcl_flight_util DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
" Métodos reutilizables
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.
" Ejecución de prueba
out->write( 'Utilidades de reserva de vuelos - Test' ).
DATA(lt_lh_bookings) = get_bookings_by_carrier( 'LH' ).
out->write( |Lufthansa: { lines( lt_lh_bookings ) } reservas| ).
DATA(lv_revenue) = calculate_revenue( lt_lh_bookings ).
out->write( |Ingresos: { 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 para procesamiento batch

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 = 'Período (AAAAMM)'
mandatory_ind = abap_true )
).
" Default: Mes anterior
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 '' ).
" Calcular y guardar estadísticas
DATA(lo_util) = NEW zcl_flight_util( ).
" ... Lógica de procesamiento ...
ENDMETHOD.
ENDCLASS.

3. RAP Service para Fiori App

La interfaz interactiva se realiza a través de RAP:

" CDS View para visualización de reservas
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Reservas de vuelos'
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
}

Mejores prácticas

1. Estructura tu código

" ✅ Bueno: Lógica en métodos separados
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.
" ❌ Malo: Todo en main
CLASS zcl_bad_example IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" 500 líneas de código aquí...
ENDMETHOD.
ENDCLASS.

2. Usa información de contexto

" En lugar 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( ).

Detalles sobre esto en el artículo Campos de sistema en ABAP Cloud.

3. Usar Application Logging

" En lugar de WRITE para output de Job
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 = 'Procesamiento iniciado' ) ).
cl_bali_log_db=>get_instance( )->save_log( log = lo_log ).

4. Considerar testabilidad con Unit Tests

" ¡La clase con if_oo_adt_classrun también es testeable con unit tests!
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.

Conclusión

Los programas en ABAP Cloud requieren un cambio de mentalidad:

Classic ABAPABAP Cloud
REPORTExecutable Class
PARAMETERSJob-Parameters / Fiori Filter
WRITEout->write( ) / Application Log
SM36/SM37Application Jobs (Fiori)
TransacciónFiori App

El cambio de Reports a clases promueve un mejor diseño: Métodos reutilizables, testabilidad con unit tests y clara separación de lógica y salida.

Para procesamiento asíncrono ver RAP con bgPF, para la decisión Fiori Elements vs. Freestyle ver Fiori Elements vs. Freestyle. Los fundamentos de migración los encontrarás en la Guía de migración ABAP Cloud.