Die Migration zu ABAP Cloud ist die größte technische Herausforderung für SAP-Kunden im S/4HANA-Zeitalter. Dieser Leitfaden zeigt Ihnen Schritt für Schritt, wie Sie Legacy-ABAP-Code in moderne, cloud-fähige Anwendungen transformieren.
Warum migrieren?
Technische Gründe:
- ✅ Upgrade-Sicherheit: Released APIs brechen nicht bei S/4HANA-Updates
- ✅ Cloud-Readiness: Läuft auf SAP BTP und S/4HANA Cloud
- ✅ Performance: Moderne Patterns (CDS, RAP) nutzen HANA optimal
- ✅ Wartbarkeit: Klare Architektur statt Spaghetti-Code
Business-Gründe:
- 💰 Reduzierte TCO: Weniger Aufwand bei Upgrades (30-50% Einsparung)
- 🚀 Schnellere Innovation: Neue Features ohne Breaking Changes
- ☁️ Cloud-Strategie: Voraussetzung für S/4HANA Cloud (Public/Private)
- 📊 Compliance: SAP deprecates Classic ABAP langfristig
Migrations-Phasen im Überblick
┌────────────────────────────────────────────────────────────┐│ Phase 1: ASSESSMENT (2-4 Wochen) ││ → Custom Code analysieren ││ → Komplexität bewerten ││ → Roadmap erstellen │└────────────┬───────────────────────────────────────────────┘ │ ▼┌────────────────────────────────────────────────────────────┐│ Phase 2: PREPARATION (4-8 Wochen) ││ → Tools einrichten (ATC, Custom Code Migration App) ││ → Team schulen ││ → Development Guidelines definieren │└────────────┬───────────────────────────────────────────────┘ │ ▼┌────────────────────────────────────────────────────────────┐│ Phase 3: REMEDIATION (6-24 Monate) ││ → APIs ersetzen ││ → Refactoring zu RAP ││ → Tests schreiben │└────────────┬───────────────────────────────────────────────┘ │ ▼┌────────────────────────────────────────────────────────────┐│ Phase 4: VALIDATION (2-4 Wochen) ││ → End-to-End Tests ││ → Performance-Vergleich ││ → User Acceptance Tests │└────────────┬───────────────────────────────────────────────┘ │ ▼┌────────────────────────────────────────────────────────────┐│ Phase 5: GOVERNANCE (ongoing) ││ → ATC in CI/CD ││ → Code Reviews ││ → Continuous Improvement │└────────────────────────────────────────────────────────────┘Phase 1: Assessment
Custom Code analysieren
Tool: ABAP Test Cockpit (ATC)
" In Eclipse ADT:" 1. Projekt → Properties → ABAP Development → ATC" 2. Check Variant: S4HANA_READINESS_REMOTE" 3. Run → ATC Check
" Oder im System:" Transaktion: ATC" Check Variant: S4HANA_CLOUD_DEVELOPMENTTypische Findings:
| Kategorie | Beispiel | Aufwand | Priorität |
|---|---|---|---|
| Nicht-freigegebene Tabellen | SELECT FROM but000 | Mittel | Hoch |
| Obsolete Statements | CALL TRANSACTION | Hoch | Hoch |
| Implicit Enhancements | ENHANCEMENT-POINT | Sehr hoch | Kritisch |
| Dynpro/Screens | MODULE status_0100 OUTPUT | Sehr hoch | Mittel |
| Nicht-released FuBas | CALL FUNCTION 'RFC_READ_TABLE' | Niedrig | Mittel |
| DB-Direkt-Zugriff | EXEC SQL | Hoch | Hoch |
Custom Code Migration App (Fiori)
SAP GUI → Fiori Launchpad→ App: "Custom Code Migration" (F2802)
Features:- Dashboard mit Finding-Übersicht- Kategorisierung nach Aufwand- Tracking von Remediation-Status- Empfehlungen für AlternativenPriorisierungskriterien:
Priorität = (Business Impact × Usage Frequency × Technical Risk) / Effort
Beispiel:- Billing Report: Impact=10, Frequency=1000, Risk=8, Effort=40 → Priority = (10 × 1000 × 8) / 40 = 2000 ⭐⭐⭐
- Selten genutzter Report: Impact=2, Frequency=5, Risk=3, Effort=50 → Priority = (2 × 5 × 3) / 50 = 0.6 → Stilllegung prüfen!Phase 2: Preparation
ABAP Cloud Entwicklungsumgebung
" 1. Package mit ABAP Cloud Language Version erstellen" Eclipse ADT:" → New ABAP Package: Z_CLOUD_TRAVEL" → Properties → ABAP Language Version: "ABAP for Cloud Development"
" 2. Alle Objekte in diesem Package sind automatisch Cloud-konform!" → Compiler blockiert nicht-released APIs" → Nur ABAP Cloud Syntax erlaubtWrapper-Pattern einrichten
" Zentraler Wrapper für Legacy-Zugriffe" (während Migration schrittweise ersetzen)
INTERFACE zif_bp_facade. METHODS: get_business_partner IMPORTING iv_partner TYPE bu_partner RETURNING VALUE(rs_partner) TYPE i_businesspartner RAISING cx_static_check.ENDINTERFACE.
CLASS zcl_bp_facade DEFINITION PUBLIC CREATE PRIVATE. PUBLIC SECTION. INTERFACES zif_bp_facade. CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zcl_bp_facade.
PRIVATE SECTION. CLASS-DATA go_instance TYPE REF TO zcl_bp_facade.ENDCLASS.
CLASS zcl_bp_facade IMPLEMENTATION.
METHOD get_instance. IF go_instance IS NOT BOUND. CREATE OBJECT go_instance. ENDIF. ro_instance = go_instance. ENDMETHOD.
METHOD zif_bp_facade~get_business_partner. " Heute: Released API SELECT SINGLE * FROM i_businesspartner WHERE businesspartner = @iv_partner INTO @rs_partner.
IF sy-subrc <> 0. " Falls API nicht reicht: Temporär Legacy-Zugriff " TODO: SAP um API-Enhancement bitten (Influence Request) " SELECT SINGLE * FROM but000 ... " ← Langfristig entfernen! RAISE EXCEPTION TYPE zcx_bp_not_found. ENDIF. ENDMETHOD.
ENDCLASS.
" In Legacy-Code überall:DATA(ls_bp) = zcl_bp_facade=>get_instance( )->zif_bp_facade~get_business_partner( lv_partner )." → Nur 1 Stelle anpassen wenn SAP API ändert!Phase 3: Remediation
Strategie 1: API-Ersetzung (Quick Wins)
Beispiel 1: Tabellen → CDS Views
" ❌ Vorher: Nicht-released TabelleSELECT kunnr, name1, ort01, land1 FROM kna1 WHERE land1 = 'DE' INTO TABLE @DATA(lt_customers).
" ✅ Nachher: Released CDS ViewSELECT customernumber, customername, cityname, country FROM i_customer WHERE country = 'DE' INTO TABLE @DATA(lt_customers).API finden:
- SAP API Business Hub: api.sap.com
- Filter: “API Type” → “ABAP Cloud”
- Suche nach Domain (z.B. “Customer”, “Sales Order”)
Beispiel 2: Funktionsbausteine → Klassen
" ❌ Vorher: Nicht-released FunctionCALL FUNCTION 'BAPI_MATERIAL_GET_DETAIL' EXPORTING material = lv_matnr IMPORTING material_general_data = ls_data.
" ✅ Nachher: Released API (falls verfügbar)SELECT SINGLE * FROM i_product WHERE product = @lv_matnr INTO @DATA(ls_product).
" ✅ Alternative: Released Wrapper-KlasseTRY. DATA(lo_material) = cl_md_bp_material=>get_instance( lv_matnr ). DATA(ls_data) = lo_material->get_data( ). CATCH cx_md_bp_material INTO DATA(lx_mat). " Error handlingENDTRY.Beispiel 3: CALL TRANSACTION → RAP/Fiori
" ❌ Vorher: Dynpro via CALL TRANSACTIONCALL TRANSACTION 'VA03' WITH PARAMETERS p_vbeln = lv_order AND SKIP FIRST SCREEN.
" ✅ Nachher: RAP Business Object + Fiori App" → UI wird Fiori Elements, keine Transaction Calls mehr
" Backend: RAP Intent-based Navigation" (wenn nötig, ansonsten direkter BO-Zugriff via EML)Strategie 2: Reports zu RAP Query migrieren
Beispiel: ALV Report → RAP Query + Fiori
Vorher: Classic Report (200+ Zeilen)
" ❌ salesorder_report.prog.abapREPORT zsalesorder_report.
TABLES: vbak, vbap, kna1.
SELECT-OPTIONS: s_vbeln FOR vbak-vbeln, s_erdat FOR vbak-erdat, s_kunnr FOR vbak-kunnr.
START-OF-SELECTION. SELECT v~vbeln, v~erdat, v~kunnr, k~name1, v~netwr FROM vbak AS v INNER JOIN kna1 AS k ON v~kunnr = k~kunnr WHERE v~vbeln IN @s_vbeln AND v~erdat IN @s_erdat AND v~kunnr IN @s_kunnr INTO TABLE @DATA(lt_orders).
" ALV anzeigen (50 Zeilen Code für ALV-Setup...) " ...Nachher: RAP Query (10 Zeilen + UI auto-generiert!)
-- 1. CDS View mit Parameters@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Sales Order Report'
@Metadata.allowExtensions: true@UI.headerInfo.typeName: 'Sales Order'
define view entity ZC_SalesOrderReport with parameters @Consumption.defaultValue: '' @EndUserText.label: 'Sales Order' p_SalesOrder : vbeln_va,
@Consumption.defaultValue: '' @EndUserText.label: 'Customer' p_Customer : kunnr
as select from I_SalesOrder as Order association [0..1] to I_Customer as _Customer on Order.SoldToParty = _Customer.Customer{ @UI.lineItem: [{ position: 10 }] @UI.selectionField: [{ position: 10 }] key Order.SalesOrder,
@UI.lineItem: [{ position: 20 }] Order.SalesOrderDate,
@UI.lineItem: [{ position: 30 }] @UI.selectionField: [{ position: 20 }] Order.SoldToParty,
@UI.lineItem: [{ position: 40 }] _Customer.CustomerName,
@UI.lineItem: [{ position: 50 }] @Semantics.amount.currencyCode: 'TransactionCurrency' Order.TotalNetAmount,
Order.TransactionCurrency}where Order.SalesOrder like $parameters.p_SalesOrder and Order.SoldToParty like $parameters.p_Customer-- 2. Service Definition@EndUserText.label: 'Sales Order Report Service'define service ZUI_SalesOrderReport { expose ZC_SalesOrderReport as SalesOrder;}3. Service Binding (OData V4 UI)→ In ADT: New Service Binding→ Binding Type: OData V4 - UI→ Publish
4. Fiori Preview starten→ UI ist AUTOMATISCH generiert (Fiori Elements)!→ Keine ALV-Programmierung mehr nötigErgebnis:
- 🟢 200 Zeilen → 30 Zeilen (85% weniger Code)
- 🟢 UI auto-generiert (Fiori Elements statt ALV)
- 🟢 Responsive (Mobile, Tablet, Desktop)
- 🟢 Filter/Search/Export out-of-the-box
- 🟢 Cloud-fähig
Strategie 3: Dynpro → RAP Fiori App
Beispiel: Transaktionscode VA03 → RAP BO
Vorher: Dynpro-Transaktion (1000+ Zeilen)
" ❌ Z_SALESORDER_EDIT" - Dynpro Screens (100, 200, 300...)" - PAI/PBO Modules" - Table Controls" - OK-Code Handling" → 1000+ Zeilen nicht-wartbarer CodeNachher: RAP Business Object (100 Zeilen + UI auto)
-- 1. CDS Root View@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Sales Order - Editable'
define root view entity ZI_SalesOrderEdit as select from I_SalesOrder composition [0..*] of ZI_SalesOrderItemEdit as _Items{ key SalesOrder, SalesOrderType, SalesOrganization, SoldToParty, CreationDate, TotalNetAmount, TransactionCurrency, OverallSDProcessStatus,
_Items}-- 2. Behavior Definitionmanaged implementation in class zbp_i_salesorderedit unique;strict ( 2 );with draft;
define behavior for ZI_SalesOrderEdit alias SalesOrderpersistent table I_SalesOrder // Oder eigene Tabelledraft table zsalesorder_draftlock mastertotal etag LastChangedAtauthorization master ( instance ){ // CRUD create; update; delete;
// Fields field ( readonly ) SalesOrder; field ( mandatory ) SoldToParty, SalesOrderType;
// Actions action release result [1] $self; action block result [1] $self;
// Draft draft action Edit; draft action Activate; draft action Discard; draft action Resume; draft determine action Prepare;
// Composition association _Items { create; with draft; }}
define behavior for ZI_SalesOrderItemEdit alias Itempersistent table I_SalesOrderItemdraft table zsalesorderitem_draftlock dependent by _SalesOrderauthorization dependent by _SalesOrder{ update; delete;
field ( readonly ) SalesOrder, SalesOrderItem; field ( mandatory ) Material, RequestedQuantity;
association _SalesOrder { with draft; }}-- 3. Projection View (UI-spezifisch)@EndUserText.label: 'Sales Order - Projection'@AccessControl.authorizationCheck: #CHECK@Metadata.allowExtensions: true
@UI.headerInfo: { typeName: 'Sales Order', typeNamePlural: 'Sales Orders', title: { value: 'SalesOrder' }}
define root view entity ZC_SalesOrderEdit provider contract transactional_query as projection on ZI_SalesOrderEdit{ @UI.facet: [ { position: 10, type: #IDENTIFICATION_REFERENCE, label: 'General' }, { position: 20, type: #LINEITEM_REFERENCE, label: 'Items', targetElement: '_Items' } ]
@UI.identification: [{ position: 10 }] @UI.lineItem: [{ position: 10 }] key SalesOrder,
@UI.identification: [{ position: 20 }] @UI.lineItem: [{ position: 20 }] SalesOrderType,
@UI.identification: [{ position: 30 }] @UI.lineItem: [{ position: 30 }] SoldToParty,
@UI.identification: [{ position: 40 }] TotalNetAmount,
TransactionCurrency,
_Items : redirected to composition child ZC_SalesOrderItemEdit}-- 4. Service Definition + Binding (wie vorher)-- → Fiori Elements UI automatisch!Ergebnis:
- 🟢 1000 Zeilen Dynpro → 100 Zeilen RAP (90% weniger)
- 🟢 UI responsive (statt Fixed-Screens)
- 🟢 Draft-Funktionalität (Zwischenspeichern)
- 🟢 Validation/Actions klar strukturiert
- 🟢 Wartbar und testbar
Strategie 4: Enhancements → RAP Business Events
Beispiel: Implicit Enhancement → Event
Vorher: Enhancement Point
" ❌ SAP-Standardprogramm (z.B. SAPMV45A)ENHANCEMENT-SECTION zenhancement_vbap. ENHANCEMENT 1 zorder_validation. " Globaler Zugriff auf SAP-Variablen (fragil!) IF vbap-matnr = 'BLOCKED_MAT'. MESSAGE 'Material gesperrt' TYPE 'E'. ENDIF. ENDENHANCEMENT.END-ENHANCEMENT-SECTION.Nachher: RAP Business Event
-- 1. Event in Behavior Definitiondefine behavior for ZI_SalesOrder alias SalesOrder{ // ... event MaterialChecked parameter ZA_MaterialCheckParam;}-- 2. Event im Behavior Implementation auslösenMETHOD validateMaterial. READ ENTITIES OF zi_salesorder IN LOCAL MODE ENTITY SalesOrderItem FIELDS ( Material ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_items).
LOOP AT lt_items INTO DATA(ls_item). " Prüfung IF ls_item-Material = 'BLOCKED_MAT'. " Event auslösen RAISE ENTITY EVENT zi_salesorder~MaterialChecked FROM VALUE #( ( %key-SalesOrder = ls_item-SalesOrder %param-Material = ls_item-Material %param-BlockReason = 'Quality issue' ) ).
" Fehler melden APPEND VALUE #( %tky = ls_item-%tky ) TO failed-salesorderitem. APPEND VALUE #( %tky = ls_item-%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Material gesperrt' ) ) TO reported-salesorderitem. ENDIF. ENDLOOP.ENDMETHOD.-- 3. Event Consumer (optional, für Side-Effects)CLASS zcl_material_event_handler DEFINITION PUBLIC. PUBLIC SECTION. INTERFACES if_rap_entity_event_subscriber.ENDCLASS.
CLASS zcl_material_event_handler IMPLEMENTATION. METHOD if_rap_entity_event_subscriber~on_business_event. " Reagiere auf MaterialChecked Event CASE event_key. WHEN 'MaterialChecked'. " Z.B. Email senden, externes System benachrichtigen DATA(lv_material) = event_data[ 1 ]-%param-Material. cl_email_sender=>send_alert( subject = |Material { lv_material } blockiert| recipient = 'quality@company.com' ). ENDCASE. ENDMETHOD.ENDCLASS.Vorteile:
- ✅ Kein Zugriff auf SAP-Interna
- ✅ Klare Event-basierte Architektur
- ✅ Entkoppelt (Consumer optional)
- ✅ Testbar
Phase 4: Validation
Testing-Strategie
" 1. Unit Tests mit Test Doubles (siehe /test-doubles-mocking/)CLASS ltc_salesorder 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_validate_material FOR TESTING.ENDCLASS.
CLASS ltc_salesorder IMPLEMENTATION. METHOD setup. mo_env = cl_cds_test_environment=>create_for_multiple_cds( i_for_entities = VALUE #( ( i_for_entity = 'ZI_SalesOrder' ) ( i_for_entity = 'ZI_SalesOrderItem' ) ) ). ENDMETHOD.
METHOD test_validate_material. " Arrange: Testdaten mo_env->insert_test_data( i_data = VALUE zi_salesorder( ( SalesOrder = '0001' ) ) ).
" Act: Validation ausführen MODIFY ENTITIES OF zi_salesorder ENTITY SalesOrderItem CREATE FIELDS ( Material Quantity ) WITH VALUE #( ( SalesOrder = '0001' Material = 'BLOCKED_MAT' Quantity = 10 ) ) FAILED DATA(failed).
" Assert: Fehler erwartet cl_abap_unit_assert=>assert_not_initial( failed-salesorderitem ). ENDMETHOD.
METHOD teardown. mo_env->destroy( ). ENDMETHOD.ENDCLASS." 2. Integration Tests (E2E via OData)CLASS ltc_integration DEFINITION FINAL FOR TESTING DURATION MEDIUM RISK LEVEL DANGEROUS.
PRIVATE SECTION. DATA mo_client TYPE REF TO if_web_http_client. METHODS: test_create_order_via_odata FOR TESTING.ENDCLASS.
CLASS ltc_integration IMPLEMENTATION. METHOD test_create_order_via_odata. " OData POST simulieren mo_client = cl_web_http_client_manager=>create_by_http_destination( ... ).
DATA(lv_payload) = `{"SalesOrderType":"OR","SoldToParty":"0001"}`.
DATA(lo_request) = mo_client->get_http_request( ). lo_request->set_text( lv_payload ). lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ).
DATA(lo_response) = mo_client->execute( if_web_http_client=>post ).
" Assert: Status 201 Created cl_abap_unit_assert=>assert_equals( exp = 201 act = lo_response->get_status( )-code ). ENDMETHOD.ENDCLASS.Performance-Vergleich
" Benchmark: Alt vs. NeuREPORT z_performance_benchmark.
DATA: lv_start TYPE timestampl, lv_end TYPE timestampl.
" Alt: SELECT FROM vbakGET TIME STAMP FIELD lv_start.SELECT * FROM vbak INTO TABLE @DATA(lt_vbak) UP TO 10000 ROWS.GET TIME STAMP FIELD lv_end.DATA(lv_time_old) = cl_abap_tstmp=>subtract( tstmp1 = lv_end tstmp2 = lv_start ).
" Neu: SELECT FROM I_SalesOrder (CDS View)GET TIME STAMP FIELD lv_start.SELECT * FROM i_salesorder INTO TABLE @DATA(lt_orders) UP TO 10000 ROWS.GET TIME STAMP FIELD lv_end.DATA(lv_time_new) = cl_abap_tstmp=>subtract( tstmp1 = lv_end tstmp2 = lv_start ).
WRITE: / 'Alt:', lv_time_old, 'Sekunden'.WRITE: / 'Neu:', lv_time_new, 'Sekunden'.WRITE: / 'Speedup:', lv_time_old / lv_time_new, 'x'.Phase 5: Governance
CI/CD Integration
# Azure DevOps Pipelinetrigger: branches: include: - main - develop
stages: - stage: Build jobs: - job: ABAP_ATC_Check steps: - task: ABAP_Unit_Tests@1 inputs: sapSystem: 'S4D' package: 'Z_CLOUD_*'
- task: ABAP_ATC@1 inputs: checkVariant: 'ABAP_CLOUD_READINESS' failOnErrors: true maxFindings: 0 # Keine Findings erlaubt!
- task: Code_Coverage@1 inputs: minimumCoverage: 80 # 80% Coverage PflichtPull Request Checklist
## ABAP Cloud PR Checklist
Vor Merge prüfen:
### Technisch- [ ] ATC-Check grün (ABAP_CLOUD_READINESS)- [ ] Unit Tests vorhanden (Coverage ≥ 80%)- [ ] Nur Released APIs verwendet- [ ] Keine Implicit Enhancements- [ ] ABAP Cloud Language Version
### Dokumentation- [ ] Änderungen im Changelog dokumentiert- [ ] API-Dokumentation aktualisiert- [ ] Migrationsnoten für andere Teams
### Testing- [ ] Unit Tests erfolgreich- [ ] Integration Tests erfolgreich- [ ] Manuelle Tests durchgeführt
### Review- [ ] Code Review durch 2. Entwickler- [ ] Architektur Review (bei größeren Changes)Migrations-Fallstricke (und wie Sie sie vermeiden)
Fallstrick 1: “Big Bang” Migration
❌ Fehler: Alle Custom-Objekte auf einmal migrieren
✅ Lösung: Inkrementell migrieren
Woche 1-4: 10% (Quick Wins: API-Ersetzungen)Woche 5-12: 30% (Reports → RAP Queries)Woche 13-24: 40% (Dynpro → RAP Fiori)Woche 25+: 20% (Komplexe Legacy-Integration)Fallstrick 2: API-Lücken ignorieren
❌ Fehler: Wenn keine Released API existiert → aufgeben
✅ Lösung: 3-Stufen-Ansatz
- Wrapper erstellen (temporär Legacy-Zugriff kapseln)
- SAP Influence Request (api.sap.com → Request Enhancement)
- Workaround dokumentieren (für spätere Ablösung)
Fallstrick 3: Testing vernachlässigen
❌ Fehler: “Funktioniert doch, brauche keine Tests”
✅ Lösung: Test-first Migration
" 1. Erst Legacy-Verhalten als Test dokumentierenMETHOD test_legacy_behavior. " Act: Legacy-Code ausführen CALL FUNCTION 'Z_LEGACY_CALC' ...
" Assert: Ergebnis dokumentieren cl_abap_unit_assert=>assert_equals( exp = 42 act = lv_result ).ENDMETHOD.
" 2. DANN migrieren" 3. Test MUSS weiterhin grün sein!Wichtige Hinweise / Best Practice
- Inkrementell migrieren: Nicht alles auf einmal, sondern Schritt für Schritt
- 80/20-Regel: 20% des Codes verursachen 80% der Probleme – priorisieren!
- Quick Wins first: API-Ersetzungen sind schnell → früh umsetzen für Motivation
- Wrapper-Pattern: Legacy-Code kapseln während Migration
- SAP Influence nutzen: Fehlende APIs bei SAP anfordern (influence.sap.com)
- Training: Team schulen BEVOR Migration startet (RAP, CDS, Fiori)
- Realistische Timeline: 2-5 Jahre für große Custom-Code-Basis normal
- Sunset statt Migrate: Alte, selten genutzte Programme stilllegen
- Test Doubles: Unit Tests mit CDS Test Environment (siehe Test Doubles)
- Clean Core: Migration = Clean Core implementieren (siehe Clean Core Guide)
- Continuous: Migration ist kein Projekt, sondern Dauerzustand (neue Entwicklungen = ABAP Cloud)
- Dokumentieren: Jede Abweichung begründen (z.B. “API fehlt, SAP-Ticket: 123456”)
Weitere Ressourcen
- Clean Core Strategie: /clean-core-strategie/
- ABAP Cloud: /abap-cloud-definition/
- RAP Basics: /rap-basics/
- EML Guide: /eml-entity-manipulation-language/
- RAP Managed vs Unmanaged: /rap-managed-vs-unmanaged/