Determinations und Validations sind die beiden Mechanismen in RAP, um Geschäftslogik automatisch auszuführen. Determinations setzen Werte, Validations prüfen sie. Das richtige Verständnis des Timings (wann wird was ausgeführt) ist entscheidend für fehlerfreie RAP Business Objects.
Der Unterschied auf einen Blick
┌─────────────────────────────────────────────────────────────┐│ User Action (Create/Update) │└────────────────────┬────────────────────────────────────────┘ │ ▼ ┌───────────────────────┐ │ DETERMINATION │ ← Setzt automatisch Werte │ "Was fehlt?" │ (Status, Nummern, Defaults) └───────────┬───────────┘ │ ▼ ┌───────────────────────┐ │ VALIDATION │ ← Prüft Geschäftsregeln │ "Ist es korrekt?" │ (Datumslogik, Pflichtfelder) └───────────┬───────────┘ │ ▼ (nur bei Erfolg) ┌───────────────────────┐ │ Save to Database │ └───────────────────────┘| Aspekt | Determination | Validation |
|---|---|---|
| Zweck | Werte setzen/berechnen | Werte prüfen |
| Timing | on modify oder on save | on save |
| Änderungen | ✅ Darf Entity ändern | ❌ Nur lesen, keine Änderungen |
| Fehler | Keine direkten Fehler | Füllt FAILED & REPORTED |
| Beispiel | Status auf ‘O’ setzen | Enddatum nach Beginn prüfen |
| Reihenfolge | Zuerst (vor Validation) | Danach (nach Determination) |
Determinations: Automatische Wertsetzung
Wann Determinations nutzen?
✅ Perfekt für:
- Default-Werte setzen (Status = ‘Open’)
- Felder berechnen (Gesamtpreis = Preis × Menge)
- Abhängige Felder aktualisieren
- Timestamps setzen (CreatedAt, LastChangedAt)
- Geschäftslogik-basierte Werte (Rabatt nach Kundenkategorie)
Syntax in Behavior Definition
define behavior for ZI_Travel alias Travel{ // Determination Syntax: determination <MethodName> on <Trigger> { <TriggerCondition>; }
// Trigger: modify (sofort) oder save (vor DB-Commit) // TriggerCondition: create, update, field <FieldName>}Beispiel 1: Status bei Create setzen
// Behavior Definitiondefine behavior for ZI_Travel alias Travelpersistent table ztravel{ create; update;
field ( readonly ) TravelId, Status; field ( readonly ) CreatedBy, CreatedAt;
// Determination: Bei Create → Status auf 'O' (Open) setzen determination setInitialStatus on modify { create; }}// Behavior ImplementationCLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS setInitialStatus FOR DETERMINE ON MODIFY IMPORTING keys FOR Travel~setInitialStatus.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD setInitialStatus. " 1. Aktuelle Daten lesen READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" 2. Nur Entities ohne Status aktualisieren MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status CreatedBy CreatedAt ) WITH VALUE #( FOR travel IN lt_travel WHERE ( Status IS INITIAL ) ( %tky = travel-%tky Status = 'O' " Open CreatedBy = cl_abap_context_info=>get_user_name( ) CreatedAt = cl_abap_context_info=>get_system_date( ) ) ) REPORTED DATA(update_reported). ENDMETHOD.
ENDCLASS.Ablauf:
User: CREATE Travel mit Description = 'Geschäftsreise' ↓Framework: Create in Memory ↓Determination: setInitialStatus → Status = 'O' → CreatedBy = sy-uname → CreatedAt = today ↓(Validations werden ausgeführt) ↓Framework: INSERT INTO ztravelBeispiel 2: Gesamtpreis berechnen (on modify)
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ // Bei Änderung von BeginDate oder EndDate → Preis neu berechnen determination calculateTotalPrice on modify { field BeginDate, EndDate; }}METHOD calculateTotalPrice. " Daten lesen READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( TravelId BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel). " Anzahl Tage berechnen DATA(lv_days) = ls_travel-EndDate - ls_travel-BeginDate + 1.
" Basis-Preis pro Tag (z.B. aus Customizing) DATA(lv_price_per_day) = 100. " EUR
" Gesamtpreis DATA(lv_total) = lv_days * lv_price_per_day.
" Rabatt für längere Reisen IF lv_days > 7. lv_total = lv_total * '0.90'. " 10% Rabatt ENDIF.
" Wert setzen MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( TotalPrice ) WITH VALUE #( ( %tky = ls_travel-%tky TotalPrice = lv_total ) ). ENDLOOP.ENDMETHOD.Trigger-Beispiele:
" Bei CREATE ausführendetermination setDefaults on modify { create; }
" Bei UPDATE ausführendetermination recalculate on modify { update; }
" Bei CREATE und UPDATEdetermination calculate on modify { create; update; }
" Nur wenn bestimmte Felder geändert werdendetermination updateDependentFields on modify { field Price, Quantity, Discount;}
" Bei SAVE (kurz vor DB-Commit)determination finalizeData on save { create; update; }Beispiel 3: Abhängige Felder setzen (on save)
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ // Kurz vor Save: Approval-Felder setzen determination setApprovalData on save { update; }}METHOD setApprovalData. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( Status ApprovedBy ApprovedAt ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( ApprovedBy ApprovedAt ) WITH VALUE #( FOR travel IN lt_travel WHERE ( Status = 'A' AND ApprovedBy IS INITIAL ) ( %tky = travel-%tky ApprovedBy = cl_abap_context_info=>get_user_name( ) ApprovedAt = cl_abap_context_info=>get_system_date( ) ) ) REPORTED DATA(update_reported).ENDMETHOD.Validations: Geschäftsregeln prüfen
Wann Validations nutzen?
✅ Perfekt für:
- Pflichtfelder prüfen
- Datumslogik (Enddatum nach Beginn)
- Wertebereichs-Checks (Rabatt ≤ 100%)
- Fremdschlüssel prüfen (Kunde existiert?)
- Komplexe Geschäftsregeln
❌ NICHT für:
- Werte setzen (→ Determination!)
- Berechnungen (→ Determination!)
- Nur Warnings loggen (→ OK, aber nicht als Fehler)
Syntax in Behavior Definition
define behavior for ZI_Travel alias Travel{ // Validation Syntax: validation <MethodName> on save { <TriggerCondition>; }
// Trigger: IMMER "on save" (kurz vor DB-Commit) // TriggerCondition: create, update, field <FieldName>}Beispiel 1: Datumslogik prüfen
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ create; update;
// Validation: Bei Save → Datum prüfen validation validateDates on save { field BeginDate, EndDate; }}// Behavior ImplementationCLASS lhc_travel IMPLEMENTATION.
METHOD validateDates. " 1. Daten lesen READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( TravelId BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" 2. Jede Entity prüfen LOOP AT lt_travel INTO DATA(ls_travel).
" Regel 1: EndDate muss nach BeginDate liegen IF ls_travel-EndDate < ls_travel-BeginDate.
" FAILED füllen (technischer Fehler) APPEND VALUE #( %tky = ls_travel-%tky %element-EndDate = if_abap_behv=>mk-on ) TO failed-travel.
" REPORTED füllen (Fehlermeldung für UI) APPEND VALUE #( %tky = ls_travel-%tky %element-EndDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Enddatum ({ ls_travel-EndDate DATE = USER }) | && |muss nach Beginndatum ({ ls_travel-BeginDate DATE = USER }) liegen| ) ) TO reported-travel. ENDIF.
" Regel 2: BeginDate darf nicht in Vergangenheit liegen IF ls_travel-BeginDate < cl_abap_context_info=>get_system_date( ).
APPEND VALUE #( %tky = ls_travel-%tky %element-BeginDate = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-BeginDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Reisebeginn darf nicht in der Vergangenheit liegen' ) ) TO reported-travel. ENDIF.
ENDLOOP. ENDMETHOD.
ENDCLASS.Was passiert bei Fehler?
User: UPDATE Travel, EndDate = '20250101', BeginDate = '20250115' ↓(Determinations werden ausgeführt) ↓Validation: validateDates → Prüfung: EndDate < BeginDate? → JA! → FAILED-travel gefüllt → REPORTED-travel gefüllt ↓Framework: ROLLBACK (kein INSERT/UPDATE) ↓UI: Fehlermeldung anzeigenBeispiel 2: Fremdschlüssel prüfen
// Behavior Definitiondefine behavior for ZI_Travel alias Travel{ validation validateCustomer on save { field CustomerId; }}METHOD validateCustomer. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( CustomerId ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" Alle CustomerIds sammeln DATA(lt_customer_ids) = VALUE string_table( FOR travel IN lt_travel ( travel-CustomerId ) ).
" Prüfen ob Kunden existieren (via Released API) SELECT Customer FROM I_Customer FOR ALL ENTRIES IN @lt_customer_ids WHERE Customer = @lt_customer_ids-table_line INTO TABLE @DATA(lt_valid_customers).
" Fehler für nicht-existierende Kunden LOOP AT lt_travel INTO DATA(ls_travel). IF NOT line_exists( lt_valid_customers[ table_line = ls_travel-CustomerId ] ).
APPEND VALUE #( %tky = ls_travel-%tky %element-CustomerId = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-CustomerId = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Kunde { ls_travel-CustomerId } existiert nicht| ) ) TO reported-travel.
ENDIF. ENDLOOP.ENDMETHOD.Beispiel 3: Komplexe Geschäftsregel
// Validation: Rabatt nur für Premium-Kundenvalidation validateDiscount on save { field Discount, CustomerId;}METHOD validateDiscount. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( CustomerId Discount TotalPrice ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
" Kundenkategorien laden SELECT Customer, CustomerClassification FROM I_Customer FOR ALL ENTRIES IN @lt_travel WHERE Customer = @lt_travel-CustomerId INTO TABLE @DATA(lt_customers).
LOOP AT lt_travel INTO DATA(ls_travel).
" Regel: > 10% Rabatt nur für Premium-Kunden (Kategorie 'A') IF ls_travel-Discount > 10.
DATA(ls_customer) = VALUE #( lt_customers[ Customer = ls_travel-CustomerId ] OPTIONAL ).
IF ls_customer-CustomerClassification <> 'A'.
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Rabatt über 10% nur für Premium-Kunden (aktuell: { ls_customer-CustomerClassification })| ) ) TO reported-travel.
ENDIF. ENDIF.
" Regel 2: Rabatt max. 50% des Gesamtpreises IF ls_travel-Discount > ( ls_travel-TotalPrice / 2 ).
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %element-Discount = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Rabatt darf maximal 50% des Gesamtpreises betragen' ) ) TO reported-travel.
ENDIF.
ENDLOOP.ENDMETHOD.Timing: on modify vs on save
on modify (nur Determination!)
determination calculate on modify { create; update; }Wann ausgeführt:
- ✅ Sofort nach CREATE/UPDATE Operation
- ✅ BEVOR Validation läuft
- ✅ Mehrfach möglich (bei jedem Modify)
Use Cases:
- Default-Werte sofort setzen
- Felder berechnen die in Validation geprüft werden
- User Feedback (Feld wird sofort gefüllt im UI)
on save (Determination & Validation!)
determination finalize on save { update; }validation validateData on save { field Status; }Wann ausgeführt:
- ✅ Kurz vor DB-Commit
- ✅ NACH allen on modify Determinations
- ✅ Nur 1x pro Save-Zyklus
Use Cases Determination:
- Finale Berechnungen (z.B. Checksummen)
- Audit-Felder (LastChangedBy, LastChangedAt)
- Nummernvergabe aus DB-Nummernkreisen
Use Cases Validation:
- Geschäftsregeln prüfen
- Konsistenz sicherstellen
- Fremdschlüssel validieren
Ausführungsreihenfolge
User Action: CREATE/UPDATE │ ▼┌───────────────────────────────────────┐│ 1. DETERMINATION on modify (create) │ ← Sofort└───────────────┬───────────────────────┘ │ ▼┌───────────────────────────────────────┐│ 2. DETERMINATION on modify (update) │ ← Bei jedem Update└───────────────┬───────────────────────┘ │ ▼┌───────────────────────────────────────┐│ User: COMMIT ENTITIES aufrufen │└───────────────┬───────────────────────┘ │ ▼┌───────────────────────────────────────┐│ 3. DETERMINATION on save │ ← Kurz vor DB└───────────────┬───────────────────────┘ │ ▼┌───────────────────────────────────────┐│ 4. VALIDATION on save │ ← Prüfungen└───────────────┬───────────────────────┘ │ ├─ Fehler? → ROLLBACK, User sieht Fehlermeldung │ ▼ Erfolg┌───────────────────────────────────────┐│ 5. INSERT/UPDATE in Datenbank │ ← Persistent!└───────────────────────────────────────┘Beispiel mit beiden:
define behavior for ZI_Travel alias Travel{ create; update;
// Schritt 1: Sofort Status setzen determination setInitialStatus on modify { create; }
// Schritt 2: Bei Feld-Änderung neu berechnen determination calculateTotal on modify { field BeginDate, EndDate; }
// Schritt 3: Vor Save finale Werte determination setApprovalData on save { update; }
// Schritt 4: Validierungen validation validateDates on save { field BeginDate, EndDate; } validation validateCustomer on save { field CustomerId; }}Fehlermeldungen richtig gestalten
Severity Levels
" ERROR: Blockiert Save%msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Enddatum muss nach Beginndatum liegen')
" WARNING: Erlaubt Save, zeigt Warnung%msg = new_message_with_text( severity = if_abap_behv_message=>severity-warning text = 'Reise ist sehr kurz (< 3 Tage)')
" INFO: Nur Information%msg = new_message_with_text( severity = if_abap_behv_message=>severity-information text = 'Rabatt wurde automatisch angewendet')
" SUCCESS: Positive Bestätigung%msg = new_message_with_text( severity = if_abap_behv_message=>severity-success text = 'Reise erfolgreich genehmigt')Message mit Message Class
" Statt Hardcoded-Text → Message Class nutzenAPPEND VALUE #( %tky = ls_travel-%tky %element-EndDate = if_abap_behv=>mk-on %msg = new_message( id = 'ZTRAVEL_MSG' " Message Class (SE91) number = '001' " Message Number severity = if_abap_behv_message=>severity-error v1 = ls_travel-BeginDate " Platzhalter &1 v2 = ls_travel-EndDate " Platzhalter &2 )) TO reported-travel.
" In SE91: Message ZTRAVEL_MSG/001" Text: "Enddatum &2 muss nach Beginndatum &1 liegen"Mehrere Felder markieren
" Fehler betrifft BeginDate UND EndDateAPPEND VALUE #( %tky = ls_travel-%tky %element-BeginDate = if_abap_behv=>mk-on %element-EndDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Reisezeitraum ungültig' )) TO reported-travel.
" UI markiert BEIDE Felder rotBest Practices
✅ DO: Determinations
" ✅ Werte setzen, nicht prüfenMETHOD setDefaults. MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status Currency ) WITH VALUE #( FOR key IN keys ( %tky = key-%tky Status = 'O' Currency = 'EUR' ) ).ENDMETHOD.
" ✅ on modify für User Feedbackdetermination calculatePrice on modify { field Quantity, UnitPrice; }" → User sieht sofort neuen Preis
" ✅ on save für finale Wertedetermination generateDocumentNumber on save { create; }" → Nummer erst bei finalem Save vergeben✅ DO: Validations
" ✅ Nur prüfen, nicht ändernMETHOD validateAmount. READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( Amount ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel) WHERE Amount <= 0. APPEND VALUE #( %tky = ls_travel-%tky ) TO failed-travel. APPEND VALUE #( %tky = ls_travel-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Betrag muss größer als 0 sein' ) ) TO reported-travel. ENDLOOP.ENDMETHOD.
" ✅ Performance: Bulk-Read statt LoopSELECT Customer FROM I_Customer FOR ALL ENTRIES IN @lt_travel WHERE Customer = @lt_travel-CustomerId INTO TABLE @DATA(lt_valid).
" ✅ Sprechende Fehlermeldungentext = |Kunde { ls_travel-CustomerId } existiert nicht im System|" Statt: "Validierung fehlgeschlagen"❌ DON’T
" ❌ NICHT in Validation ändernMETHOD validateData. " FALSCH: Validation darf NICHT modifizieren! MODIFY ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( Status ) WITH ... " → Kann zu Inkonsistenzen führen!ENDMETHOD.
" ❌ NICHT in Determination prüfen und abbrechenMETHOD setDefaults. IF ls_travel-Amount <= 0. " FALSCH: Determination soll nicht validieren! APPEND ... TO failed-travel. " ❌ Nutze Validation! ENDIF.ENDMETHOD.
" ❌ NICHT on modify für DB-Zugriffedetermination getCustomerData on modify { field CustomerId; }" → Jedes Mal wenn Feld ändert = DB-Call" → Besser: on save (nur 1x vor Commit)
" ❌ NICHT generische Fehlermeldungentext = 'Fehler' " ❌ Nicht hilfreichtext = 'Validation failed' " ❌ Was ist das Problem?" → Konkret: "Enddatum muss nach Beginndatum liegen"Testing
CLASS ltc_determinations DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_env TYPE REF TO if_cds_test_environment. METHODS: setup, teardown, test_set_initial_status FOR TESTING, test_calculate_total FOR TESTING.ENDCLASS.
CLASS ltc_determinations IMPLEMENTATION.
METHOD setup. mo_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_Travel' ). ENDMETHOD.
METHOD test_set_initial_status. " Arrange: Travel ohne Status erstellen MODIFY ENTITIES OF zi_travel ENTITY Travel CREATE FIELDS ( AgencyId CustomerId ) WITH VALUE #( ( %cid = 'T1' AgencyId = '001' CustomerId = '042' ) ) MAPPED DATA(mapped).
COMMIT ENTITIES.
" Act: Determination sollte Status gesetzt haben READ ENTITIES OF zi_travel ENTITY Travel FIELDS ( Status ) WITH VALUE #( ( %cid = 'T1' ) ) RESULT DATA(lt_travel).
" Assert: Status = 'O' cl_abap_unit_assert=>assert_equals( exp = 'O' act = lt_travel[ 1 ]-Status msg = 'Determination sollte Status auf O setzen' ). ENDMETHOD.
METHOD test_calculate_total. " Test für calculation determination " ... (analog zu oben) ENDMETHOD.
METHOD teardown. ROLLBACK ENTITIES. mo_env->destroy( ). ENDMETHOD.
ENDCLASS.
CLASS ltc_validations DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA mo_env TYPE REF TO if_cds_test_environment. METHODS: setup, test_validate_dates_error FOR TESTING, test_validate_dates_success FOR TESTING.ENDCLASS.
CLASS ltc_validations IMPLEMENTATION.
METHOD setup. mo_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_Travel' ). ENDMETHOD.
METHOD test_validate_dates_error. " Arrange: EndDate < BeginDate MODIFY ENTITIES OF zi_travel ENTITY Travel CREATE FIELDS ( BeginDate EndDate ) WITH VALUE #( ( %cid = 'T1' BeginDate = '20250615' EndDate = '20250601' ) ) " FALSCH! FAILED DATA(failed).
COMMIT ENTITIES RESPONSE OF zi_travel FAILED DATA(commit_failed) REPORTED DATA(commit_reported).
" Assert: Fehler erwartet cl_abap_unit_assert=>assert_not_initial( act = commit_failed-travel msg = 'Validation sollte Fehler melden' ).
" Message prüfen cl_abap_unit_assert=>assert_bound( act = commit_reported-travel[ 1 ]-%msg msg = 'Fehlermeldung sollte vorhanden sein' ). ENDMETHOD.
METHOD test_validate_dates_success. " Arrange: Korrekte Daten MODIFY ENTITIES OF zi_travel ENTITY Travel CREATE FIELDS ( BeginDate EndDate ) WITH VALUE #( ( %cid = 'T1' BeginDate = '20250601' EndDate = '20250615' ) ) FAILED DATA(failed).
COMMIT ENTITIES RESPONSE OF zi_travel FAILED DATA(commit_failed).
" Assert: Kein Fehler cl_abap_unit_assert=>assert_initial( act = commit_failed-travel msg = 'Validation sollte erfolgreich sein' ). ENDMETHOD.
ENDCLASS.Wichtige Hinweise / Best Practice
- Determination = Setzen, Validation = Prüfen: Niemals vermischen!
- Timing beachten:
on modifyfür sofortiges Feedback,on savefür finale Operationen - Validation darf NICHT ändern: Nur lesen und FAILED/REPORTED füllen
- Performance: Bulk-Operationen statt Loops mit einzelnen DB-Calls
- Sprechende Messages: Konkret beschreiben, was falsch ist
- %element markieren: Zeigt User genau welches Feld das Problem hat
- IN LOCAL MODE: Immer in Behavior Implementations verwenden
- Severity richtig: ERROR blockiert, WARNING erlaubt Save
- Message Class nutzen: Statt Hardcoded-Texte → Übersetzbarkeit
- Reihenfolge: Determinations → Validations → DB Save
- Testen: Unit Tests für JEDE Determination und Validation schreiben
- Fehler sammeln: Alle Fehler auf einmal melden, nicht beim ersten abbrechen
Weitere Ressourcen
- RAP Basics: /rap-basics/
- EML Guide: /eml-entity-manipulation-language/
- Test Doubles: /test-doubles-mocking/
- RAP Managed vs Unmanaged: /rap-managed-vs-unmanaged/