Application des principes SOLID et Clean Code dans un système ERP Spring Boot complexe
Dans un ERP développé avec Spring Boot, la croissance fonctionnelle accroît la complexité : domaines multiples, règles métier évolutives, intégrations hétérogènes et volumes croissants. Pour préserver la maintenabilité, la robustesse et la testabilité, l’application combinée des principes SOLID et des pratiques Clean Code constitue un levier déterminant.
Contexte : pourquoi SOLID et Clean Code sont critiques en ERP
Un ERP centralise des cas d’usage tels que la gestion des commandes, facturation, achats, inventaire, préparation logistique ou encore synchronisation avec des systèmes externes. En conséquence, les changements de spécifications peuvent impacter de nombreux modules. Sans discipline d’architecture, le code se fragmente, la duplication augmente et les régressions deviennent coûteuses.
Les principes SOLID réduisent l’effet domino des modifications, tandis que les règles de Clean Code améliorent la lisibilité et facilitent la collaboration.
Approche d’architecture : appliquer SOLID au niveau du design
S : Single Responsibility Principle (SRP)
Chaque classe devrait porter une seule responsabilité métier ou technique. En ERP, cela signifie séparer clairement : la validation, la transformation de données, la persistance, la publication d’événements, et l’orchestration transactionnelle.
Exemple (antipattern) : un service unique “God Service” qui valide, calcule, persiste et notifie.
// Anti-pattern : trop de responsabilités
public class OrderService {
public void createOrder(OrderRequest req) {
validate(req);
calculateTotals(req);
save(req);
publishNotification(req);
}
}
Exemple (pattern) : découpage en composants spécialisés.
public class OrderValidator {
public void validate(OrderRequest req) { /* règles de validation */ }
}
public class OrderPricingCalculator {
public Money calculateTotal(OrderRequest req) { /* calculs */ }
}
public class OrderRepositoryAdapter {
public OrderEntity save(OrderEntity entity) { /* persistance */ }
}
public class OrderEventPublisher {
public void publishCreated(Order order) { /* événements */ }
}
L’orchestrateur peut rester minimal, uniquement chargé de coordonner les briques, en conservant la responsabilité d’ensemble limitée à la “création de commande”.
O : Open/Closed Principle (OCP)
Les composants doivent être ouverts à l’extension, fermés à la modification. Dans un ERP, des variantes apparaissent fréquemment : politiques de calcul selon pays, règles d’éligibilité selon segment client, ou modes de préparation logistique.
Pour éviter les if/else massifs, l’utilisation de stratégies et d’une sélection dynamique améliore fortement la maintenabilité.
public interface PricingStrategy {
Money calculate(OrderRequest req);
boolean supports(OrderRequest req);
}
public class DefaultPricingStrategy implements PricingStrategy {
@Override
public Money calculate(OrderRequest req) { /* ... */ }
@Override
public boolean supports(OrderRequest req) { return true; }
}
public class CountrySpecificPricingStrategy implements PricingStrategy {
@Override
public Money calculate(OrderRequest req) { /* ... */ }
@Override
public boolean supports(OrderRequest req) {
return "FR".equals(req.getCountryCode());
}
}
L’ajout d’une nouvelle règle se fait par création d’une nouvelle stratégie plutôt que modification des services existants.
L : Liskov Substitution Principle (LSP)
Les sous-types ne doivent pas rompre les attentes des clients. En pratique, cela implique d’éviter : les surcharges qui changent le contrat (exceptions inattendues, comportements incompatibles), ou les classes héritées nécessitant des préconditions invisibles.
Pour limiter les substitutions risquées, l’approche par composition est souvent préférable à l’héritage, surtout dans les services métier.
I : Interface Segregation Principle (ISP)
Les interfaces doivent rester petites et centrées sur des besoins spécifiques. Les adapters vers l’infrastructure peuvent ainsi se subdiviser : lecture vs écriture, tâches de synchronisation vs tâches de calcul.
public interface OrderReader {
OrderDto findById(String id);
}
public interface OrderWriter {
OrderEntity save(OrderEntity entity);
}
Dans un ERP, cette séparation clarifie les responsabilités et simplifie les tests (mocks plus ciblés, moins de méthodes inutilisées).
D : Dependency Inversion Principle (DIP)
Les modules de haut niveau ne doivent pas dépendre de détails concrets. Les services métier doivent dépendre d’abstractions : ports (interfaces) côté domaine, et implémentations côté infrastructure (DB, messaging, API externes).
public interface InventoryPort {
void reserveStock(String sku, int qty);
}
@org.springframework.stereotype.Service
public class InventoryAdapter implements InventoryPort {
@Override
public void reserveStock(String sku, int qty) {
// Appel DB ou service externe
}
}
Les services ERP restent testables sans dépendre de la technologie d’accès aux données ou des clients externes.
Clean Code : conventions qui protègent la maintenabilité
Nommage expressif
Les noms doivent refléter le rôle métier. Un champ status gagne à être accompagné de contextes : orderStatus, shipmentStatus, invoiceState. Les méthodes doivent être verbales et précises : validatePaymentEligibility, calculateTaxAmount, plutôt que process ou handle.
Fonctions courtes et orientées intention
Les méthodes longues masquent les invariants métier. La décomposition doit être faite pour que chaque fonction exprime une intention claire.
public void createInvoice(InvoiceRequest req) {
var validated = validateRequest(req);
var invoice = buildInvoice(validated);
applyTaxRules(invoice);
persist(invoice);
publishInvoiceCreated(invoice);
}
Réduction de la duplication et des “codes smells”
Dans un ERP, les duplications apparaissent souvent entre modules : mapping DTO↔Entity, conversions de montants, normalisation d’adresses, gestion des statuts. Des utilitaires dédiés et des mappers explicites limitent les divergences.
Les conditions complexes doivent être encapsulées : règles métier isolées, tableaux de décisions, chaînes de responsabilité ou stratégies.
Gestion robuste des exceptions
Les erreurs doivent porter un sens métier. Une exception technique pure est insuffisante pour l’analyse opérationnelle. Un modèle typé peut différencier : validation, indisponibilité d’intégration, conflits de concurrence, ou échec de transaction.
public class BusinessRuleViolationException extends RuntimeException {
public BusinessRuleViolationException(String message) {
super(message);
}
}
Tests : structuration pour éviter les régressions
La qualité du design se mesure aussi par la testabilité. Les ports (DIP) facilitent les tests unitaires. Les règles métier peuvent être validées par des tests de paramètres (table-driven tests) pour couvrir les variantes.
Les composants d’orchestration peuvent être testés avec des doubles (mocks) des ports, tandis que les adapters peuvent être testés avec tests d’intégration (containers, base de données de test).
Concrétiser : organisation typique d’un projet Spring Boot ERP
Une structure efficace sépare la couche de domaine, la couche application et l’infrastructure. L’idée centrale consiste à maintenir le cœur métier proche des abstractions et éloigné des détails.
Exemple de découpage par “couches” et “ports/adapters”
// Domaine
package com.company.erp.domain.order;
// Application (orchestration)
package com.company.erp.application.order;
// Ports (abstractions)
package com.company.erp.application.ports;
// Infrastructure (implémentations)
package com.company.erp.infrastructure.persistence;
package com.company.erp.infrastructure.messaging;
Le contrôleur REST ne doit pas contenir la logique métier. Il convertit, délègue, et renvoie un résultat. La complexité ERP est gérée dans les services applicatifs et les règles du domaine.
Mesures de qualité : indicateurs pratiques
Pour vérifier l’application réelle de SOLID et Clean Code, des indicateurs peuvent être suivis :
- Nombre de classes “God” : réduire drastiquement les responsabilités multiples.
- Couplage détecté : diminuer la dépendance directe entre modules métier et infrastructure.
- Couverture de tests : viser les règles métier et les orchestrations critiques.
- Temps de build et fréquence des régressions : baisse attendue en cas de bonne modularisation.
- Complétude du langage : noms de méthodes/variables explicites et cohérents.
Conclusion
Dans un ERP Spring Boot complexe, la combinaison SOLID et Clean Code offre un cadre concret pour limiter l’effet domino des changements, renforcer la testabilité et améliorer la compréhension du système. Le bénéfice principal apparaît à moyen terme : plus de stabilité, des évolutions plus rapides, et une dette technique contenue.
Point clé : l’architecture doit guider les changements, pas l’inverse.
À propos de l'auteur
Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.
Contact : latygueyesamba@gmail.com | Dakar, Sénégal