La migration vers ABAP Cloud est le plus grand defi technique pour les clients SAP a l’ere S/4HANA. Ce guide vous montre etape par etape comment transformer le code ABAP legacy en applications modernes compatibles cloud.
Pourquoi migrer ?
Raisons techniques :
- Les APIs publiees ne cassent pas lors des mises a jour S/4HANA
- Pret pour le cloud : Fonctionne sur SAP BTP et S/4HANA Cloud
- Performance : Les patterns modernes (CDS, RAP) utilisent HANA de maniere optimale
- Maintenabilite : Architecture claire au lieu de code spaghetti
Raisons business :
- TCO reduit : Moins d’efforts lors des mises a niveau (30-50% d’economie)
- Innovation plus rapide : Nouvelles fonctionnalites sans breaking changes
- Strategie cloud : Prerequis pour S/4HANA Cloud (Public/Private)
- Conformite : SAP deprecie Classic ABAP a long terme
Apercu des phases de migration
+------------------------------------------------------------+| Phase 1 : EVALUATION (2-4 semaines) || -> Analyser le code custom || -> Evaluer la complexite || -> Creer la feuille de route |+------------+-----------------------------------------------+ | v+------------------------------------------------------------+| Phase 2 : PREPARATION (4-8 semaines) || -> Configurer les outils (ATC, Custom Code Migration App) || -> Former l'equipe || -> Definir les directives de developpement |+------------+-----------------------------------------------+ | v+------------------------------------------------------------+| Phase 3 : REMEDIATION (6-24 mois) || -> Remplacer les APIs || -> Refactoring vers RAP || -> Ecrire les tests |+------------+-----------------------------------------------+ | v+------------------------------------------------------------+| Phase 4 : VALIDATION (2-4 semaines) || -> Tests de bout en bout || -> Comparaison des performances || -> Tests d'acceptation utilisateur |+------------+-----------------------------------------------+ | v+------------------------------------------------------------+| Phase 5 : GOUVERNANCE (continu) || -> ATC dans CI/CD || -> Revues de code || -> Amelioration continue |+------------------------------------------------------------+Phase 1 : Evaluation
Analyser le code custom
Outil : ABAP Test Cockpit (ATC)
" Dans Eclipse ADT :" 1. Projet -> Properties -> ABAP Development -> ATC" 2. Check Variant : S4HANA_READINESS_REMOTE" 3. Run -> ATC Check
" Ou dans le systeme :" Transaction : ATC" Check Variant : S4HANA_CLOUD_DEVELOPMENTResultats typiques :
| Categorie | Exemple | Effort | Priorite |
|---|---|---|---|
| Tables non publiees | SELECT FROM but000 | Moyen | Haute |
| Statements obsoletes | CALL TRANSACTION | Eleve | Haute |
| Implicit Enhancements | ENHANCEMENT-POINT | Tres eleve | Critique |
| Dynpro/Ecrans | MODULE status_0100 OUTPUT | Tres eleve | Moyenne |
| FuBas non publies | CALL FUNCTION 'RFC_READ_TABLE' | Faible | Moyenne |
| Acces DB direct | EXEC SQL | Eleve | Haute |
Custom Code Migration App (Fiori)
SAP GUI -> Fiori Launchpad-> App : "Custom Code Migration" (F2802)
Fonctionnalites :- Tableau de bord avec apercu des resultats- Categorisation par effort- Suivi du statut de remediation- Recommandations d'alternativesCriteres de priorisation :
Priorite = (Impact Business x Frequence d'utilisation x Risque Technique) / Effort
Exemple :- Rapport facturation : Impact=10, Frequence=1000, Risque=8, Effort=40 -> Priorite = (10 x 1000 x 8) / 40 = 2000
- Rapport rarement utilise : Impact=2, Frequence=5, Risque=3, Effort=50 -> Priorite = (2 x 5 x 3) / 50 = 0.6 -> Verifier l'arret !Phase 2 : Preparation
Environnement de developpement ABAP Cloud
" 1. Creer un package avec la version de langage ABAP Cloud" Eclipse ADT :" -> New ABAP Package : Z_CLOUD_TRAVEL" -> Properties -> ABAP Language Version : "ABAP for Cloud Development"
" 2. Tous les objets dans ce package sont automatiquement conformes au cloud !" -> Le compilateur bloque les APIs non publiees" -> Seule la syntaxe ABAP Cloud est autoriseeConfigurer le pattern Wrapper
" Wrapper central pour les acces legacy" (remplacer progressivement pendant la migration)
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. " Aujourd'hui : API publiee SELECT SINGLE * FROM i_businesspartner WHERE businesspartner = @iv_partner INTO @rs_partner.
IF sy-subrc <> 0. " Si l'API ne suffit pas : Acces legacy temporaire " TODO : Demander une amelioration de l'API a SAP (Influence Request) " SELECT SINGLE * FROM but000 ... " <- A supprimer a long terme ! RAISE EXCEPTION TYPE zcx_bp_not_found. ENDIF. ENDMETHOD.
ENDCLASS.
" Dans le code legacy partout :DATA(ls_bp) = zcl_bp_facade=>get_instance( )->zif_bp_facade~get_business_partner( lv_partner )." -> Un seul endroit a modifier si SAP change l'API !Phase 3 : Remediation
Strategie 1 : Remplacement d’API (Quick Wins)
Exemple 1 : Tables -> CDS Views
" Avant : Table non publieeSELECT kunnr, name1, ort01, land1 FROM kna1 WHERE land1 = 'DE" INTO TABLE @DATA(lt_customers).
" Apres : CDS View publieeSELECT customernumber, customername, cityname, country FROM i_customer WHERE country = 'DE" INTO TABLE @DATA(lt_customers).Trouver l’API :
- SAP API Business Hub : api.sap.com
- Filtre : “API Type” -> “ABAP Cloud”
- Recherche par domaine (par ex. “Customer”, “Sales Order”)
Exemple 2 : Modules fonctions -> Classes
" Avant : Fonction non publieeCALL FUNCTION 'BAPI_MATERIAL_GET_DETAIL" EXPORTING material = lv_matnr IMPORTING material_general_data = ls_data.
" Apres : API publiee (si disponible)SELECT SINGLE * FROM i_product WHERE product = @lv_matnr INTO @DATA(ls_product).
" Alternative : Classe wrapper publieeTRY. 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). " Gestion des erreursENDTRY.Exemple 3 : CALL TRANSACTION -> RAP/Fiori
" Avant : Dynpro via CALL TRANSACTIONCALL TRANSACTION 'VA03" WITH PARAMETERS p_vbeln = lv_order AND SKIP FIRST SCREEN.
" Apres : RAP Business Object + App Fiori" -> L'UI devient Fiori Elements, plus d'appels Transaction
" Backend : Navigation RAP basee sur Intent" (si necessaire, sinon acces direct BO via EML)Strategie 2 : Migrer les reports vers RAP Query
Exemple : ALV Report -> RAP Query + Fiori
Avant : Report classique (200+ lignes)
" 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).
" Afficher ALV (50 lignes de code pour la configuration ALV...) " ...Apres : RAP Query (10 lignes + UI auto-generee !)
-- 1. CDS View avec parametres@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)-> Dans ADT : New Service Binding-> Binding Type : OData V4 - UI-> Publish
4. Demarrer Fiori Preview-> L'UI est generee AUTOMATIQUEMENT (Fiori Elements) !-> Plus de programmation ALV necessaireResultat :
- 200 lignes -> 30 lignes (85% de code en moins)
- UI auto-generee (Fiori Elements au lieu d’ALV)
- Responsive (Mobile, Tablette, Desktop)
- Filtre/Recherche/Export pret a l’emploi
- Compatible cloud
Strategie 3 : Dynpro -> RAP Fiori App
Exemple : Code transaction VA03 -> RAP BO
Avant : Transaction Dynpro (1000+ lignes)
" Z_SALESORDER_EDIT" - Ecrans Dynpro (100, 200, 300...)" - Modules PAI/PBO" - Controles de table" - Gestion OK-Code" -> 1000+ lignes de code non maintenableApres : RAP Business Object (100 lignes + 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 // Ou table personnaliseedraft table zsalesorder_draftlock mastertotal etag LastChangedAtauthorization master ( instance ){ // CRUD create; update; delete;
// Champs 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 (specifique UI)@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 (comme avant)-- -> UI Fiori Elements automatique !Resultat :
- 1000 lignes Dynpro -> 100 lignes RAP (90% de moins)
- UI responsive (au lieu d’ecrans fixes)
- Fonctionnalite Draft (sauvegarde intermediaire)
- Validation/Actions clairement structurees
- Maintenable et testable
Strategie 4 : Enhancements -> RAP Business Events
Exemple : Implicit Enhancement -> Event
Avant : Enhancement Point
" Programme standard SAP (par ex. SAPMV45A)ENHANCEMENT-SECTION zenhancement_vbap. ENHANCEMENT 1 zorder_validation. " Acces global aux variables SAP (fragile !) IF vbap-matnr = 'BLOCKED_MAT'. MESSAGE 'Article bloque' TYPE 'E'. ENDIF. ENDENHANCEMENT.END-ENHANCEMENT-SECTION.Apres : RAP Business Event
-- 1. Event dans Behavior Definitiondefine behavior for ZI_SalesOrder alias SalesOrder{ // ... event MaterialChecked parameter ZA_MaterialCheckParam;}-- 2. Declenchement de l'event dans Behavior ImplementationMETHOD 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). " Verification IF ls_item-Material = 'BLOCKED_MAT'. " Declencher l'event RAISE ENTITY EVENT zi_salesorder~MaterialChecked FROM VALUE #( ( %key-SalesOrder = ls_item-SalesOrder %param-Material = ls_item-Material %param-BlockReason = 'Quality issue' ) ).
" Signaler l'erreur 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 = 'Article bloque" ) ) TO reported-salesorderitem. ENDIF. ENDLOOP.ENDMETHOD.-- 3. Event Consumer (optionnel, pour les effets secondaires)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. " Reagir a l'event MaterialChecked CASE event_key. WHEN 'MaterialChecked'. " Par ex. envoyer un email, notifier un systeme externe DATA(lv_material) = event_data[ 1 ]-%param-Material. cl_email_sender=>send_alert( subject = |Article { lv_material } bloque| ). ENDCASE. ENDMETHOD.ENDCLASS.Avantages :
- Pas d’acces aux internals SAP
- Architecture claire basee sur les events
- Decoupage (Consumer optionnel)
- Testable
Phase 4 : Validation
Strategie de test
" 1. Tests unitaires avec Test Doubles (voir /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 : Donnees de test mo_env->insert_test_data( i_data = VALUE zi_salesorder( ( SalesOrder = '0001' ) ) ).
" Act : Executer la validation MODIFY ENTITIES OF zi_salesorder ENTITY SalesOrderItem CREATE FIELDS ( Material Quantity ) WITH VALUE #( ( SalesOrder = '0001' Material = 'BLOCKED_MAT' Quantity = 10 ) ) FAILED DATA(failed).
" Assert : Erreur attendue cl_abap_unit_assert=>assert_not_initial( failed-salesorderitem ). ENDMETHOD.
METHOD teardown. mo_env->destroy( ). ENDMETHOD.ENDCLASS." 2. Tests d'integration (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. " Simuler POST OData 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.Comparaison des performances
" Benchmark : Ancien vs. NouveauREPORT z_performance_benchmark.
DATA: lv_start TYPE timestampl, lv_end TYPE timestampl.
" Ancien : 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 ).
" Nouveau : 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: / 'Ancien:', lv_time_old, 'Secondes'.WRITE: / 'Nouveau:', lv_time_new, 'Secondes'.WRITE: / 'Acceleration:', lv_time_old / lv_time_new, 'x'.Phase 5 : Gouvernance
Integration CI/CD
# Pipeline Azure DevOpstrigger: 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 # Aucune erreur autorisee !
- task: Code_Coverage@1 inputs: minimumCoverage: 80 # 80% de couverture obligatoireChecklist Pull Request
## Checklist PR ABAP Cloud
A verifier avant le merge :
### Technique- [ ] Verification ATC verte (ABAP_CLOUD_READINESS)- [ ] Tests unitaires presents (Couverture >= 80%)- [ ] Seules des APIs publiees utilisees- [ ] Pas d'Implicit Enhancements- [ ] Version de langage ABAP Cloud
### Documentation- [ ] Modifications documentees dans le changelog- [ ] Documentation API mise a jour- [ ] Notes de migration pour les autres equipes
### Tests- [ ] Tests unitaires reussis- [ ] Tests d'integration reussis- [ ] Tests manuels effectues
### Revue- [ ] Revue de code par un 2e developpeur- [ ] Revue d'architecture (pour les changements majeurs)Pieges de migration (et comment les eviter)
Piege 1 : Migration “Big Bang”
Erreur : Migrer tous les objets custom en une fois
Solution : Migrer incrementalement
Semaine 1-4 : 10% (Quick Wins : Remplacements d'API)Semaine 5-12 : 30% (Reports -> RAP Queries)Semaine 13-24 : 40% (Dynpro -> RAP Fiori)Semaine 25+ : 20% (Integration legacy complexe)Piege 2 : Ignorer les lacunes d’API
Erreur : Si aucune API publiee n’existe -> abandonner
Solution : Approche en 3 etapes
- Creer un wrapper (encapsuler temporairement l’acces legacy)
- SAP Influence Request (api.sap.com -> Request Enhancement)
- Documenter le contournement (pour remplacement ulterieur)
Piege 3 : Negliger les tests
Erreur : “Ca marche, je n’ai pas besoin de tests”
Solution : Migration test-first
" 1. D'abord documenter le comportement legacy comme testMETHOD test_legacy_behavior. " Act : Executer le code legacy CALL FUNCTION 'Z_LEGACY_CALC' ...
" Assert : Documenter le resultat cl_abap_unit_assert=>assert_equals( exp = 42 act = lv_result ).ENDMETHOD.
" 2. ENSUITE migrer" 3. Le test DOIT toujours etre vert !Remarques importantes / Bonnes pratiques
- Migrer incrementalement : Pas tout en une fois, mais etape par etape
- Regle 80/20 : 20% du code cause 80% des problemes - prioriser !
- Quick Wins d’abord : Les remplacements d’API sont rapides -> implementer tot pour la motivation
- Pattern Wrapper : Encapsuler le code legacy pendant la migration
- Utiliser SAP Influence : Demander les APIs manquantes a SAP (influence.sap.com)
- Formation : Former l’equipe AVANT de commencer la migration (RAP, CDS, Fiori)
- Timeline realiste : 2-5 ans pour une grande base de code custom est normal
- Arret au lieu de migration : Arreter les anciens programmes rarement utilises
- Test Doubles : Tests unitaires avec CDS Test Environment (voir Test Doubles)
- Clean Core : Migration = implementer Clean Core (voir Clean Core Guide)
- Continu : La migration n’est pas un projet, mais un etat permanent (nouveaux developpements = ABAP Cloud)
- Documenter : Justifier chaque deviation (par ex. “API manquante, Ticket SAP : 123456”)
Ressources supplementaires
- Strategie Clean Core : /clean-core-strategie/
- ABAP Cloud : /abap-cloud-definition/
- RAP Basics : /rap-basics/
- Guide EML : /eml-entity-manipulation-language/
- RAP Managed vs Unmanaged : /rap-managed-vs-unmanaged/