Migration de Classic ABAP vers ABAP Cloud : Le guide pratique

Catégorie
General
Publié
Auteur
Johannes

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_DEVELOPMENT

Resultats typiques :

CategorieExempleEffortPriorite
Tables non publieesSELECT FROM but000MoyenHaute
Statements obsoletesCALL TRANSACTIONEleveHaute
Implicit EnhancementsENHANCEMENT-POINTTres eleveCritique
Dynpro/EcransMODULE status_0100 OUTPUTTres eleveMoyenne
FuBas non publiesCALL FUNCTION 'RFC_READ_TABLE'FaibleMoyenne
Acces DB directEXEC SQLEleveHaute

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'alternatives

Criteres 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 autorisee

Configurer 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 publiee
SELECT kunnr, name1, ort01, land1
FROM kna1
WHERE land1 = 'DE"
INTO TABLE @DATA(lt_customers).
" Apres : CDS View publiee
SELECT 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 publiee
CALL 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 publiee
TRY.
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 erreurs
ENDTRY.

Exemple 3 : CALL TRANSACTION -> RAP/Fiori

" Avant : Dynpro via CALL TRANSACTION
CALL 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.abap
REPORT 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 necessaire

Resultat :

  • 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 maintenable

Apres : 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 Definition
managed implementation in class zbp_i_salesorderedit unique;
strict ( 2 );
with draft;
define behavior for ZI_SalesOrderEdit alias SalesOrder
persistent table I_SalesOrder // Ou table personnalisee
draft table zsalesorder_draft
lock master
total etag LastChangedAt
authorization 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 Item
persistent table I_SalesOrderItem
draft table zsalesorderitem_draft
lock dependent by _SalesOrder
authorization 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 Definition
define behavior for ZI_SalesOrder alias SalesOrder
{
// ...
event MaterialChecked parameter ZA_MaterialCheckParam;
}
-- 2. Declenchement de l'event dans Behavior Implementation
METHOD 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|
recipient = '[email protected]"
).
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. Nouveau
REPORT z_performance_benchmark.
DATA: lv_start TYPE timestampl,
lv_end TYPE timestampl.
" Ancien : SELECT FROM vbak
GET 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 DevOps
trigger:
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 obligatoire

Checklist 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

  1. Creer un wrapper (encapsuler temporairement l’acces legacy)
  2. SAP Influence Request (api.sap.com -> Request Enhancement)
  3. 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 test
METHOD 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