RAP Actions und Functions: Geschäftslogik implementieren

kategorie
RAP
Veröffentlicht
autor
Johannes

Actions und Functions sind die Mittel in RAP, um eigene Geschäftslogik über die Standard-CRUD-Operationen hinaus zu implementieren. Dieser Artikel erklärt die Unterschiede und zeigt, wann welches Konzept zum Einsatz kommt.

Action vs. Function: Der Unterschied

AspektActionFunction
ZweckÄndert Daten (Seiteneffekte)Liest/berechnet Daten (keine Seiteneffekte)
HTTP-MethodePOSTGET
TransaktionalJa, Teil der TransactionNein, nur lesend
IdempotentNeinJa
BeispieleStornieren, Freigeben, KopierenPreis berechnen, Verfügbarkeit prüfen

Wann Action, wann Function?

  • Action: Wenn die Operation den Zustand der Entität oder des Systems ändert
  • Function: Wenn nur ein Wert berechnet oder eine Information abgefragt wird, ohne Daten zu ändern

Bound vs. Unbound (Static)

AspektBound (Instance)Unbound (Static)
KontextAn eine Instanz gebundenOhne Instanz-Kontext
AufrufAuf selektierter ZeileÜber Button in Toolbar
KeysKeys der Instanz verfügbarKeine automatischen Keys
Anwendungsfall”Diese Reise stornieren""Alle offenen Reisen freigeben”

Instance-bound Action

Eine Instance-bound Action wird auf einer konkreten Instanz ausgeführt:

Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
persistent table ztravel
lock master
authorization master ( instance )
{
// Instance-bound Action ohne Parameter
action cancel result [1] $self;
// Instance-bound Action mit Ergebnis
action accept result [1] $self;
// Instance-bound Action die mehrere Instanzen zurückgibt
action copyTravel result [0..*] $self;
}

Implementation

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.
" Aktuelle Daten lesen
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TravelID Status )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
" Nur offene Reisen können storniert werden
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>)
WHERE Status = 'O'.
" Status auf Cancelled setzen
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( (
%tky = <travel>-%tky
Status = 'X' " Cancelled
) ).
ENDLOOP.
" Ergebnis zurückgeben
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.
" Status auf Accepted setzen
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
) ).
" Aktualisierte Daten zurückgeben
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.

Result-Kardinalität

KardinalitätBedeutung
result [1] $selfGenau eine Instanz zurück (die gleiche Entität)
result [0..1] $selfOptional eine Instanz
result [0..*] $selfBeliebig viele Instanzen
result [1] EntityNameEine Instanz einer anderen Entität
(kein result)Keine Rückgabe

Static (Unbound) Action

Static Actions sind nicht an eine Instanz gebunden und werden typischerweise für Massenoperationen verwendet:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Static Action ohne Parameter
static action releaseAllOpen;
// Static Action mit Ergebnis
static action createFromTemplate result [1] $self;
// Static Action mit Global Feature Control
static action ( features: global ) massUpdate;
// Für Global Feature Control
static features;
}

Implementation

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.
" Alle offenen Reisen finden
SELECT * FROM ztravel
WHERE status = 'O'
INTO TABLE @DATA(open_travels).
" Status auf Released setzen
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.
" Neue Reise aus Template erstellen
DATA(template_id) = 'DEFAULT'.
" Template laden (vereinfachtes Beispiel)
SELECT SINGLE * FROM ztravel_template
WHERE template_id = @template_id
INTO @DATA(template).
" Neue Reise erstellen
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 = 'Erstellt aus Template'
) )
MAPPED DATA(mapped).
" Erstellte Entität zurückgeben
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.
" Prüfen ob Benutzer Berechtigung für Massenupdate hat
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: Berechnungen ohne Seiteneffekte

Functions sind ideal für Berechnungen und Abfragen, die keine Daten ändern:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Instance-bound Function
function calculatePrice result [1] ZA_Price;
// Function mit mehreren Rückgabewerten
function getAvailability result [1] ZA_Availability;
// Static Function
static function getExchangeRates result [0..*] ZA_ExchangeRate;
// Function mit Parametern
function simulateDiscount
parameter ZA_DiscountInput
result [1] ZA_DiscountResult;
}

Abstract Entity für Rückgabewerte

Functions benötigen oft abstrakte Entitäten für strukturierte Rückgabewerte:

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;
}

Implementation

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.
" Reisedaten lesen
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>).
" Preis berechnen (vereinfacht)
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 - keine keys vorhanden
" Wechselkurse aus System lesen
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 mit Input-Parametern

Actions können strukturierte Input-Parameter entgegennehmen:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Action mit abstraktem Parameter
action rejectWithReason
parameter ZA_RejectionReason
result [1] $self;
// Action mit mehreren Parametern
action reschedule
parameter ZA_RescheduleInput
result [1] $self;
// Action mit Deep Structure
action addParticipants
parameter ZA_ParticipantList
result [1] $self;
}

Abstract Entity für Input-Parameter

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;
}

Implementation

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.
" Parameter aus keys lesen
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Ablehnungsgrund aus Parameter
DATA(reason_code) = <key>-%param-ReasonCode.
DATA(reason_text) = <key>-%param-ReasonText.
DATA(notify) = <key>-%param-NotifyCustomer.
" Status und Begründung speichern
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
) ).
" Optional: Kunde benachrichtigen
IF notify = abap_true.
" E-Mail senden...
send_rejection_notification(
travel_key = <key>-%tky
reason = reason_text
).
ENDIF.
ENDLOOP.
" Aktualisierte Daten zurückgeben
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>).
" Neue Termine aus Parameter
DATA(new_begin) = <key>-%param-NewBeginDate.
DATA(new_end) = <key>-%param-NewEndDate.
DATA(update_price) = <key>-%param-UpdatePrice.
" Termine aktualisieren
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
) ).
" Optional: Preis neu berechnen
IF update_price = abap_true.
recalculate_price( <key>-%tky ).
ENDIF.
ENDLOOP.
" Ergebnis zurückgeben
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 in der Projection

Actions müssen in der Projection exponiert werden:

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;
}

UI-Integration mit Fiori Elements

Button-Platzierung

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: 'Stornieren' },
{ type: #FOR_ACTION, dataAction: 'accept', label: 'Freigeben' }
]
@UI.identification: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'rejectWithReason', label: 'Ablehnen' }
]
key TravelUUID,
// Header Actions
@UI.fieldGroup: [{ qualifier: 'Actions',
type: #FOR_ACTION, dataAction: 'reschedule', label: 'Umbuchen' }]
Status
}

Action mit Dialog

Für Actions mit Input-Parametern wird automatisch ein Dialog generiert:

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

Factory Actions erstellen neue Instanzen basierend auf existierenden:

define behavior for ZI_Travel alias Travel
{
// Factory Action erstellt neue Instanz
factory action copyTravel [1];
// Factory Action mit Parameter
factory action createFromBooking parameter ZA_BookingRef [1];
}

Implementation

METHOD copyTravel.
" Originaldaten lesen
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>).
" Kopie mit neuem %cid erstellen
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 = |Kopie von { <travel>-TravelID }|
TotalPrice = <travel>-TotalPrice
Currency = <travel>-Currency
) )
MAPPED DATA(mapped).
" mapped enthält %cid und %tky der neuen Instanz
APPEND VALUE #(
%cid_ref = keys[ sy-index ]-%cid_ref
%key = mapped-travel[ 1 ]-%key
) TO mapped-travel.
ENDLOOP.
ENDMETHOD.

Best Practices

  1. Action vs. Function korrekt wählen: Actions für Änderungen, Functions für Berechnungen.

  2. Result Type definieren: Gib immer result [n] $self an, damit die UI aktualisiert wird.

  3. IN LOCAL MODE verwenden: Bei internen Aufrufen Berechtigungsprüfungen umgehen.

  4. Error Handling: Fehler über FAILED und REPORTED zurückgeben:

METHOD cancel.
" Validierung
IF travel-Status <> 'O'.
APPEND VALUE #(
%tky = travel-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Nur offene Reisen können storniert werden'
)
) TO reported-travel.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
RETURN.
ENDIF.
ENDMETHOD.
  1. Feature Control kombinieren: Deaktiviere Actions wenn sie nicht anwendbar sind.

  2. Transaktionale Konsistenz: Alle Änderungen werden erst beim SAVE committed.

Weiterführende Themen