(Caveat : Des notions de maniement de bases de données ne nuiront pas à la compréhension de ce qui suit. - Addendum : Je parle plus en détail et en théorie des différences entre clés fonctionnelles et arbitraires dans ce billet)

Oracle Applications

Le schéma de données est à peu près normalisé ; les noms de table sont en anglais, à peu près explicites. Chaque ligne porte un identifiant numérique, distinct de celui vu par l’être humain sur un écran ou un rapport. Bref, une conception comme on l’enseigne aux étudiants de première année d’informatique.

Par exemple, la table des en-têtes de commande OE_ORDER_HEADERS a comme clé primaire un identifiant numérique ORDER_HEADER_ID, et un numéro de commande utilisateur ORDER_NUMBER.
Cet ORDER_HEADER_ID sert de clé externe pour une jointure sur la table des lignes de commandes, OE_ORDER_LINES, dont la clé est ORDER_LINE_ID.
Pour être compréhensible par un humain, une ligne (un enregistrement dans la table), possède les champs LINE_NUMBER (valeur démarrant à 1) et SHIPMENT_NUMBER (idem).
Ce sont ces deux dernières valeurs qui s’affichent sur un écran ou un rapport, les clés réelles ne sont utiles qu’aux développeurs.

Exemple : par le ORDER_HEADER_ID 175321, je peux joindre les deux tables et récupérer les identifiants apparents de la commande, par exemple ORDER_NUMBER = 1000 et LINE_NUMBER = 1 et SHIPMENT_NUMBER = 1.

Un ORDER_NUMBER possède ses contraintes de numérotation (telle plage pour tel type de business par exemple) mais l’ORDER_HEADER_ID, identifiant unique de l’enregistrement, est unique et peut valoir ce qu’il veut. En général c’est une valeur abstraite mais « compréhensible » par un humain, genre 175321. Idem pour l’ORDER_LINE_ID, par exemple 321000.

À première vue, doublonner les identifiants numériques et les identifiants visibles semble inutile et ne facilite pas le travail du développeur ; mais au contraire cela offre une souplesse complète, les identifiants numériques pouvant être manipulés à volonté sans conséquence visible pour l’utilisateur[1]. De plus, chacune des tables principales n’a ainsi qu’une seule colonne en clé primaire, ce qui simplifie bien des choses[2].

Petit bonus également pour le développeur : avec un peu d’habitude d’une base précise, il sait qu’un ORDER_HEADER_ID tourne autour de 170-180000, un ORDER_LINE_ID de 320000, et les reconnaît au premier coup d’oeil.
De la même manière, il sait vite que la colonne ORDER_TYPE_ID des en-têtes de commande pointe vers OE_ORDER_TYPES, et que la valeur 74 correspond par exemple au type de commande « Petit électroménager » dans telle installation de tel revendeur d’électroménager.
Le seul inconvénient que je connaisse est que la mise en place sur différentes bases du même paramétrage (par exemple création d’un nouveau type de commande sur les bases de développement, recette et production) mène à des différences d’identifiants numériques (sauf coup de bol). Ce n’est pas censé être un problème si on programme proprement (ne jamais mettre ces identifiants « en dur » dans un programme), et si on rafraîchit suffisamment souvent ses bases de développement et recette[3].

Donc au final un schéma très propre, forcément touffu vu le nombre de tables impliquées, qui a cependant la mauvaise habitude de devenir de plus en plus tordu avec le temps et les nouvelles fonctionnalités. La pire aberration que j’ai rencontrée provient de lots de données qui migrent de table en table suivant l’état du statut au sein du flux logistique. Il y a peut-être une raison profonde mais cela jure avec la cohérence du reste.

SAP R/3

Le modèle de données de R/3 est à la fois plus simple et plus cauchemardesque.

Comme dit dans un précédent billet, R/3 (l’ERP « classique ») utilise surtout des tables dont les noms inspirés de l’allemand ont quatre lettres, du genre MARA, VBAK, LIKP, T001...

Dans ces tables il n’y a pas d’identifiant numérique comme sous Oracle. La clé primaire est fonctionnelle. Pour VBAK (les en-têtes de commande), cette clé est composée des champs MANDT (le mandant, qui en quelque sorte identifie la base de travail[4]) et de VBELN, le document commercial visible par l’utilisateur !

Évidemment, dans VBAP, correspondant aux postes (lignes de commandes), ce même VBELN sera répercuté pour identifier ce qui correspond à une commande donnée. Et la clé primaire de cette table des postes est un triplet MANDT, VBELN, POSNR.
De même, la table TVAK des types de commande contient la valeur de type directement dans sa clé.

En conséquence, renommer un objet sous SAP revient à renommer sa clé primaire ! Ça ne se fait pas à la légère, sous peine de corrompre les données. Donc, sous SAP, on ne modifie jamais rien directement dans les tables[5] ! Le système fait d’ailleurs de son mieux pour l’interdire au développeur, et il faut utiliser les fonctions, BAPIs, BADIs, IDOCs, user exits, programmes d’import... livrés avec le système.

Pire : la clé « fonctionnelle » utilisée ici n’est plus un moyen de vérifer l’unicité ou la destruction d’un enregistrement. Supposons une ligne de commande (clé : VBELN+POSNR) créée, puis détruite (pas logiquement, mais physiquement et totalement), puis récréée avec le même numéro de poste (ligne)[6]. Un programme extérieur[7] ne peut savoir si une ligne qu’il a mémorisée (par sa clé VBELN+POSNR) est la même ou pas.
Sous Oracle, le ORDER_LINE_ID aurait totalement disparu, et un nouveau aurait été recréé, même si l’utilisateur a choisi de conserver les mêmes LINE_NUMBER et SHIPMENT_NUMBER.

Il y a plus vicieux. L’utilisateur verra à l’écran un document de vente numéroté 18500, alors que la base stockera en réalité dans VBELN la chaîne 0000018500 (dix caractères). Forcer la taille de la chaîne a un intérêt en terme de classement (sans cela, 1869 serait entre 18500 et 18695), mais pour le développeur c’est un petit cauchemar : chaque variable est susceptible de contenir la chaîne avec ou sans ses zéros initiaux et il faut convertir à gogo (sachant que le moindre appel de fonction en ABAP fait cinq lignes et qu’elles ne peuvent pas être imbriquées les unes dans les autres sur la même ligne comme dans la plupart des langages de programmation).

Encore plus drôle : les nouveaux modules, en premier lieu le CRM mais aussi des morceaux de R/3, ont décidé de prendre en compte les avancées du dernier quart du siècle dernier en matière de bases de données, et de passer aux identifiants numériques !
Mais dans une optique d’universalité (sans doute pour faciliter la fusion de bases SAP différentes), ces identifiants sont des GUID (global identifier), en hexadécimal, du genre de 447072A2FB800063000000000A1F352F.
Allez déboguer des programmes où la moitié des variables sont des identifiants de ce genre...
Le plus drôle c’est que ces GUID apparaissent parfois à l’utilisateur (abomination peut-être liée au paramétrage là où je travaille).

Enfin, pour compliquer la tâche, SAP interdisant tout accès à Oracle (la base, qui en général tourne derrière) et imposant ses propres outils de développement, une bonne partie des outils de contrôle d’Oracle sont inaccessibles. Adieu les bonnes grosses requêtes de contrôle du bon déroulement d’un programme qui impliquaient dix tables...[8]

Verdict

Travailler avec Oracle avait ses bons côtés ; j’ai beaucoup de mal à apprécier SAP pour le développement. Autant SAP est en avance par son interface perfectible mais fonctionnelle, et l’effroyable panel de possibilités, autant Oracle permet au développeur de faire ce qu’il veut pour intervenir sur les données ou rajouter des programmes, sans lui mettre de bâtons dans les roues. Mais on demande rarement son avis au développeur lors du choix d’un ERP...

Partie 1 : Des interfaces hideuses

Partie 2 : Deux gros patchworks

Partie 3 : Des interfaces très particulières

Partie 4 : Philosophies opposées

Partie 5 : Schémas de données

Notes

[1] Encore un niveau en dessous, Oracle identifie l’enregistrement sur le disque dur par un autre identifiant, le ROWID, qui suit le même principe, et est susceptible de modification à tout moment, en cas de réorganisation des données. L’utilisateur de la base de donneés (le développeur) n’a en général pas à se préoccuper de ce ROWID.

[2] Les tables de correspondance notamment peuvent avoir des clés composées, et rien n’interdit de rajouter des index à volonté sur les autres colonnes (au contraire).

[3] Et là on s’aperçoit que c’est une opération lourde ; je travaille actuellement avec une base de développement vieille d’un an au bas mot, et mon client précédent attendait souvent six mois pour rafraîchir son Oracle.

[4] Je simplifie. Un mandant indique une vision du monde, et on peut ainsi collectioner les mandants dans une même base de données SAP : mandant de paramétrage, mandant de développement ne comportant que des programmes, mandant de test comportant des données. Lourd mais pratique. Chaque clé primaire de table commence par le mandant.

[5] Exception pour les tables ajoutées par l’utilisateur que SAP à proprement parler ignore.

[6] Je suis d’accord que ça ne devrait pas pouvoir se faire, mais l’utilisateur peut le faire, et il le fera.

[7] Comme celui que je suis en train d’écrire au moment où je rédige ceci.

[8] De plus se pose le problème des droits d’accès à la base, forcément restreints dans ce contexte...