Organisation et architecture des Programmes de Services
Organisation et architecture des SRVPGM
(Ce document est aussi un hommage à l’immense Stevie Ray Vaughan )
L'approche moderne recommandée
L'industrie IBM i tend vers un hybride pragmatique :
Architecture modulaire par domaine :
Exemple concret :
// QRPGLESRC/CUSTOMER_H
// Fichier unique de prototypes pour CUSTOMER_SRVPGM
dcl-pr validerClient ind;
clientId packed(9:0) const;
end-pr;
dcl-pr creerClient packed(9:0);
nom varchar(50) const;
email varchar(100) const;
end-pr;
dcl-pr supprimerClient ind;
clientId packed(9:0) const;
end-pr;
Utilisation dans un programme :
**free
ctl-opt bnddir('MYAPP'); // Contient CUSTOMER_SRVPGM
/include qrpglesrc,customer_h
dcl-s nouveauId packed(9:0);
nouveauId = creerClient('Dupont':'dupont@mail.com');
if validerClient(nouveauId);
// ...
endif;
Le bon ratio de fonction dans les fonctions/modules : par responsabilité cohérente
*Un MODULE = un ensemble de procédures partageant une responsabilité commune
Pas une procédure = un module (trop granulaire), ni tout dans un module (pas assez granulaire).
Exemples concrets
❌ Trop granulaire (à éviter)
CUSTOMER_SRVPGM
├─ CUSTVAL1 (*MODULE) → juste validerEmail()
├─ CUSTVAL2 (*MODULE) → juste validerTelephone()
├─ CUSTVAL3 (*MODULE) → juste validerCodePostal()
└─ CUSTVAL4 (*MODULE) → juste validerSiret()
Problème : Complexité de build, trop de fichiers, overhead de linking
❌ Pas assez granulaire
CUSTOMER_SRVPGM (source unique compilé directement)
→ validerEmail()
→ validerTelephone()
→ creerClient()
→ modifierClient()
→ supprimerClient()
→ calculerChiffreAffaires()
→ exporterVersCompta()
→ envoyerEmailBienvenue()
Problème : Modification d'une ligne = recompilation complète
✅ Juste équilibre (recommandé)
CUSTOMER_SRVPGM
├─ CUSTVAL (*MODULE) → Toutes les validations
│ ├─ validerEmail()
│ ├─ validerTelephone()
│ ├─ validerCodePostal()
│ └─ validerSiret()
│
├─ CUSTCRUD (*MODULE) → Opérations CRUD
│ ├─ creerClient()
│ ├─ lireClient()
│ ├─ modifierClient()
│ └─ supprimerClient()
│
├─ CUSTBUS (*MODULE) → Logique métier
│ ├─ calculerChiffreAffaires()
│ ├─ calculerStatutFidelite()
│ └─ verifierLimiteCredit()
│
└─ CUSTINT (*MODULE) → Intégrations externes
├─ exporterVersCompta()
├─ synchroniserCRM()
└─ envoyerEmailBienvenue()
Les critères de regroupement
1. Responsabilité fonctionnelle (le plus important)
Regroupe ce qui change pour les mêmes raisons :
Validations ensemble
CRUD ensemble
Calculs métier ensemble
Intégrations externes ensemble
2. Fréquence de modification
Code stable (validations basiques) → peut être plus gros
Code volatile (règles métier) → plus petit et isolé
3. Dépendances
Procédures qui s'appellent entre elles → même module
Procédures indépendantes → modules séparés si logique
4. Taille raisonnable
Règle empirique :
3 à 10 procédures par module en moyenne
500 à 2000 lignes de code par module
Si > 2500 lignes → probablement à découper
Si < 200 lignes avec 1 seule proc → probablement à fusionner
Exemple détaillé : module CUSTVAL
**free
// QRPGLESRC/CUSTVAL
// Module de validation client
ctl-opt nomain; // C'est un module, pas un programme
/include qrpglesrc,customer_h
//--------------------------------------------------
// Valide une adresse email
//--------------------------------------------------
dcl-proc validerEmail export;
dcl-pi *n ind;
email varchar(100) const;
end-pi;
dcl-s regex varchar(200);
if %len(email) = 0;
return *off;
endif;
// Logique validation regex
// ...
return *on;
end-proc;
//--------------------------------------------------
// Valide un numéro de téléphone français
//--------------------------------------------------
dcl-proc validerTelephone export;
dcl-pi *n ind;
tel varchar(20) const;
end-pi;
// Logique validation téléphone
// ...
return *on;
end-proc;
//--------------------------------------------------
// Valide un code postal français
//--------------------------------------------------
dcl-proc validerCodePostal export;
dcl-pi *n ind;
cp char(5) const;
end-pi;
// Logique validation CP
// ...
return *on;
end-proc;
//--------------------------------------------------
// Valide un numéro SIRET
//--------------------------------------------------
dcl-proc validerSiret export;
dcl-pi *n ind;
siret char(14) const;
end-pi;
// Logique validation SIRET (algo Luhn)
// ...
return *on;
end-proc;
//--------------------------------------------------
// Procédure interne (non exportée)
// Utilisée par les autres validations
//--------------------------------------------------
dcl-proc verifierLuhn;
dcl-pi *n ind;
numero char(50) const;
end-pi;
// Algorithme de Luhn
// ...
return *on;
end-proc;
Points clés :
ctl-opt nomain; → c'est un module, pas un programme standalone
export sur les procédures publiques
Pas de export sur verifierLuhn() → privée au module
Toutes les validations regroupées (cohésion)
Règle d'or pragmatique
Se poser ces questions :
"Si je modifie cette procédure, est-ce que les autres du module risquent de changer aussi ?"
Oui → bon regroupement
Non → peut-être à séparer
"Est-ce que ces procédures partagent des utilitaires internes ?"
Oui → même module
Non → modules séparés possibles
"Est-ce que je peux nommer le module avec UN concept clair ?"
Oui ("Validation", "CRUD", "Export") → bon module
Non ("Divers", "Utils") → probablement mal découpé
Le problème des dépendances circulaires
Pourquoi c'est aberrant :
Impossible à compiler proprement (ordre de build ?)
Couplage fort → maintenance cauchemardesque
Réutilisation impossible → tu ne peux pas prendre A sans B ni B sans A
Signature instable → changement dans A impacte B qui impacte A...
Testabilité nulle → impossible de tester unitairement
Comment on arrive à cette situation
Scénario typique :
// CLIENT_SRVPGM
dcl-proc creerClient;
// …
creerAdresseDefaut(clientId); // Appel vers ADRESSE_SRVPGM
// …
end-proc;
// ADRESSE_SRVPGM
dcl-proc creerAdresseDefaut;
// …
if validerClient(clientId); // Oups ! Appel vers CLIENT_SRVPGM
// …
endif;
end-proc;
```
Pourquoi ça arrive :
- Mauvaise séparation des responsabilités
- Logique métier éparpillée
- Évolution organique sans refactoring
- Méconnaissance des principes de découplage
Les solutions
✅ Solution 1 : Extraction de dépendances communes
Principe : Créer un *SRVPGM de niveau inférieur contenant les fonctions partagées
```
Exemple concret :
// VALIDATION_SRVPGM (couche basse)
dcl-proc validerClient export;
dcl-pi *n ind;
clientId packed(9:0) const;
end-pi;
// Validation pure, sans dépendance
return clientId > 0;
end-proc;
dcl-proc validerAdresse export;
dcl-pi *n ind;
adresseId packed(9:0) const;
end-pi;
return adresseId > 0;
end-proc;
// CLIENT_SRVPGM (couche haute)
// Utilise : VALIDATION_SRVPGM + ADRESSE_SRVPGM
dcl-proc creerClient export;
// …
if validerClient(clientId);
adresseId = creerAdresseDefaut(clientId);
endif;
// …
end-proc;
// ADRESSE_SRVPGM (couche haute)
// Utilise : VALIDATION_SRVPGM uniquement
dcl-proc creerAdresseDefaut export;
dcl-pi *n packed(9:0);
clientId packed(9:0) const;
end-pi;
// N'appelle PLUS validerClient du CLIENT_SRVPGM
// Mais validerClient du VALIDATION_SRVPGM
if not validerClient(clientId);
return 0;
endif;
// …
return nouvelleAdresseId;
end-proc;
✅ Solution 2 : Inversion de dépendance (Dependency Injection)
Principe : Passer la fonction de validation en paramètre
// ADRESSE_SRVPGM
dcl-proc creerAdresseDefaut export;
dcl-pi *n packed(9:0);
clientId packed(9:0) const;
validateurClient pointer(*proc) const; // Fonction passée en param
end-pi;
dcl-pr validateur ind extproc(validateurClient);
id packed(9:0) const;
end-pr;
if not validateur(clientId);
return 0;
endif;
// ...
end-proc;
// CLIENT_SRVPGM - Appelant
adresseId = creerAdresseDefaut(clientId : %paddr(validerClient));
```
Avantage : ADRESSE_SRVPGM ne dépend plus de CLIENT_SRVPGM
✅ Solution 3 : Architecture en couches strictes
Règle d'or :Une dépendance ne va QUE vers le bas, jamais vers le haut ou latéralement.
Règle : On ne dépend QUE de la couche du dessous
Exemple d'organisation :
```
// Couche Infrastructure (niveau 0 - aucune dépendance métier)
DBUTIL_SRVPGM
→ executeSQL()
→ prepareStatement()
HTTPCLIENT_SRVPGM
→ httpGet()
→ httpPost()
// Couche Services (niveau 1 - dépend de Infrastructure)
VALIDATION_SRVPGM (utilise: rien ou DBUTIL)
→ validerEmail()
→ validerSiret()
EMAIL_SRVPGM (utilise: HTTPCLIENT_SRVPGM)
→ envoyerEmail()
→ verifierFormat()
// Couche Métier (niveau 2 - dépend de Services)
CLIENT_SRVPGM (utilise: VALIDATION + EMAIL + DBUTIL)
→ creerClient()
→ validerClient()
ADRESSE_SRVPGM (utilise: VALIDATION + DBUTIL)
→ creerAdresse()
→ normaliserAdresse()
FACTURE_SRVPGM (utilise: CLIENT + ADRESSE + EMAIL)
→ creerFacture()
→ envoyerFacture()
```
✅ Solution 4 : Refactoring radical si nécessaire
Parfois, la circularité révèle un découpage conceptuel erroné.
Exemple :
```
Problème : CLIENT et ADRESSE trop couplés
Solution : Créer CLIENTCOMPLET_SRVPGM qui englobe les deux
```
🔍 Détection : Comment détecter et prévenir
Commande IBM i pour voir les dépendances :
```
DSPSRVPGM SRVPGM(MALIB/CLIENT_SRVPGM)
Option 11 = Afficher les dépendances
Script CL pour audit :
PGM
DCL VAR(&LIB) TYPE(*CHAR) LEN(10) VALUE('MALIB')
/* Liste tous les SRVPGM et leurs dépendances */
DSPOBJD OBJ(&LIB/*ALL) OBJTYPE(*SRVPGM) +
OUTPUT(*OUTFILE) +
OUTFILE(QTEMP/SRVLIST)
/* Analyse des dépendances circulaires */
/* (nécessite un programme pour parser) */
ENDPGM
🛡️ Prévention
1. Règle de design :
"Si A utilise B, alors B ne doit JAMAIS utiliser A, ni directement ni indirectement"
2. Checklist avant création d'un SRVPGM :
Quelle est sa responsabilité unique ?
De quels *SRVPGM dépend-il ?
Ces dépendances sont-elles "vers le bas" ?
Y a-t-il un risque de retour circulaire ?
3. Revue de code systématique : Lors de l'ajout d'un /include dans un *SRVPGM, vérifier qu'on ne crée pas de cycle.
4. Documentation des dépendances :
**free
//====================================================
// CLIENT_SRVPGM
//
// Dépendances:
// - VALIDATION_SRVPGM (services)
// - EMAIL_SRVPGM (services)
// - DBUTIL_SRVPGM (infrastructure)
//
// Utilisé par:
// - FACTURE_SRVPGM
// - COMMANDE_SRVPGM
//===================================================
Solution 1 : Script SQL complet
-- =========================================================
-- DÉTECTION DES DÉPENDANCES CIRCULAIRES ENTRE *SRVPGM
-- =========================================================
-- 1. Créer une table temporaire des dépendances directes
CREATE OR REPLACE TABLE QTEMP.SRVPGM_DEPS AS (
SELECT
TRIM(A.SYSTEM_OBJECT_SCHEMA) AS LIB_SOURCE,
TRIM(A.SYSTEM_OBJECT_NAME) AS SRVPGM_SOURCE,
TRIM(B.BOUND_OBJECT_LIBRARY) AS LIB_CIBLE,
TRIM(B.BOUND_OBJECT) AS SRVPGM_CIBLE,
B.BOUND_OBJECT_TYPE AS TYPE_CIBLE
FROM QSYS2.PROGRAM_INFO A
INNER JOIN TABLE(QSYS2.BOUND_SRVPGM_INFO(
SERVICE_PROGRAM_LIBRARY => A.SYSTEM_OBJECT_SCHEMA,
SERVICE_PROGRAM => A.SYSTEM_OBJECT_NAME
)) B
WHERE A.OBJECT_TYPE = '*SRVPGM'
AND A.SYSTEM_OBJECT_SCHEMA = 'VOTRE_LIB' -- ← ADAPTER ICI
AND B.BOUND_OBJECT_TYPE = '*SRVPGM'
) WITH DATA;
-- 2. Détecter les cycles directs (A → B et B → A)
SELECT
'CYCLE DIRECT' AS TYPE_CYCLE,
D1.LIB_SOURCE || '/' || D1.SRVPGM_SOURCE AS SRVPGM_A,
D1.LIB_CIBLE || '/' || D1.SRVPGM_CIBLE AS SRVPGM_B,
'←→' AS RELATION
FROM QTEMP.SRVPGM_DEPS D1
INNER JOIN QTEMP.SRVPGM_DEPS D2
ON D1.LIB_SOURCE = D2.LIB_CIBLE
AND D1.SRVPGM_SOURCE = D2.SRVPGM_CIBLE
AND D1.LIB_CIBLE = D2.LIB_SOURCE
AND D1.SRVPGM_CIBLE = D2.SRVPGM_SOURCE
WHERE D1.SRVPGM_SOURCE < D2.SRVPGM_SOURCE -- Éviter les doublons
UNION ALL
-- 3. Détecter les cycles indirects (A → B → C → A)
SELECT
'CYCLE INDIRECT' AS TYPE_CYCLE,
D1.LIB_SOURCE || '/' || D1.SRVPGM_SOURCE AS SRVPGM_A,
D3.LIB_CIBLE || '/' || D3.SRVPGM_CIBLE AS SRVPGM_C,
D1.SRVPGM_SOURCE || ' → ' || D2.SRVPGM_CIBLE || ' → ' || D3.SRVPGM_CIBLE || ' → ' || D1.SRVPGM_SOURCE AS RELATION
FROM QTEMP.SRVPGM_DEPS D1
INNER JOIN QTEMP.SRVPGM_DEPS D2
ON D1.LIB_CIBLE = D2.LIB_SOURCE
AND D1.SRVPGM_CIBLE = D2.SRVPGM_SOURCE
INNER JOIN QTEMP.SRVPGM_DEPS D3
ON D2.LIB_CIBLE = D3.LIB_SOURCE
AND D2.SRVPGM_CIBLE = D3.SRVPGM_SOURCE
WHERE D3.LIB_CIBLE = D1.LIB_SOURCE
AND D3.SRVPGM_CIBLE = D1.SRVPGM_SOURCE
AND D1.SRVPGM_SOURCE <> D2.SRVPGM_CIBLE -- Pas de boucle immédiate
AND D2.SRVPGM_CIBLE <> D3.SRVPGM_CIBLE -- Pas de doublon
ORDER BY TYPE_CYCLE, SRVPGM_A;
```
### Résultat attendu
```
TYPE_CYCLE SRVPGM_A SRVPGM_B RELATION
-------------- ------------------ -------------------------- ---------------------
CYCLE DIRECT MYLIB/CLIENT MYLIB/FACTURE ←→
CYCLE DIRECT MYLIB/ADRESSE MYLIB/COMMANDE ←→
CYCLE INDIRECT MYLIB/PRODUIT MYLIB/PRODUIT PRODUIT → STOCK → PRIX → PRODUIT
Solution 2 : Vue SQL réutilisable
Pour un audit permanent, créer une vue SQL :
-- =========================================================
-- VUE : TOUTES LES DÉPENDANCES ENTRE SRVPGM
-- =========================================================
CREATE OR REPLACE VIEW MYLIB.V_SRVPGM_DEPENDENCIES AS
SELECT
TRIM(A.SYSTEM_OBJECT_SCHEMA) AS SOURCE_LIB,
TRIM(A.SYSTEM_OBJECT_NAME) AS SOURCE_SRVPGM,
TRIM(A.OBJECT_TEXT) AS SOURCE_DESC,
TRIM(B.BOUND_OBJECT_LIBRARY) AS TARGET_LIB,
TRIM(B.BOUND_OBJECT) AS TARGET_SRVPGM,
B.BOUND_OBJECT_TYPE AS TARGET_TYPE,
B.ACTIVATION_GROUP AS ACTGRP,
B.BINDING_DIRECTORY AS BNDDIR
FROM QSYS2.PROGRAM_INFO A
CROSS JOIN LATERAL (
SELECT * FROM TABLE(QSYS2.BOUND_SRVPGM_INFO(
SERVICE_PROGRAM_LIBRARY => A.SYSTEM_OBJECT_SCHEMA,
SERVICE_PROGRAM => A.SYSTEM_OBJECT_NAME
))
) B
WHERE A.OBJECT_TYPE = '*SRVPGM'
AND B.BOUND_OBJECT_TYPE = '*SRVPGM';
-- =========================================================
-- VUE : CYCLES DIRECTS DÉTECTÉS
-- =========================================================
CREATE OR REPLACE VIEW MYLIB.V_SRVPGM_CIRCULAR_DEPS AS
SELECT
D1.SOURCE_LIB || '/' || D1.SOURCE_SRVPGM AS SRVPGM_A,
D1.TARGET_LIB || '/' || D1.TARGET_SRVPGM AS SRVPGM_B,
'Direct circular dependency' AS ISSUE_TYPE,
CURRENT_TIMESTAMP AS DETECTED_AT
FROM MYLIB.V_SRVPGM_DEPENDENCIES D1
INNER JOIN MYLIB.V_SRVPGM_DEPENDENCIES D2
ON D1.SOURCE_LIB = D2.TARGET_LIB
AND D1.SOURCE_SRVPGM = D2.TARGET_SRVPGM
AND D1.TARGET_LIB = D2.SOURCE_LIB
AND D1.TARGET_SRVPGM = D2.SOURCE_SRVPGM
WHERE D1.SOURCE_SRVPGM < D2.SOURCE_SRVPGM; -- Évite doublons
Et hop ! Exécution de la vue !
-- Voir toutes les dépendances
SELECT * FROM MYLIB.V_SRVPGM_DEPENDENCIES
WHERE SOURCE_LIB = 'MYLIB'
ORDER BY SOURCE_SRVPGM, TARGET_SRVPGM;
-- Voir uniquement les cycles
SELECT * FROM MYLIB.V_SRVPGM_CIRCULAR_DEPS;
Solution 3 : Procédure stockée avec graphe complet
-- =========================================================
-- PROCÉDURE : ANALYSER LES CYCLES À N NIVEAUX
-- =========================================================
CREATE OR REPLACE PROCEDURE MYLIB.CHECK_SRVPGM_CYCLES(
IN P_LIBRARY VARCHAR(10)
)
LANGUAGE SQL
BEGIN
-- Table temporaire pour stocker les chemins
DECLARE GLOBAL TEMPORARY TABLE SESSION.PATHS (
LEVEL INT,
PATH VARCHAR(1000),
SOURCE_SRVPGM VARCHAR(10),
TARGET_SRVPGM VARCHAR(10),
IS_CYCLE CHAR(1)
) WITH REPLACE;
-- 1. Récupérer toutes les dépendances directes
INSERT INTO SESSION.PATHS (LEVEL, PATH, SOURCE_SRVPGM, TARGET_SRVPGM, IS_CYCLE)
SELECT
1 AS LEVEL,
SOURCE_SRVPGM || ' → ' || TARGET_SRVPGM AS PATH,
SOURCE_SRVPGM,
TARGET_SRVPGM,
'N' AS IS_CYCLE
FROM MYLIB.V_SRVPGM_DEPENDENCIES
WHERE SOURCE_LIB = P_LIBRARY
AND TARGET_LIB = P_LIBRARY;
-- 2. Récursion pour trouver les chemins à 2+ niveaux
FOR V_LEVEL AS cur CURSOR FOR
SELECT LEVEL FROM (VALUES(2),(3),(4),(5)) AS T(LEVEL)
DO
INSERT INTO SESSION.PATHS (LEVEL, PATH, SOURCE_SRVPGM, TARGET_SRVPGM, IS_CYCLE)
SELECT
V_LEVEL AS LEVEL,
P.PATH || ' → ' || D.TARGET_SRVPGM AS PATH,
P.SOURCE_SRVPGM,
D.TARGET_SRVPGM,
CASE
WHEN D.TARGET_SRVPGM = P.SOURCE_SRVPGM THEN 'Y' -- Cycle détecté !
WHEN POSITION(D.TARGET_SRVPGM IN P.PATH) > 0 THEN 'Y' -- Déjà visité
ELSE 'N'
END AS IS_CYCLE
FROM SESSION.PATHS P
INNER JOIN MYLIB.V_SRVPGM_DEPENDENCIES D
ON P.TARGET_SRVPGM = D.SOURCE_SRVPGM
AND P.LEVEL = V_LEVEL - 1
AND P.IS_CYCLE = 'N' -- Ne continue que les chemins sans cycle
WHERE D.SOURCE_LIB = P_LIBRARY
AND D.TARGET_LIB = P_LIBRARY
AND POSITION(D.TARGET_SRVPGM IN P.PATH) = 0; -- Évite boucles infinies
END FOR;
-- 3. Afficher uniquement les cycles
SELECT
LEVEL AS CYCLE_DEPTH,
PATH AS CIRCULAR_PATH,
SOURCE_SRVPGM AS STARTS_AT,
TARGET_SRVPGM AS ENDS_AT
FROM SESSION.PATHS
WHERE IS_CYCLE = 'Y'
ORDER BY LEVEL, PATH;
END;
Utilisation :
CALL MYLIB.CHECK_SRVPGM_CYCLES('MYLIB');
```
**Résultat :**
```
CYCLE_DEPTH CIRCULAR_PATH STARTS_AT ENDS_AT
----------- ----------------------------------------- --------- ---------
2 CLIENT → FACTURE → CLIENT CLIENT CLIENT
3 PRODUIT → STOCK → PRIX → PRODUIT PRODUIT PRODUIT
4 A → B → C → D → A A A
Anatomie du STRPGMEXP
Le STRPGMEXP (Start Program Export) est un outil puissant mais souvent mal utilisé.
C'est le "contrat d'interface" du *SRVPGM.
Voyons les bonnes pratiques en détail ! 📜
Structure de base
// QSRVSRC/CUSTOMER - Binder source
**free
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(calculerScoreFidelite)
ENDPGMEXP
```
### Éléments clés
```
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('...')
│ │ │
│ │ └─ Signature unique pour cette version
│ └─ Niveau de programme (*CURRENT ou *PRV)
└─ Début de la définition d'export
EXPORT SYMBOL(nomProcedure)
│ │
│ └─ Nom exact de la procédure à exporter
└─ Directive d'export
ENDPGMEXP
└─ Fin de la définition d'export
Bonne pratique #1 : Versioning sémantique de la signature
❌ Mauvais : Signature générique
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER')
Problème : Impossible de savoir quelle version, aucune traçabilité.
✅ Bon : Signature versionnée
// Format recommandé: NOMLIB_NOM_VMAJOR_MINOR_PATCH
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V2_0_0')
Avantages :
✅ Traçabilité claire des versions
✅ Compatible avec versioning sémantique
✅ Facile à auditer
🎯 Excellent : Signature avec date
// Format: NOMLIB_NOM_VMAJOR_MINOR_YYYYMMDD
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUST_V2_0_20260122')
Quand incrémenter la version ?
Bonne pratique #2 : Gestion multi-niveaux (*CURRENT + *PRV)
Concept : Compatibilité ascendante
Permet aux anciens programmes de continuer à fonctionner tout en offrant de nouvelles fonctionnalités.
// QSRVSRC/CUSTOMER
**free
//===========================================================
// Version CURRENT (3.0.0) - Latest
//===========================================================
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V3_0_0')
// Exports V1.0
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
// Exports V2.0 (ajouts compatibles)
EXPORT SYMBOL(calculerScoreFidelite)
EXPORT SYMBOL(verifierLimiteCredit)
// Exports V3.0 (nouvelles fonctionnalités)
EXPORT SYMBOL(creerClientComplet) // Nouvelle version étendue
EXPORT SYMBOL(obtenirHistoriqueClient)
EXPORT SYMBOL(archiverClient)
ENDPGMEXP
//===========================================================
// Version PRV (2.0.0) - Previous (pour compatibilité)
//===========================================================
STRPGMEXP PGMLVL(*PRV) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(calculerScoreFidelite)
EXPORT SYMBOL(verifierLimiteCredit)
ENDPGMEXP
```
Comment ça marche ?
Programme bindé avec V2 → Utilise PGMLVL(*PRV)
├─ Trouve signature 'CUSTOMER_V2_0_0'
└─ Accède aux 6 procédures de V2
Programme bindé avec V3 → Utilise PGMLVL(*CURRENT)
├─ Trouve signature 'CUSTOMER_V3_0_0'
└─ Accède aux 9 procédures de V3
Les deux cohabitent dans le MÊME *SRVPGM ! ✅
Limite : Maximum 2 niveaux
IBM i supporte uniquement :
*CURRENT (version courante)
*PRV (version précédente)
Pas de *PRV2, *PRV3... → Si on veut supporter plus de 2 versions, il faut créer des *SRVPGM séparés.
Bonne pratique #3 : Organisation et documentation
✅ Bon : Grouper par version et commenter
**free
//===========================================================
// Version 3.0.0 - CURRENT (2026-01-22)
// Ajouts:
// - creerClientComplet (avec adresse et facturation)
// - obtenirHistoriqueClient
// - archiverClient
//===========================================================
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V3_0_0')
//--- V1.0 - Exports initiaux (2024-03-15) ---
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
//--- V2.0 - Scoring et limites (2025-06-10) ---
EXPORT SYMBOL(calculerScoreFidelite)
EXPORT SYMBOL(verifierLimiteCredit)
//--- V3.0 - Gestion avancée (2026-01-22) ---
EXPORT SYMBOL(creerClientComplet)
EXPORT SYMBOL(obtenirHistoriqueClient)
EXPORT SYMBOL(archiverClient)
ENDPGMEXP
//===========================================================
// Version 2.0.0 - PREVIOUS (2025-06-10)
// Compatibilité pour programmes existants
//===========================================================
STRPGMEXP PGMLVL(*PRV) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(calculerScoreFidelite)
EXPORT SYMBOL(verifierLimiteCredit)
ENDPGMEXP
Avantages :
✅ Historique clair des évolutions
✅ Facile de voir ce qui a été ajouté quand
✅ Documentation intégrée au source
❌ Mauvais : Liste en vrac sans structure
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER')
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(calculerScoreFidelite)
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(archiverClient)
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(validerSiret)
ENDPGMEXP
Problèmes :
❌ Pas d'ordre logique
❌ Impossible de savoir quand chaque export a été ajouté
❌ Difficile de maintenir
Bonne pratique #4 : Stratégie de dépréciation
Scénario : Remplacer creerClient par creerClientV2
Étape 1 : Ajouter nouvelle version (compatibilité totale)
**free
//===========================================================
// Version 2.0.0 - CURRENT
//===========================================================
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V2_0_0')
//--- V1.0 - Exports initiaux ---
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient) // DEPRECATED - Utiliser creerClientV2
EXPORT SYMBOL(supprimerClient)
//--- V2.0 - Nouvelles versions ---
EXPORT SYMBOL(creerClientV2) // Nouvelle version recommandée
ENDPGMEXP
//===========================================================
// Version 1.0.0 - PREVIOUS
//===========================================================
STRPGMEXP PGMLVL(*PRV) SIGNATURE('CUSTOMER_V1_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
ENDPGMEXP
Dans le module CUSTCRUD :
// Ancienne version - DEPRECATED
dcl-proc creerClient export;
dcl-pi *n packed(9:0);
nom varchar(50) const;
email varchar(100) const;
end-pi;
// Log warning
logWarn('creerClient est obsolète, utilisez creerClientV2');
// Délègue à la nouvelle version avec param par défaut
return creerClientV2(nom : email : '');
end-proc;
// Nouvelle version
dcl-proc creerClientV2 export;
dcl-pi *n packed(9:0);
nom varchar(50) const;
email varchar(100) const;
siret char(14) const;
end-pi;
// Implémentation complète
// ...
return clientId;
end-proc;
*Étape 2 (6 mois plus tard) : Retirer de CURRENT
//===========================================================
// Version 3.0.0 - CURRENT (creerClient retiré)
//===========================================================
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V3_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
// EXPORT SYMBOL(creerClient) ← RETIRÉ de *CURRENT
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(creerClientV2)
ENDPGMEXP
//===========================================================
// Version 2.0.0 - PREVIOUS (garde creerClient pour compat)
//===========================================================
STRPGMEXP PGMLVL(*PRV) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient) // Encore là pour anciens programmes
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(creerClientV2)
ENDPGMEXP
Résultat :
Nouveaux programmes → Doivent utiliser creerClientV2
Anciens programmes → Continuent de fonctionner (bindés avec *PRV)
Étape 3 (12 mois plus tard) : Retrait total
//===========================================================
// Version 4.0.0 - CURRENT
//===========================================================
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V4_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(creerClientV2)
ENDPGMEXP
//===========================================================
// Version 3.0.0 - PREVIOUS
//===========================================================
STRPGMEXP PGMLVL(*PRV) SIGNATURE('CUSTOMER_V3_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
// EXPORT SYMBOL(creerClient) ← RETIRÉ aussi de *PRV
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(creerClientV2)
ENDPGMEXP
À ce stade : creerClient n'existe plus nulle part → les programmes doivent avoir été migrés.
Bonne pratique #5 : Ordre des exports
Principe : Une fois exporté, ne JAMAIS changer l'ordre
// ❌ ERREUR GRAVE
// Version 1.0
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V1_0_0')
EXPORT SYMBOL(validerEmail) // Position 1
EXPORT SYMBOL(creerClient) // Position 2
EXPORT SYMBOL(supprimerClient) // Position 3
ENDPGMEXP
// Version 2.0 - ORDRE CHANGÉ !
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(creerClient) // Position 1 (était 2)
EXPORT SYMBOL(validerEmail) // Position 2 (était 1)
EXPORT SYMBOL(supprimerClient) // Position 3 (inchangé)
EXPORT SYMBOL(nouvelleProc) // Position 4
ENDPGMEXP
Conséquence : Programmes peuvent appeler la mauvaise procédure ! 💥
✅ Bon : Toujours ajouter à la fin
// Version 1.0
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V1_0_0')
EXPORT SYMBOL(validerEmail) // Position 1
EXPORT SYMBOL(creerClient) // Position 2
EXPORT SYMBOL(supprimerClient) // Position 3
ENDPGMEXP
// Version 2.0 - Ajout à la FIN
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(validerEmail) // Position 1 (inchangé)
EXPORT SYMBOL(creerClient) // Position 2 (inchangé)
EXPORT SYMBOL(supprimerClient) // Position 3 (inchangé)
EXPORT SYMBOL(nouvelleProc) // Position 4 (nouveau)
ENDPGMEXP
Important : Cette règle s'applique au sein d'un même niveau (CURRENT ou PRV).
Bonne pratique #6 : Utiliser EXPORT(*SRCFILE) vs EXPORT(*ALL)
Comparaison
/* Option A : EXPORT(*ALL) - Automatique */
CRTSRVPGM SRVPGM(MYLIB/CUSTOMER) +
MODULE(MYLIB/CUSTVAL MYLIB/CUSTCRUD) +
EXPORT(*ALL)
/* Option B : EXPORT(*SRCFILE) - Contrôlé */
CRTSRVPGM SRVPGM(MYLIB/CUSTOMER) +
MODULE(MYLIB/CUSTVAL MYLIB/CUSTCRUD) +
SRCFILE(MYLIB/QSRVSRC) +
SRCMBR(CUSTOMER) +
EXPORT(*SRCFILE)
EXPORT(*ALL) - Automatique
Ce que ça fait :
Exporte toutes les procédures marquées export dans tous les modules
Génère automatiquement une signature
Avantages :
✅ Simple, pas de fichier binder à gérer
✅ Bon pour prototypage rapide
Inconvénients :
❌ Aucun contrôle sur ce qui est exporté
❌ Signature auto-générée (pas de versioning)
❌ Impossible de gérer *CURRENT + *PRV
❌ Pas de documentation des exports
Quand utiliser :
Phase de développement/prototypage
*SRVPGM interne non partagé
Petit projet mono-personne
EXPORT(*SRCFILE) - Contrôlé ✅ Recommandé
Ce que ça fait :
Exporte uniquement ce qui est listé dans STRPGMEXP
Utilise la signature définie dans le binder source
Avantages :
✅ Contrôle total sur les exports
✅ Versioning avec *CURRENT + *PRV
✅ Documentation intégrée
✅ Sécurité (pas d'export accidentel)
Inconvénients :
⚠️ Fichier supplémentaire à maintenir
⚠️ Doit être synchronisé avec les procédures réelles
Quand utiliser :
Production
*SRVPGM partagé entre équipes
Projet avec versioning strict
Toujours recommandé pour du code professionnel
Bonne pratique #7 : Validation des exports
Problème : Export listé mais procédure inexistante
// QSRVSRC/CUSTOMER
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(procInexistante) // ← ERREUR : n'existe pas !
ENDPGMEXP
```
**Résultat lors de CRTSRVPGM :**
```
CPF5D02: Symbol procInexistante not found
Solution : Script de validation
-- Vérifier que tous les exports existent réellement
-- (nécessite de parser le binder source, exemple simplifié)
-- Liste des procédures effectivement exportées
SELECT DISTINCT TRIM(EXPORT_SYMBOL_NAME) AS PROCEDURE_NAME
FROM TABLE(QSYS2.SRVPGM_EXPORT_INFO(
SERVICE_PROGRAM_LIBRARY => 'MYLIB',
SERVICE_PROGRAM => 'CUSTOMER'
))
ORDER BY PROCEDURE_NAME;
Intégration dans build :
/* Script de build */
PGM
/* 1. Compiler les modules */
CRTRPGMOD MODULE(MYLIB/CUSTVAL) …
CRTRPGMOD MODULE(MYLIB/CUSTCRUD) ...
/* 2. Créer le SRVPGM */
CRTSRVPGM SRVPGM(MYLIB/CUSTOMER) +
SRCFILE(MYLIB/QSRVSRC) +
SRCMBR(CUSTOMER) +
EXPORT(*SRCFILE)
MONMSG MSGID(CPF5D02) EXEC(DO)
SNDPGMMSG MSG('Export introuvable dans binder source !') +
MSGTYPE(*ESCAPE)
ENDDO
/* 3. Valider les exports */
RUNSQL SQL('SELECT COUNT(*) FROM TABLE(...)')
ENDPGM
## Bonne pratique #8 : Convention de nommage des signatures
### Format recommandé
```
[BIBLIOTHEQUE]_[SRVPGM]_V[MAJOR]_[MINOR]_[PATCH/DATE]
Exemples :
// Avec patch version
SIGNATURE('MYLIB_CUSTOMER_V2_0_1')
// Avec date
SIGNATURE('MYLIB_CUSTOMER_V2_0_20260122')
// Court (si lib = srvpgm)
SIGNATURE('CUSTOMER_V2_0_0')
// Avec suffixe fonctionnel
SIGNATURE('CUSTOMER_CRUD_V1_0_0')
SIGNATURE('CUSTOMER_VALID_V1_0_0')
❌ À éviter
// Trop générique
SIGNATURE('CUSTOMER')
// Pas de version
SIGNATURE('CUSTOMER_PROD')
// Version non standard
SIGNATURE('CUSTOMER_VERSION2')
// Trop long (max 256 caractères mais illisible)
SIGNATURE('MYCOMPANY_MYLIB_CUSTOMER_SRVPGM_VERSION_2_POINT_0_RELEASE_CANDIDATE_1')
Bonne pratique #9 : Gestion des data areas / variables exportées
Exporter des variables (rare mais possible)
// Module CUSTCFG
**free
ctl-opt nomain;
// Variable globale exportée
dcl-s tauxTVADefaut packed(5:2) export;
dcl-proc initConfig export;
tauxTVADefaut = 20.0;
end-proc;
Binder source :
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CONFIG_V1_0_0')
EXPORT SYMBOL(initConfig)
EXPORT SYMBOL(tauxTVADefaut) // Export de la variable
ENDPGMEXP
Utilisation :
// Programme utilisateur
**free
/copy qrpglesrc,config_h
dcl-s montantTTC packed(11:2);
initConfig();
montantTTC = 100.0 * (1 + tauxTVADefaut / 100);
// montantTTC = 120.00
⚠️ Attention : Variables exportées = état global
Problèmes potentiels :
❌ État partagé entre tous les programmes
❌ Difficile à tester
❌ Risques de concurrence
Recommandation : Préférer des procédures getter/setter plutôt que des variables exportées.
// ✅ Meilleure approche
dcl-proc getTauxTVADefaut export;
dcl-pi *n packed(5:2);
end-pi;
return 20.0;
end-proc;
dcl-proc setTauxTVADefaut export;
dcl-pi *n;
nouveauTaux packed(5:2) const;
end-pi;
// Validation + setter
end-proc;
Bonne pratique #10 : Template de binder source
Template complet réutilisable
**free
//===========================================================
// [NOM_SRVPGM] - Export definitions
//
// Description:
// [Courte description de la responsabilité du SRVPGM]
//===========================================================
// Version [MAJOR].[MINOR].[PATCH] - CURRENT ([DATE])
//
// Changes in this version:
// - [Liste des changements]
//===========================================================
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('[NOM]_V[X]_[Y]_[Z]')
//--- V1.0 - Initial exports ([DATE]) —
EXPORT SYMBOL(procedure1)
EXPORT SYMBOL(procedure2)
//--- V2.0 - New features ([DATE]) —
EXPORT SYMBOL(procedure3)
EXPORT SYMBOL(procedure4)
ENDPGMEXP
//===========================================================
// Version [MAJOR_PRV].[MINOR_PRV].[PATCH_PRV] - PREVIOUS
// Maintained for backward compatibility
// Deprecated: [Date de fin de support prévue]
//===========================================================
STRPGMEXP PGMLVL(*PRV) SIGNATURE('[NOM]_V[X]_[Y]_[Z]')
EXPORT SYMBOL(procedure1)
EXPORT SYMBOL(procedure2)
ENDPGMEXP
Exemple concret d'utilisation
**free
//===========================================================
// CUSTOMER - Export definitions
//
// Description:
// Gestion des clients: validation, CRUD, scoring
// Change history:
// V1.0.0 - 2024-03-15 - Pacôme - Création initiale (validation + CRUD)
// V2.0.0 - 2025-06-10 - Abélar - Ajout scoring et limites de crédit
// V3.0.0 - 2026-01-22 - Hégésipe - Gestion avancée et archivage
//===========================================================
// Version 3.0.0 - CURRENT (2026-01-22)
//
// Changes in this version:
// - Added creerClientComplet (full client creation with address)
// - Added obtenirHistoriqueClient (client history retrieval)
// - Added archiverClient (soft delete with audit trail)
//===========================================================
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTOMER_V3_0_0')
//--- V1.0 - Initial exports (2024-03-15) —
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
//--- V2.0 - Scoring & Credit (2025-06-10) —
EXPORT SYMBOL(calculerScoreFidelite)
EXPORT SYMBOL(verifierLimiteCredit)
//--- V3.0 - Advanced Management (2026-01-22) —
EXPORT SYMBOL(creerClientComplet)
EXPORT SYMBOL(obtenirHistoriqueClient)
EXPORT SYMBOL(archiverClient)
ENDPGMEXP
//===========================================================
// Version 2.0.0 - PREVIOUS (2025-06-10)
//
// Maintained for backward compatibility
// Deprecated: End of support planned for 2027-06-10
//===========================================================
STRPGMEXP PGMLVL(*PRV) SIGNATURE('CUSTOMER_V2_0_0')
EXPORT SYMBOL(validerEmail)
EXPORT SYMBOL(validerSiret)
EXPORT SYMBOL(creerClient)
EXPORT SYMBOL(supprimerClient)
EXPORT SYMBOL(calculerScoreFidelite)
EXPORT SYMBOL(verifierLimiteCredit)
ENDPGMEXP
```
## Checklist finale : STRPGMEXP
Avant de compiler le *SRVPGM avec EXPORT(*SRCFILE) :
```
☐ Signature versionnée (format: NOM_VMAJOR_MINOR_PATCH)
☐ Commentaires de version avec date et auteur
☐ Exports groupés par version d'ajout
☐ Ordre des exports JAMAIS modifié (ajouts à la fin)
☐ PGMLVL(*PRV) si besoin de compatibilité
☐ Documentation du changelog
☐ Validation que tous les exports existent réellement
☐ Convention de nommage cohérente
☐ Plan de dépréciation si retrait prévu
Résumé ultra-synthétique
STRPGMEXP - Bonnes pratiques :
Signature versionnée : CUSTOMER_V2_0_0 (pas juste CUSTOMER)
Multi-niveaux : *CURRENT + *PRV pour compatibilité
Organisation : Grouper exports par version, commenter
Dépréciation progressive : CURRENT → PRV → Retrait total
Ordre invariant : Ne JAMAIS changer l'ordre, toujours ajouter à la fin
*EXPORT(SRCFILE) : Privilégier vs *ALL pour le contrôle
Validation : Vérifier que tous les exports existent
Nommage cohérent : Standard d'équipe pour signatures
Variables exportées : Éviter, préférer getters/setters
Template : Utiliser un template avec changelog
Le STRPGMEXP est le contrat d'interface → A traiter avec autant de soin qu'une API REST publique ! 📜
✅ Architecture en couches (Infrastructure → Services → Métier)
✅ Détection des dépendances circulaires (SQL)
✅ Gestion des modules (ajout, modification)
✅ Procédures privées vs publiques
✅ STRPGMEXP et versioning sémantique


Commentaires
Enregistrer un commentaire