Actions y Functions son los medios en RAP para implementar lógica de negocio propia más allá de las operaciones CRUD estándar. Este artículo explica las diferencias y muestra cuándo usar cada concepto.
Action vs. Function: La Diferencia
| Aspecto | Action | Function |
|---|---|---|
| Propósito | Modifica datos (efectos secundarios) | Lee/calcula datos (sin efectos secundarios) |
| Método HTTP | POST | GET |
| Transaccional | Sí, parte de la transacción | No, solo lectura |
| Idempotente | No | Sí |
| Ejemplos | Cancelar, Aprobar, Copiar | Calcular precio, Verificar disponibilidad |
¿Cuándo Action, cuándo Function?
- Action: Cuando la operación cambia el estado de la entidad o del sistema
- Function: Cuando solo se calcula un valor o se consulta información, sin modificar datos
Bound vs. Unbound (Static)
| Aspecto | Bound (Instance) | Unbound (Static) |
|---|---|---|
| Contexto | Vinculada a una instancia | Sin contexto de instancia |
| Llamada | En fila seleccionada | Mediante botón en toolbar |
| Keys | Keys de la instancia disponibles | Sin keys automáticas |
| Caso de uso | ”Cancelar este viaje" | "Liberar todos los viajes abiertos” |
Instance-bound Action
Una Instance-bound Action se ejecuta en una instancia concreta:
Behavior Definition
managed implementation in class zbp_i_travel unique;strict ( 2 );
define behavior for ZI_Travel alias Travelpersistent table ztravellock masterauthorization master ( instance ){ // Instance-bound Action sin parámetro action cancel result [1] $self;
// Instance-bound Action con resultado action accept result [1] $self;
// Instance-bound Action que devuelve múltiples instancias action copyTravel result [0..*] $self;}Implementación
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS cancel FOR MODIFY IMPORTING keys FOR ACTION Travel~cancel RESULT result.
METHODS accept FOR MODIFY IMPORTING keys FOR ACTION Travel~accept RESULT result.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION. METHOD cancel. " Leer datos actuales READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( TravelID Status ) WITH CORRESPONDING #( keys ) RESULT DATA(travels).
" Solo los viajes abiertos pueden cancelarse LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>) WHERE Status = 'O'.
" Establecer estado a Cancelled MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ) WITH VALUE #( ( %tky = <travel>-%tky Status = 'X' " Cancelled ) ). ENDLOOP.
" Devolver resultado READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels_result).
result = VALUE #( FOR travel IN travels_result ( %tky = travel-%tky %param = travel ) ). ENDMETHOD.
METHOD accept. " Establecer estado a Accepted MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky Status = 'A' " Accepted ) ).
" Devolver datos actualizados READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels).
result = VALUE #( FOR travel IN travels ( %tky = travel-%tky %param = travel ) ). ENDMETHOD.ENDCLASS.Cardinalidad del Result
| Cardinalidad | Significado |
|---|---|
result [1] $self | Exactamente una instancia de vuelta (la misma entidad) |
result [0..1] $self | Opcionalmente una instancia |
result [0..*] $self | Cualquier cantidad de instancias |
result [1] EntityName | Una instancia de otra entidad |
| (sin result) | Sin devolución |
Static (Unbound) Action
Las Static Actions no están vinculadas a una instancia y se usan típicamente para operaciones masivas:
Behavior Definition
define behavior for ZI_Travel alias Travel{ // Static Action sin parámetro static action releaseAllOpen;
// Static Action con resultado static action createFromTemplate result [1] $self;
// Static Action con Global Feature Control static action ( features: global ) massUpdate;
// Para Global Feature Control static features;}Implementación
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS releaseAllOpen FOR MODIFY IMPORTING keys FOR ACTION Travel~releaseAllOpen.
METHODS createFromTemplate FOR MODIFY IMPORTING keys FOR ACTION Travel~createFromTemplate RESULT result.
METHODS get_global_features FOR GLOBAL FEATURES IMPORTING REQUEST requested_features FOR Travel RESULT result.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION. METHOD releaseAllOpen. " Encontrar todos los viajes abiertos SELECT * FROM ztravel WHERE status = 'O' INTO TABLE @DATA(open_travels).
" Establecer estado a Released MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ) WITH VALUE #( FOR travel IN open_travels ( TravelUUID = travel-travel_uuid Status = 'R' " Released ) ). ENDMETHOD.
METHOD createFromTemplate. " Crear nuevo viaje desde plantilla DATA(template_id) = 'DEFAULT'.
" Cargar plantilla (ejemplo simplificado) SELECT SINGLE * FROM ztravel_template WHERE template_id = @template_id INTO @DATA(template).
" Crear nuevo viaje MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel CREATE FIELDS ( CustomerID BeginDate EndDate Description ) WITH VALUE #( ( %cid = 'NEW_TRAVEL' CustomerID = template-customer_id BeginDate = cl_abap_context_info=>get_system_date( ) EndDate = cl_abap_context_info=>get_system_date( ) + 7 Description = 'Creado desde plantilla' ) ) MAPPED DATA(mapped).
" Devolver entidad creada READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH VALUE #( ( %tky = mapped-travel[ 1 ]-%tky ) ) RESULT DATA(created_travel).
result = VALUE #( ( %cid_ref = 'NEW_TRAVEL' %param = created_travel[ 1 ] ) ). ENDMETHOD.
METHOD get_global_features. " Verificar si el usuario tiene autorización para actualización masiva DATA(has_auth) = check_mass_update_auth( ).
result = VALUE #( %action-massUpdate = COND #( WHEN has_auth = abap_true THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) ). ENDMETHOD.ENDCLASS.Function: Cálculos sin Efectos Secundarios
Las Functions son ideales para cálculos y consultas que no modifican datos:
Behavior Definition
define behavior for ZI_Travel alias Travel{ // Instance-bound Function function calculatePrice result [1] ZA_Price;
// Function con múltiples valores de retorno function getAvailability result [1] ZA_Availability;
// Static Function static function getExchangeRates result [0..*] ZA_ExchangeRate;
// Function con parámetros function simulateDiscount parameter ZA_DiscountInput result [1] ZA_DiscountResult;}Abstract Entity para Valores de Retorno
Las Functions a menudo necesitan entidades abstractas para valores de retorno estructurados:
define abstract entity ZA_Price{ TravelUUID : sysuuid_x16; NetPrice : abap.dec( 15, 2 ); TaxAmount : abap.dec( 15, 2 ); TotalPrice : abap.dec( 15, 2 ); Currency : abap.cuky;}define abstract entity ZA_Availability{ TravelUUID : sysuuid_x16; IsAvailable : abap_boolean; SeatsLeft : abap.int4; NextAvailable: abap.dats;}Implementación
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS calculatePrice FOR READ IMPORTING keys FOR FUNCTION Travel~calculatePrice RESULT result.
METHODS getAvailability FOR READ IMPORTING keys FOR FUNCTION Travel~getAvailability RESULT result.
METHODS getExchangeRates FOR READ IMPORTING keys FOR FUNCTION Travel~getExchangeRates RESULT result.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION. METHOD calculatePrice. " Leer datos del viaje READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( TotalPrice Currency ) WITH CORRESPONDING #( keys ) RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>). " Calcular precio (simplificado) DATA(net_price) = <travel>-TotalPrice * '0.84'. DATA(tax_amount) = <travel>-TotalPrice * '0.16'.
APPEND VALUE #( %tky = <travel>-%tky %param = VALUE za_price( TravelUUID = <travel>-TravelUUID NetPrice = net_price TaxAmount = tax_amount TotalPrice = <travel>-TotalPrice Currency = <travel>-Currency ) ) TO result. ENDLOOP. ENDMETHOD.
METHOD getAvailability. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( BeginDate MaxSeats BookedSeats ) WITH CORRESPONDING #( keys ) RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>). DATA(seats_left) = <travel>-MaxSeats - <travel>-BookedSeats.
APPEND VALUE #( %tky = <travel>-%tky %param = VALUE za_availability( TravelUUID = <travel>-TravelUUID IsAvailable = COND #( WHEN seats_left > 0 THEN abap_true ELSE abap_false ) SeatsLeft = seats_left NextAvailable = COND #( WHEN seats_left = 0 THEN <travel>-BeginDate + 30 ELSE <travel>-BeginDate ) ) ) TO result. ENDLOOP. ENDMETHOD.
METHOD getExchangeRates. " Static Function - sin keys disponibles " Leer tipos de cambio del sistema SELECT * FROM tcurr WHERE kurst = 'M' AND gdatu >= @( cl_abap_context_info=>get_system_date( ) ) INTO TABLE @DATA(rates) UP TO 100 ROWS.
result = VALUE #( FOR rate IN rates ( %param = VALUE za_exchangerate( SourceCurrency = rate-fcurr TargetCurrency = rate-tcurr ExchangeRate = rate-ukurs ValidFrom = rate-gdatu ) ) ). ENDMETHOD.ENDCLASS.Action con Parámetros de Entrada
Las Actions pueden recibir parámetros de entrada estructurados:
Behavior Definition
define behavior for ZI_Travel alias Travel{ // Action con parámetro abstracto action rejectWithReason parameter ZA_RejectionReason result [1] $self;
// Action con múltiples parámetros action reschedule parameter ZA_RescheduleInput result [1] $self;
// Action con Deep Structure action addParticipants parameter ZA_ParticipantList result [1] $self;}Abstract Entity para Parámetros de Entrada
define abstract entity ZA_RejectionReason{ ReasonCode : abap.char( 2 ); ReasonText : abap.string( 255 ); NotifyCustomer : abap_boolean;}define abstract entity ZA_RescheduleInput{ NewBeginDate : abap.dats; NewEndDate : abap.dats; UpdatePrice : abap_boolean;}Implementación
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS rejectWithReason FOR MODIFY IMPORTING keys FOR ACTION Travel~rejectWithReason RESULT result.
METHODS reschedule FOR MODIFY IMPORTING keys FOR ACTION Travel~reschedule RESULT result.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION. METHOD rejectWithReason. " Leer parámetro de keys LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>). " Motivo del rechazo desde parámetro DATA(reason_code) = <key>-%param-ReasonCode. DATA(reason_text) = <key>-%param-ReasonText. DATA(notify) = <key>-%param-NotifyCustomer.
" Guardar estado y justificación MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status RejectionReason ) WITH VALUE #( ( %tky = <key>-%tky Status = 'X' " Rejected RejectionReason = reason_text ) ).
" Opcional: Notificar al cliente IF notify = abap_true. " Enviar email... send_rejection_notification( travel_key = <key>-%tky reason = reason_text ). ENDIF. ENDLOOP.
" Devolver datos actualizados READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels).
result = VALUE #( FOR travel IN travels ( %tky = travel-%tky %param = travel ) ). ENDMETHOD.
METHOD reschedule. LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>). " Nuevas fechas desde parámetro DATA(new_begin) = <key>-%param-NewBeginDate. DATA(new_end) = <key>-%param-NewEndDate. DATA(update_price) = <key>-%param-UpdatePrice.
" Actualizar fechas MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( BeginDate EndDate ) WITH VALUE #( ( %tky = <key>-%tky BeginDate = new_begin EndDate = new_end ) ).
" Opcional: Recalcular precio IF update_price = abap_true. recalculate_price( <key>-%tky ). ENDIF. ENDLOOP.
" Devolver resultado READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels).
result = VALUE #( FOR travel IN travels ( %tky = travel-%tky %param = travel ) ). ENDMETHOD.ENDCLASS.Actions en la Projection
Las Actions deben exponerse en la Projection:
projection implementation in class zbp_c_travel unique;strict ( 2 );
define behavior for ZC_Travel alias Travel{ use action cancel; use action accept; use action rejectWithReason; use action reschedule; use function calculatePrice; use function getAvailability;}Integración UI con Fiori Elements
Ubicación de Botones
define root view entity ZC_Travel provider contract transactional_query as projection on ZI_Travel{ @UI.lineItem: [ { position: 10 }, { type: #FOR_ACTION, dataAction: 'cancel', label: 'Cancelar' }, { type: #FOR_ACTION, dataAction: 'accept', label: 'Aprobar' } ] @UI.identification: [ { position: 10 }, { type: #FOR_ACTION, dataAction: 'rejectWithReason', label: 'Rechazar' } ] key TravelUUID,
// Header Actions @UI.fieldGroup: [{ qualifier: 'Actions', type: #FOR_ACTION, dataAction: 'reschedule', label: 'Reprogramar' }] Status}Action con Diálogo
Para Actions con parámetros de entrada se genera automáticamente un diálogo:
define abstract entity ZA_RejectionReason{ @UI.defaultValue: 'OT' @Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_ReasonCode', element: 'Code' }}] ReasonCode : abap.char( 2 );
@UI.multiLineText: true ReasonText : abap.string( 255 );
@UI.defaultValue: 'true' NotifyCustomer : abap_boolean;}Factory Actions
Las Factory Actions crean nuevas instancias basadas en existentes:
define behavior for ZI_Travel alias Travel{ // Factory Action crea nueva instancia factory action copyTravel [1];
// Factory Action con parámetro factory action createFromBooking parameter ZA_BookingRef [1];}Implementación
METHOD copyTravel. " Leer datos originales READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>). " Crear copia con nuevo %cid MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel CREATE FIELDS ( CustomerID BeginDate EndDate Description TotalPrice Currency ) WITH VALUE #( ( %cid = |COPY_{ sy-index }| CustomerID = <travel>-CustomerID BeginDate = <travel>-BeginDate EndDate = <travel>-EndDate Description = |Copia de { <travel>-TravelID }| TotalPrice = <travel>-TotalPrice Currency = <travel>-Currency ) ) MAPPED DATA(mapped).
" mapped contiene %cid y %tky de la nueva instancia APPEND VALUE #( %cid_ref = keys[ sy-index ]-%cid_ref %key = mapped-travel[ 1 ]-%key ) TO mapped-travel. ENDLOOP.ENDMETHOD.Mejores Prácticas
-
Elegir correctamente Action vs. Function: Actions para cambios, Functions para cálculos.
-
Definir Result Type: Siempre especifica
result [n] $selfpara que la UI se actualice. -
Usar IN LOCAL MODE: Para llamadas internas omitir verificaciones de autorización.
-
Manejo de Errores: Devolver errores via
FAILEDyREPORTED:
METHOD cancel. " Validación IF travel-Status <> 'O'. APPEND VALUE #( %tky = travel-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Solo los viajes abiertos pueden cancelarse' ) ) TO reported-travel.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel. RETURN. ENDIF.ENDMETHOD.-
Combinar con Feature Control: Desactivar Actions cuando no sean aplicables.
-
Consistencia Transaccional: Todos los cambios se confirman en el SAVE.
Temas Relacionados
- Fundamentos de RAP - Bases del RESTful ABAP Programming Model
- Feature Control y Side Effects - Control dinámico de UI
- Draft Handling en RAP - Almacenamiento intermedio con Drafts