Affichage des articles dont le libellé est Génie logiciel. Afficher tous les articles
Affichage des articles dont le libellé est Génie logiciel. Afficher tous les articles

jeudi 13 novembre 2025

Refonte ou refactoring

Refonte vs Refactoring : Quand réécrire, quand faire du refactoring ?

Face à un code legacy, la tentation de tout réécrire est forte. Pourtant, le refactoring et la refonte sont deux stratégies radicalement différentes, avec des impacts opposés sur le risque, le temps et la valeur métier.

Point à prendre en considération

Langage, Framework, Librairie: est-ce qu'ils sont désuet, est-t'il possible de trouver des ressources

Usage interne / externe: il peut avoir plus d'exigence si l'application est utilisé par des clients 

Utilisation: est-ce que l'application est très utilisé

Maintenance: il y a beaucoup de maintenance à faire, temps pour maîtriser le système

Documentation: technique, fonctionnel


Refactoring : Changer sans changer

Le refactoring, est une modification du code interne sans changer son comportement externe. C’est l’art de rendre le code lisible, modulaire, testable sans toucher à la logique métier.

Quand l’utiliser ?

Le code fonctionne, mais est illisible, répétitif ou mal structuré.

Les tests unitaires existent ou peuvent être ajoutés

Le framework ou la technologie sous-jacente est encore pertinent

L’équipe connaît suffisamment le domaine pour identifier les points de fragilité.

Trouver des ressources pour travailler sur le système n'est pas un enjeux

Avantage

Moins de risque, pas de perte de fonctionnalité

Amélioration ciblé, pas à pas

Désavantage

Il peut devenir un gouffre temporel. Pourquoi ? Parce que comprendre un code mal documenté, avec des dépendances cachées et des comportements implicites, prend souvent plus de temps que de le réécrire.

C’est ce que j’ai vécu sur plusieurs projet legacy. Le code ne respectait pas les spécifications et le code avait très peu été testé.

Refonte: Repartir de zéro

Une refonte consiste à reconstruire entièrement un système, souvent avec une nouvelle architecture, un nouveau langage, une nouvelle base de données, ou un nouveau framework (ex. : passer d’un monolithe C++ à un service Spring Boot.

Quand l’utiliser ?

Le code est si complexe qu’il devient une « boîte noire » : personne ne comprend plus ce qu’il fait.
Le framework, librairie est obsolète, par exemple JBoss Seam.
Les besoins métier ont évolué au point que le code existant ne peut plus évoluer sans cassures.
Il n’y a aucune documentation, ni tests automatisés  et personne ne veut plus y toucher.

Avantage 

Mise en place de bonne pratique plus aisément

Opportunité de revoir des règles métier

Désavantage

La perte de connaissance tacite : si personne n’a documenté le comportement réel du système
Le temps de développement est long


Réalité

Dans la réalité, le choix n'est pas toujours évident. Il y a un aspect technologique, ressource, économique et temporel à prendre en considération. J'ai été confronté à mainte reprises aux deux situations avec des cas hybrides.

Cas 1 

Refonte d'un système qui utilisait JEE JBoss avec Seam. Est-ce que ce système avec réellement besoin de JEE? Non, long et lourd à déployer. Seam n'est plus supporté par Red Hat depuis longtemps. Le gros avantage que le système avait c'est la documentation. Un point qui a facilité la refonte. 

Spring Boot a été sélectionné pour la refonte. Passage de Java 1.6 à 21. La qualité du code a été grandement améliorer, sans compté que beaucoup de code a pu être supprimer grace à Spring Data, Lombok et Mapstruct. Il y a eu quand même de nombreuse portion de l'ancien système qui a pu être réutilisé directement et d'autre qui a été améliorer. La documentation technique a été accrue.

Fait à noter, la refonte a pris plus de temps que lorsque le système avait été créer la première fois. Afin de garder les mêmes comportement à fonctionalité égale, il fallait s'assurer que l'existant faisait bien ce qu'il était supposé faire. Des cas très poussé on pu être testé plus en profondeur et permis de décelé des anomalies qui existait depuis des années. Elles ont été fixé dans le nouveau système.

Cas 2

Refonte d'un système qui utilisait Ruby. Il était difficile de trouver des ressources pour ce langage. Des soucis de performance. Pas de documentation fonctionnel.

Spring Boot a été sélectionné pour la réécrire du système, ce qui a permis de trouver des ressources aisément sur le marché. Le modèle de donnée a été complètement revue afin d'être en mesure de supporter de nouvelle exigences fonctionnels.

Le système final comportait plus de 400 000 ligne de code. Certaine partie qui utilisait perl, bash ont été aussi convertie en Java quand il y avait possibilité

Cas 3

Refonte complète de système de recherche d'entrant (courriel, facture, document) dans une entreprise. Changement total du langage de programmation, base de donnée et même architecture cpu qui allait faire tourner l'application. Le cas qui arrive pratiquement jamais dans une vie.

Même le mix de spring / struts avec du GWT était très audacieux. Il y avait aucune compatibilité qui devait être assuré avec l'ancien système, ce qui a réduire considérablement la complexité du projet.

Cas 4

Dans un autre système d'appel d'offre, c'est l'approche refactoring qui a été pris en considération. Le système utilisait vbscript. Encore une fois peu de documentation, mais constamment des modifications devaient être apporté au système. L'entreprise ne voulait pas investir dans le système. Peu de personne voulait toucher au code. Il a été décidé d'investir un peu de temps lors de correction d'anomalie et ajout de fonctionnalité pour améliorer le système.

Conclusion

Le choix n'est pas technique, il est stratégique. Il doit se baser sur une analyse des coûts, des risques et des bénéfices métier.

Refactoring : Pour améliorer ce qui marche.

Refonte : Pour reconstruire ce qui ne marche plus.


Le conseil ultime : Avant de vous lancer, documentez ce que vous avez compris. Parfois, le meilleur refactoring est de s'arrêter pour planifier une refonte ciblée. Surtout, méfiez-vous du piège classique : confondre la difficulté de comprendre un système legacy avec la facilité supposée de le réécrire.

lundi 10 novembre 2025

Gérer le cycle de vie d’un plugin avec Spring Statemachine

Les machines à états sont un sujet très peu discuté, peut-être que le côté mathématique fait peur à certain. Pour faire suite à notre article sur les plugin, nous allons mettre en place une machine à état pour suivre son cycle de vie. 

Le projet State Machine de Spring sera utilisé pour la suite de cet article.

Un plugin peut avoir différent état 

  • INSTALLED, 
  • ACTIVE, 
  • INACTIVE, 
  • UPDATING, 
  • FAILED,
  • UNINSTALLED

Dans un système de gestion de plugin, différente action devrait être mis en place :

  • Téléchargement du JAR
  • Vérification des dépendances
  • Initialisation du contexte Spring
  • Enregistrement des routes ou services
  • Nettoyage des ressources

Chaque transition doit être explicitement autorisée. 

  • On ne peut pas désactiver un plugin qui n’est pas activé.
  • On ne peut pas réactiver un plugin désinstallé.
  • Une erreur pendant l’activation doit mener à l’état FAILED.

Gérer cela avec des if/else mène à un code difficile à maintenir. Une machine à états permet de modéliser ce cycle clairement.

État et évènement

Liste des états


public enum PluginState {
    INSTALLED,
    ACTIVE,
    INACTIVE,
    UPDATING,
    FAILED,
    UNINSTALLED
}

Listes des évènements


public enum PluginEvent {
    INSTALL,
    ACTIVATE,
    DEACTIVATE,
    UPDATE,
    UNINSTALL,
    ERROR
}

Le plugin

Cette classe a été allégé

public class Plugin {

    private String id;
    private PluginState state;

    public Plugin(String id) {
        this.id = id;
        this.state = PluginState.INSTALLED; // Après installation
    }

    public String getId() { return id; }
    public PluginState getState() { return state; }
    public void setState(PluginState state) { this.state = state; }
}
Dans la réalité, il y aurait fort probablement son nom, url, version.

Action du plugin

Activation du plugin

@Component
public class ActivatePluginAction implements Action<PluginState, PluginEvent> {
    private static final Logger log = LoggerFactory.getLogger(ActivatePluginAction.class);
    @Override
    public void execute(StateContext<PluginState, PluginEvent> context) {
        Plugin plugin = (Plugin) context.getMessage().getHeaders().get("plugin");
        log.info("Activation du plugin : " + plugin.getId());
    }
}

Désactivation du plugin


@Component
public class DeactivatePluginAction implements Action<PluginState, PluginEvent> {
    private static final Logger log = LoggerFactory.getLogger(DeactivatePluginAction.class);
    @Override
    public void execute(StateContext<PluginState, PluginEvent> context) {
        Plugin plugin = (Plugin) context.getMessage().getHeaders().get("plugin");
        log.info("Plugin désactivé : " + plugin.getId());
    }
}

Erreur du plugin


@Component
public class FailPluginAction implements Action<PluginState, PluginEvent> {
    private static final Logger log = LoggerFactory.getLogger(FailPluginAction.class);
    @Override
    public void execute(StateContext<PluginState, PluginEvent> context) {
        Plugin plugin = (Plugin) context.getMessage().getHeaders().get("plugin");
        log.warn("Plugin en échec : " + plugin.getId());
        plugin.setState(PluginState.FAILED);
    }
}

Configuration de la machine à états

La classe PluginStateMachineConfig est le coeur de la machine à états.
Elle configure
  • Les états possibles du plugin
  • Les transitions autorisées entre ces états
  • Les événements qui déclenchent ces transitions
  • Les actions à exécuter lors de certaines transitions

@Configuration
@EnableStateMachineFactory
public class PluginStateMachineConfig extends StateMachineConfigurerAdapter<PluginState, PluginEvent> {

    private final ActivatePluginAction activateAction;
    private final DeactivatePluginAction deactivateAction;
    private final FailPluginAction failAction;

    public PluginStateMachineConfig(ActivatePluginAction activateAction,
                                    DeactivatePluginAction deactivateAction,
                                    FailPluginAction failAction) {
        this.activateAction=activateAction;
        this.deactivateAction=deactivateAction;
        this.failAction=failAction;
    }

    @Override
    public void configure(StateMachineStateConfigurer<PluginState, PluginEvent> states) throws Exception {
        states
                .withStates()
                .initial(PluginState.INSTALLED)
                .states(EnumSet.allOf(PluginState.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<PluginState, PluginEvent> transitions) throws Exception {
        transitions
                .withExternal()
                .source(PluginState.INSTALLED)
                .target(PluginState.ACTIVE)
                .event(PluginEvent.ACTIVATE)
                .action(activateAction)
                .and()
                .withExternal()
                .source(PluginState.ACTIVE)
                .target(PluginState.INACTIVE)
                .event(PluginEvent.DEACTIVATE)
                .action(deactivateAction)
                .and()
                .withExternal()
                .source(PluginState.INACTIVE)
                .target(PluginState.ACTIVE)
                .event(PluginEvent.ACTIVATE)
                .action(activateAction)
                .and()
                .withExternal()
                .source(PluginState.ACTIVE)
                .target(PluginState.UPDATING)
                .event(PluginEvent.UPDATE)
                .and()
                .withExternal()
                .source(PluginState.UPDATING)
                .target(PluginState.ACTIVE)
                .event(PluginEvent.ACTIVATE)
                .action(activateAction)
                .and()
                .withExternal()
                .source(PluginState.INSTALLED)
                .target(PluginState.UNINSTALLED)
                .event(PluginEvent.UNINSTALL)
                .and()
                .withExternal()
                .source(PluginState.ACTIVE)
                .target(PluginState.FAILED)
                .event(PluginEvent.ERROR)
                .action(failAction)
                .and()
                .withExternal()
                .source(PluginState.INACTIVE)
                .target(PluginState.FAILED)
                .event(PluginEvent.ERROR)
                .action(failAction)
                .and()
                .withExternal()
                .source(PluginState.FAILED)
                .target(PluginState.UNINSTALLED)
                .event(PluginEvent.UNINSTALL);
    }
}

Diagramme du workflow

Ce diagramme affiche les différents états et les transitions possible.



Cycle de vie du plugin

Cette classe crée, stocke et orchestre les transitions d'états des plugins via Spring StateMachine. Une map sert de stockage temporaire des plugins.

Lors de l'activation d'un plugin, on 

  • Récupère le plugin via son id.
  • Crée une instance de machine à états via la factory.
  • Construit un message contenant l’événement ACTIVATE, avec le plugin comme contexte.
  • Envoie l’événement à la machine.
  • Si la transition échoue, une exception est levée (ex. : INSTALLED → DEACTIVATE n’est pas autorisé).
  • Met à jour l’état du plugin avec le nouvel état de la machine.
  • La machine à états garantit que seul ce qui est autorisé se produit.

@Service
public class PluginService {

    private final Map<String, Plugin> plugins = new ConcurrentHashMap<>();

    @Autowired
    private StateMachineFactory<PluginState, PluginEvent> stateMachineFactory;

    public Plugin createPlugin(String id) {
        Plugin plugin = new Plugin(id);
        plugins.put(id, plugin);
        return plugin;
    }

    public Plugin getPlugin(String id) {
        return plugins.get(id);
    }

    public void activate(String id) {
        Plugin plugin = getPlugin(id);
        if (plugin == null) {
            throw new IllegalArgumentException("Plugin non trouvé : " + id);
        }

        StateMachine<PluginState, PluginEvent> stateMachine = stateMachineFactory.getStateMachine();

        Message<PluginEvent> message = MessageBuilder
                .withPayload(PluginEvent.ACTIVATE)
                .setHeader("plugin", plugin)
                .build();

        stateMachine.sendEvent(message);
        plugin.setState(stateMachine.getState().getId());
    }

    public void deactivate(String id) {
        Plugin plugin = getPlugin(id);
        if (plugin == null) {
            throw new IllegalArgumentException("Plugin non trouvé : " + id);
        }

        StateMachine<PluginState, PluginEvent> stateMachine = stateMachineFactory.getStateMachine();

        Message<PluginEvent> message = MessageBuilder
                .withPayload(PluginEvent.DEACTIVATE)
                .setHeader("plugin", plugin)
                .build();

        stateMachine.sendEvent(message);
        plugin.setState(stateMachine.getState().getId());
    }

    public void uninstall(String id) {
        Plugin plugin = getPlugin(id);
        if (plugin == null) {
            throw new IllegalArgumentException("Plugin non trouvé : " + id);
        }

        StateMachine<PluginState, PluginEvent> stateMachine = stateMachineFactory.getStateMachine();

        Message<PluginEvent> message = MessageBuilder
                .withPayload(PluginEvent.UNINSTALL)
                .setHeader("plugin", plugin)
                .build();

        stateMachine.sendEvent(message);
        plugin.setState(stateMachine.getState().getId());
        plugins.remove(id); // Supprimer du stockage
    }

}

La machine à états valide les transitions. Si un plugin est dans l’état INSTALLED, une tentative de DEACTIVATE échouera.

Il est facile d'ajouter des états / transitions

Test

Un controlleur a été mis en place pour pouvoir tester plus aisément les états.


@RestController
@RequestMapping("/plugins")
public class PluginController {

    private final PluginService pluginService;

    public PluginController(PluginService pluginService) {
        this.pluginService=pluginService;
    }

    @PostMapping("/create/{id}")
    public String create(@PathVariable String id) {
        Plugin plugin = pluginService.createPlugin(id);
        return "Plugin créé : " + id + ", état initial : " + plugin.getState();
    }

    @PostMapping("/{id}/activate")
    public String activate(@PathVariable String id) {
        pluginService.activate(id);
        Plugin plugin = pluginService.getPlugin(id);
        return "Plugin activé : " + id + ", état : " + plugin.getState();
    }

    @PostMapping("/{id}/deactivate")
    public String deactivate(@PathVariable String id) {
        pluginService.deactivate(id);
        Plugin plugin = pluginService.getPlugin(id);
        return "Plugin désactivé : " + id + ", état : " + plugin.getState();
    }

    @PostMapping("/{id}/uninstall")
    public String uninstall(@PathVariable String id) {
        pluginService.uninstall(id);
        return "Plugin désinstallé : " + id;
    }

    @GetMapping("/{id}")
    public String getStatus(@PathVariable String id) {
        Plugin plugin = pluginService.getPlugin(id);
        if (plugin == null) {
            return "Plugin non trouvé";
        }
        return "Plugin " + id + ", état : " + plugin.getState();
    }
}

Par la suite, vous pouvez utiliser ce controller pour tester différents scénarios

Créer un plugin

curl -X POST http://localhost:8080/plugins/create/demo

Tenter de le désactiver (état INSTALLED)

curl -X POST http://localhost:8080/plugins/demo/deactivate

Activer le plugin

curl -X POST http://localhost:8080/plugins/demo/activate

Désactiver 

curl -X POST http://localhost:8080/plugins/demo/deactivate

Tenter de réactiver un plugin désinstallé

curl -X POST http://localhost:8080/plugins/demo/uninstall
curl -X POST http://localhost:8080/plugins/demo/activate

Conclusion

PluginStateMachineConfig est le plan de votre machine à états.
Il définit les règles du jeu : ce qui est autorisé, ce qui n’est pas, et ce qui se passe à chaque étape.
C’est une alternative claire et robuste aux if/else imbriqués, surtout dans des systèmes modulaires comme des plugins ou des workflows métier. 

Les sources sont disponible sur github

jeudi 6 novembre 2025

Construire un système de plugins en Java

Les CMS comme WordPress, Joomla ou Shopify ont popularisé une idée puissante : permettre à n’importe qui d’étendre une application sans toucher à son code source. Même si cela existait bien avant ces applications. Cette extensibilité repose sur un système de plugins (ou extensions), qui transforme une application figée en une plateforme vivante.

Mais est-il possible de reproduire ce modèle en Java, sans framework web, sans Spring, et sans magie ?
Oui ! Et c’est plus simple qu’on ne le pense.

Dans cet article, nous allons construire, pas à pas, un système de plugins modulaire en Java pur, inspiré des meilleures pratiques des CMS les plus utilisés au monde. Le résultat sera une application capable de charger dynamiquement des fonctionnalités tierces, de réagir à des événements, et de rester entièrement découplée de ses extensions.


Pourquoi un système de plugins ?


Avant de coder, posons les fondations :
  • Extensibilité : ajouter des fonctionnalités sans recompiler le cœur.
  • Découplage : le noyau ne dépend pas des plugins (et inversement).
  • Sécurité : les plugins tournent dans un cadre défini.
  • Maintenance : un plugin défectueux n’arrête pas toute l’application.
  • Écosystème : ouvrir la porte à une communauté de développeurs.

C’est exactement ce que font WordPress (avec ses 60 000+ plugins) ou Shopify (avec ses apps). En Java, on peut atteindre le même objectif grâce à trois piliers :
  1. Les interfaces (contrats),
  2. Le chargement dynamique de classes,
  3. Le mécanisme ServiceLoader (standard Java).

Étape 1 : Définir le contrat (l’API du noyau)

Tout commence par une interface. C’est le seul lien entre le cœur de l’application et les plugins.

public interface Plugin {
    String getName();
    void onLoad(Core core);
    void onEvent(String eventName, Object data);
}

Et une classe centrale qui orchestre tout :

import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;

public class Core {
    private static final Logger logger = Logger.getLogger(Core.class.getName());
    private final List<Plugin> plugins = new ArrayList<>();

    public void registerPlugin(Plugin plugin) {
        if (plugin == null) {
            logger.warning("Tentative d'enregistrement d'un plugin null");
            return;
        }
        plugins.add(plugin);
        safeCall(() -> plugin.onLoad(this), plugin.getName(), "onLoad");
    }

    public void fireEvent(String eventName, Object data) {
        for (Plugin plugin : plugins) {
            safeCall(() -> plugin.onEvent(eventName, data), plugin.getName(), "onEvent(\"" + eventName + "\")");
        }
    }

    // Méthode utilitaire pour exécuter du code de plugin de façon sécurisée
    private void safeCall(Runnable action, String pluginName, String context) {
        try {
            action.run();
        } catch (Exception e) {
            logger.log(
                Level.SEVERE,
                String.format(
                    "Le plugin '%s' a échoué lors de l'appel à %s : %s",
                    pluginName,
                    context,
                    e.toString()
                ),
                e // inclut la stack trace complète
            );
            // On continue, le plugin suivant est exécuté
        }
    }

    public void start() {
        logger.info("Démarrage de l'application...");
        fireEvent("app.start", null);
        fireEvent("user.register", "laboiteaprog@gmail.com");
        logger.info("Application terminée.");
    }
}

Ce Core est le cœur de notre application. Il ne sait rien des plugins concrets — il ne connaît que l’interface Plugin. C’est le principe du « program to an interface, not an implementation ».


Étape 2 : Créer un plugin

Imaginons un plugin qui envoie un email lorsqu’un utilisateur s’inscrit.

public class EmailNotifierPlugin implements Plugin {
    @Override
    public String getName() {
        return "EmailNotifier";
    }

    @Override
    public void onLoad(Core core) {
        System.out.println("Email Plugin chargé !");
    }

    @Override
    public void onEvent(String eventName, Object data) {
        if ("user.register".equals(eventName)) {
            System.out.println("Bienvenue envoyé à " + data);
        }
    }
}

Ce plugin est autonome. Il ne dépend que de Plugin et Core — deux classes que nous mettrons dans une API partagée.

Étape 3 : Découvrir les plugins avec ServiceLoader


Java offre depuis Java 6 un mécanisme standard pour charger des implémentations dynamiquement : java.util.ServiceLoader.

Il fonctionne ainsi :
  1. Chaque JAR de plugin contient un fichier de configuration.
  2. Ce fichier indique quelle classe implémente l’interface.
  3. L’application utilise ServiceLoader pour les découvrir automatiquement.

Le fichier de configuration

Dans le JAR du plugin, créez :
META-INF/services/Plugin

Contenu :
EmailNotifierPlugin

Si vous utilisez des packages, indiquez le nom qualifié complet : com.mesplugins.EmailNotifierPlugin.



Étape 4 : Charger et exécuter


Voici le point d’entrée de notre application :
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        Core core = new Core();

        // Découverte automatique des plugins
        ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
        for (Plugin p : loader) {
            core.registerPlugin(p);
        }

        core.start();
    }
}

Lorsque vous exécutez l’application avec les JARs des plugins dans le classpath, ils sont chargés automatiquement.


Plugin chargé !
Application démarrée
Bienvenue envoyé à laboiteaprog@gmail.com
Terminé


Aller plus loin

Chargement depuis un dossier

Pour imiter WordPress (/wp-content/plugins), chargez les plugins depuis un dossier à l’exécution :

File pluginDir = new File("plugins");
for (File jar : pluginDir.listFiles((d, name) -> name.endsWith(".jar"))) {
    URLClassLoader loader = new URLClassLoader(
        new URL[]{jar.toURI().toURL()},
        Main.class.getClassLoader()
    );
    ServiceLoader<Plugin> sl = ServiceLoader.load(Plugin.class, loader);
    sl.forEach(core::registerPlugin);
}

Désactivation

Les plugins qui ont échoué au chargement pourrait être désactivé. Une Map pourrait être utilisé pour cette fonctionalité.


Map<String, Integer> errorCount = new HashMap<>();
if (++errorCount.getOrDefault(pluginName, 0) > 3) {
    logger.warning("Plugin '" + pluginName + "' désactivé après trop d'erreurs.");
    // le retirer de la liste `plugins` (attention à la concurrence)
}

Timeout

Pour éviter qu’un plugin ne bloque indéfiniment (boucle infinie, appel réseau bloquant), vous pouvez exécuter les appels dans un Future avec délai :

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(action);
try {
    future.get(5, TimeUnit.SECONDS); // timeout de 5s
} catch (TimeoutException e) {
    future.cancel(true);
    logger.severe("Plugin '" + pluginName + "' a dépassé le délai autorisé.");
}

Conclusion

Un système de plugins n’est pas réservé qu'aux CMS PHP ou aux plateformes SaaS. En Java, avec  le mécanisme standard ServiceLoader, on peut créer une architecture modulaire, extensible et robuste exactement comme WordPress. D'ailleurs les IDE tel que netbeans, eclipse font usage des plugins. 

samedi 1 novembre 2025

Spring Events : la magie du pattern Observer

Spring a une solution simple pour découpler ton code sans ajouter de dépendances. 

Quelques annotations magiques... au final c'’est juste une implémentation propre du bon vieux pattern Observer qui semble sous utilisé dans la communauté Spring.

Le problème classique

Imaginons que lorsqu'une commande est passée, il faut envoyer un email, logger l’action, invalider un cache, et notifier un service d’analytics.

Si tu mets tout ça dans ta méthode placeOrder(), tu mélanges logique métier et effets secondaires, ton code devient difficile à tester, à maintenir, et à étendre.


La solution Spring : les événements

Spring te permet de publier un événement et de laisser d’autres composants réagir sans les connaître.

public record OrderPlacedEvent(Order order) {}

Mise en place de ton évènement

@Service
public class OrderService {

    private final ApplicationEventPublisher eventPublisher;

    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void placeOrder(Order order) {
        // ... logique métier
        eventPublisher.publishEvent(new OrderPlacedEvent(order));
    }
}

Réaction à l'évènement

@Component
public class EmailListener {
    @EventListener
    public void sendConfirmation(OrderPlacedEvent event) {
        // Envoie un courriel
    }
}

@Component
public class AuditListener {
    @EventListener
    public void logOrder(OrderPlacedEvent event) {
        // Écrit dans les logs ou une table d’audit
    }
}

Si tu as besoin d'ajouter un nouveau comportement, tu n'as pas besoin de modifier OrderService.

Attention aux pièges

  • Les listeners synchrones partagent la même transaction que l’émetteur. Si la transaction échoue, aucun effet ne doit persister.
  • Les listeners asynchrones ne peuvent pas participer à la transaction car il y a risque de divergence si tu modifies des données critiques.

Fonctionnalités avancées

Ajout de condition

@EventListener(condition = "#event.order.total > 1000")
public void notifyPremiumSupport(OrderPlacedEvent event) {
...
}

Il y a la possibilité d'imposer un ordre d'exécution

@Component
public class EmailListener {
    @EventListener
    @Order(1)
    public void sendConfirmation(OrderPlacedEvent event) {
        // Envoie un courriel
    }
} 

@Component
public class AuditListener {
    @EventListener
    @Order(2)
    public void logOrder(OrderPlacedEvent event) {
    // Écrit dans les logs ou une table d’audit
    }
}

Détail technique concernant l'ordre

  • Spring collecte tous les @EventListener écoutant un événement donné.
  • Il les trie selon :La valeur de @Order (plus petit = exécuté en premier). Si aucune @Order n’est spécifiée, la valeur par défaut est Ordered.LOWEST_PRECEDENCE (c’est-à-dire exécuté en dernier).
  • Le tri est strictement global au contexte applicatif, pas local à la classe ou au package.Spring permet de mettre des conditions

Pourquoi l’utiliser ?

  • Zéro dépendance externe (c’est dans Spring Core).
  • Typé, testable, et lisible.
  • Parfait pour les architectures modulaires ou les librairies réutilisables.

Il est possible d'utiliser spring modulith pour aller encore plus loin dans la communication de vos modules de votre application monolith.

Ce n’est pas de la magie… c’est du bon design, rendu simple par Spring.

jeudi 26 juin 2025

Concevoir un système d’annotations pour l’automatisation

À la suite de l'article concernant Automatisation d'interface utilisateur, j'ai reçu différents commentaires relatifs au choix qui avait ét fait. J'ai décidé d'écrire un autre article présentant différentes conceptions, avec leurs avantages et inconvénieunts respectifs

Annotation et convention over configuration

L'approche qui avait été adoptée était basée sur une annotation par responsabilité, avec le moins de choix possible. Un maximum d'automatisation avait été mis en place.


@Order(value = 9)
@HideField(value = SectionEnum.TABLE)
@Mandatory
private Integer groundElementOwnerId;

Les annotations indiquent 

  • que le champs serait affiché à la neuvième position.
  • que le champ serait caché dans la section table de la page.
  • que le champ est obligatoire

@Order(value = 7)
@TableStruture
private Map<String,String> properties;

Ici il y avait détection de la map et un tableau était affiché.




Différentes conventions avaient été mises en place.

Concernant les select, lorsque le nom de l'attribut contenait "id" et comportait plus de deux caractères, une tentative était faite pour rechercher un service au nom du dto et de trouver une méthode s'appelant findAll. Le résultat était mis dans le modèle et pouvait être utilisé dans thymeleaf.

Lorsqu'un traitement spécifique était nécessaire pour un champ, ce dernier pouvait être masqué via l'annotation et une gestion personnalisée était alors implémentée dans un template thymeleaf.

Cette première approche avait été simple en mettre place, car il y avait peu de cas spécifique à gérer. Le développeur pouvait aisément ajouter de nouveau dto.

La gestion de la construction de la structure dynamique était assez simple malgré les quelques annotations à gérer. Peu d'attribut était disponible dans chaune des annotations.


Annotation générique

Avec cette approche, une seule annotation est définie avec l'ensemble des attributs possibles. C'est possiblement la plus simple, même si le développeur doit fournir davantage d'informations. Le principal défaut est qu'il est possible que le développeur assigne des valeurs inexistantes  pour le type spécifié. Deplus, l'annotation a plusieurs responsabilités.

@FormField(type = FormFieldType.PHONE, order = 9, pattern = "999 999-9999")
private String phone;
    
@FormField(type = FormFieldType.SELECT, optionsProvider = "getCities", order = 5 )
private String city;
    
@FormField(type = FormFieldType.CHECKBOX, options = {"sportCheck:Sport", "musiqueCheck:Musique", "lectureCheck:Lecture"}, order = 15 )
private List<String> interest;

Une seule annotation facilite la construction de la structure dynamique. Seuls les attributs existants pour un type donnée sont pris en compte.


Annotation par responsabilité

Les responsabilités sont définies via différentes annotations. Le dto devient plus verbeux, et la gestion plus complexe pour construire la structure dynamique. C'est une version avancé que la première approche.

@FormField(type = FieldType.SELECT)
@SelectConfig(valueField = "id", labelField = "name")
private List<Ville> villes;

@FormField(type = FieldType.INPUT, placeholder = "+1 (555) 123-4567")
@FormStyle(cssClass="form-control")
@FormValidation(required=true, readonly=true, pattern="'mask' : '999 999-9999'")
@Order(8)
private String phone;

Annotation générique avec avec type spécifique

Chaque type dispose de sa propre annotation avec ses propres attributs. Ainsi, le développeur ne peut pas se tromper sur les propriétés applicables.

@HiddenInput(order=1)
private Long idEditeur;

@TextInput(minLength = 2, maxLength = 20, order=2)
private String name;

@TextInput(readonly=true, order=3)
private String age;

@SelectInput(optionsProvider = "getCities", required = true, order=4)
private String city

Approche assez simple à comprendre, cependant l'attribut order bien qu'utile pour la position d'affichage n'étant pas un attribut HTML, il vient briser le concept de séparation des responsabilités. Il pourrait être mis dans une autre annotation. Parfois un juste milieu est la meilleur solution.


Une librairie a été créé: https://github.com/marccollin/formatic


Quel approche au niveau des annotations préférez-vous?



jeudi 31 décembre 2020

Architecture hexagonale

Introduction

Dans une application conventionnel, il ne sera pas rare d'avoir une couche de contrôleur,  de service et de dao. Pour chacune de nos entités, une classe existera sous chacune de ces couches. Il est possible de se retrouver rapidement avec des centaines de classes. Ce type d'architecture utilise un modèle d'objet anémique, c'est-à-dire qu'il ne contient pas de logique métier.

Il n'est pas rare de voir un appel à la couche de persistance directement de la première couche.

Les entités, les domains object n'ont pas de règle métier. Elle est alors mise dans les classes de service, utilitaire. Ces classes chargent des entités, modifient leurs états et persistent le tout. Les services commencent à utiliser d'autres services et il n'est pas rare de se retrouver avec des classes de plusieurs milliers de lignes. Pour une petite application, de type CRUD, ça peut être plus rapide à développer, mais imaginer maintenant une application avec 200K, 300K... lignes de code.

C'est une architecture souvent basée sur le design de la base de données. Une modification au sein de celle-ci et des modifications à chaque couche est nécessaire.

Plus l'application prendra de l'expansion, plus il deviendra difficile de la modifier sans causer de multiples anomalies.

Une bonne architecture doit pouvoir s'adapter à de nouvelles exigences sans tout chambouler.

Une architecture hexagonale pourrait être une approche à songer pour débuter sans trop devoir tout changer si on désirait passer à des microservices par la suite.

Architecture hexagonale

Le but de ce type d'architecture est d'avoir un noyau relié à la logique métier. Tout le reste est externalisé tel que la sauvegarde de donnée ou bien la réception de donnée. Cette conception est priorisée en DDD: domain-driven design.  

 


Avantages

  • Indépendance de la couche métier aux autres couches, technologie
  • Test plus facile à effectuer

 Désavantage

  • Plus de code nécessaire
  • Structure plus complexe

 

Couche

Cette architecte implique de diviser l'application en trois couches:

  1. Application
  2. Domaine
  3. Infrastructure

Application

Cette couche est le point d’interaction de l’application. Il peut s'agir de contrôleur rest, de service de messages. C'est la porte d'entrée pour exécuter le domaine métier.  Cette portion est parfois appelée espace utilisateur.

Domaine

C'est l'implémentation de la couche métier, c'est le noyau central de l'application. Il se trouve aussi les interfaces de communication avec les deux autres couches. Il est possible de rencontrer cette couche sous l’appellation règle d'affaires.

Infrastructure

Cette couche possède l'implémentation pour sauvegarder dans une base de données, fichier, web service externe. C'est le point de sortie du domaine métier. Un synonyme pour cette couche est espace serveur.

Principe

Le domaine n'a pas connaissance de l'application et de l'infrastructure. C'est là qu'entrent en jeu les adapteurs et les ports. Les ports sont des interfaces que les adapteurs implémenteront. Chaque couche peut être testée individuellement, de plus l'utilisation d'interface ajoute un niveau d'indépendance au niveau de l'implémentation. Pour un test, les données pourraient être sauvegardées dans un fichier alors qu'en production la sauvegarde se ferait dans une base de données.

Contexte, limite d'un domaine

Contrairement à une application monolithique, l'architecture hexagonale nécessite de découper selon le contexte (bounded context). On se retrouve avec plusieurs petits modèles au lieu d'un modèle unique. 

Un contexte peut être vu comme les limites d'un sous-domaine dans une application auquel une certaine logique métier peut être appliqué.

Si on prend une application telle qu'amazon: il en existe plusieurs inventaire, prix, produit, commentaire.

 
L'interface graphique accède une multitude de microservice délimité par leur domaine. Si les instances du service de commentaire sont en arrêt, un client pourra tout de même acheter un livre.
 

Objet valeur (Value Object)

C'est un objet immuable qui décrit un aspect du domaine mais qui n'a pas d'identité. De la logique métier est fortement conseillé d'y être présent. Un âge ou bien une adresse pourrait être présenté de la sorte. Il faut considérer que deux VO sont égales si l'ensemble de ses champs sont identiques.

public final class Age{
     private final int value;
 
     public Age(int value){
        if(age < 0 || age > 120){
            throw new InvalidAgeException()
        }
        this.value=value;
     }

     public Age change(int value){
         return new Age(value);
     }
}            

Il serait tout à fait possible de ne pas utiliser un objet dédié, mais il faudrait externaliser la logique métier.  D'ailleurs c'est souvent cette façon de faire qu'il est possible de voir, on se retrouve avec des validations à maint endroit et le risque d'erreur de tout genre s'accroît.

Agrégat

C'est un groupe d'objet métier: objet de valeur, entité qu'on manipule comme si ça serait qu'une seule entité métier. Pour une facture, il y a différents éléments qui peuvent la composer: émetteur, destinataire, date, liste des produits ou service, coût, taxe. Toute modification devrait se faire au sein de notre agrégat afin de garantir sa cohérence. Si la facture est déjà payée, il ne devrait pas être possible d'ajouter d'élément dedans. Si un agrégat a besoin des informations d'un autre agrégat, son identifiant peut-être utilisé. Dans une transaction, il devrait avoir qu'une sauvegarde pour un agrégat.

Il faut prendre en considération que pour une situation donnée, un objet pourrait être un objet de valeur et dans un autre cas une entité ou même un agrégat.

Est-ce qu'on se soucie des valeurs de l'objet ou bien de l'identification d'une instance? Ce questionnement peut nous aider à choisir entre une value objet ou bien un agrégat. Pour spécifier l'identité d'un agrégat, une bonne pratique est d'utiliser une VO. 

Au lieu de faire

public class Invoice{
    int id;
}
 
Passer par un VO dans l'agrégat 

public class Invoice{
    InvoiceId id;
}

public class InvoiceId{
    int id; 
    InvoiceId(int id){
        if(id < 1){
            throw new ArgumentException("Id must have a minimum value of 1");
        } 
        this.id=id; 
    }
}
 
Dans le cas de notre InvoiceId, le id pourrait être la date du jour: 20201229 suivit d'un chiffre  qui s'incrémente et se réinitialise chaque jour. 

Il est plus aisé de changer le type de donnée par la suite, il y a moins d'endroits à effectuer les changements. Il est possible d'ajouter de la logique pour en autre effectuer des validations. Il est plus difficile de passer une mauvaise valeur que si on utilisait le type primitif.

Mise en pratique

Notre exemple consistera à une application de facturation, il pourrait avoir un contexte de facturation, client, paiement.... nous développerons celui de facturation. Plus précisément le scénario d'aller chercher une Invoice selon son Id. Notre application sera monolithique mais sous-divisée. Chacun des modules devrait utiliser une architecture hexagonale.

Notre objet Invoice va représenter toutce qui doit être facturé à un customer. LineInvoice représenterait chaque item produit ou service acheté. Invoice est un bon candidat pour un agrégat, mais LineInvoice?

public class Invoice {

    private final InvoiceId id;
    private final InvoiceStatus status;
    private final Long customerId;
    private final LocalDate paidDate;
    private final LocalDate dueDate;
    private final Flux<InvoiceItem> invoiceItems;
    
    private Invoice(InvoiceId id, InvoiceStatus status, Long customerId,
            LocalDate paidDate, LocalDate dueDate,
            Flux<InvoiceItem> invoiceItems) {
        this.id = id;
        this.status = status;
        this.customerId = customerId;
        this.paidDate = paidDate;
        this.dueDate = dueDate;
        this.invoiceItems = invoiceItems;
    }
    
    public static Invoice withoutId(InvoiceStatus status, Long customerId,
            LocalDate paidDate, LocalDate dueDate,
            Flux<InvoiceItem> invoiceItems) {
        return new Invoice(null, status, customerId, paidDate, dueDate, invoiceItems);
    }
    
    public BigDecimal calculateTotalPrice() {
        if (invoiceItems == null) {
            return BigDecimal.ZERO;
        }

        return invoiceItems.map(ii -> ii.calculateTotalPrice()).reduce(BigDecimal.ZERO, BigDecimal::add).block();
        
    }
}

LineInvoice pourrait être aussi un bon candidat pour un agrégat,  d'autres entités auraient besoin d'un identifiant. Il pourrait être possible d'appliquer une taxe, un rabais ou un frais d'envois spécifiques.

 public class InvoiceItem {
    private String description;
    private int quantity;
    private BigDecimal unitPrice;
    
    public InvoiceItem(String description, int quantity, BigDecimal unitPrice){
        if(quantity < 1){
            //quantity must be over 0
        }
        
        if(unitPrice.compareTo(BigDecimal.ZERO)!=1){
            //unit price must be over 0
        }
        this.description=description;
        this.quantity=quantity;
        this.unitPrice=unitPrice;
    }
    
    public BigDecimal calculateTotalPrice(){
      return unitPrice.multiply(BigDecimal.valueOf(quantity));
    }
    
}

 L'adapteur web

@RestController
@RequestMapping("/invoices")
public class GetInvoiceController {

    LoadInvoiceQuery loadInvoiceQuery;
    
    GetInvoiceController(LoadInvoiceQuery loadInvoiceQuery){
        this.loadInvoiceQuery=loadInvoiceQuery;
    }
    
    @GetMapping("/{id}")
    public Mono<Invoice> getInvoicesById(@PathVariable Long id){
        return loadInvoiceQuery.getInvoice(new InvoiceId(id));
    }
}

Le port pour la partie web    

public interface LoadInvoiceQuery {
    Mono<Invoice> getInvoice(InvoiceId invoiceId);
}

Le service au niveau du domaine

@Service
public class GetInvoiceService implements LoadInvoiceQuery{

    private final LoadInvoicePort loadInvoicePort;

    public GetInvoiceService(final LoadInvoicePort loadInvoicePort) {
        this.loadInvoicePort = loadInvoicePort;
    }
    
    @Override
    public Mono<Invoice> getInvoice(InvoiceId invoiceId){
        return loadInvoicePort.loadInvoice(invoiceId);
    }

}

L'adapteur de persistence

@Component
public class InvoicePersistenceAdapter implements LoadInvoicePort {

    @Autowired
    InvoiceMapper invoiceMapper;

    SpringPostgresInvoiceRepository invoiceRepository;
    SpringPostgresInvoiceLineRepository invoiceLineRepository;
    
    public InvoicePersistenceAdapter(SpringPostgresInvoiceRepository invoiceRepository,
            SpringPostgresInvoiceLineRepository invoiceLineRepository) {
        this.invoiceRepository = invoiceRepository;
        this.invoiceLineRepository=invoiceLineRepository;
    }


    @Override
    public Mono<Invoice> loadInvoice(InvoiceId invoiceId) {

        Mono<InvoiceEntity> invoiceEntity = invoiceRepository.findById(invoiceId.getValue());
        Flux<InvoiceLineEntity> invoiceLineEntity = invoiceLineRepository.findByInvoiceId(invoiceId.getValue());
        return invoiceMapper.mapToDomainEntity(invoiceEntity, invoiceLineEntity);
    }

}

Pour la persistence, il est possible d'utiliser une base de données ou un schéma par contexte ou bien dans utiliser un global à l'application. Seul un contexte a été développé, cependant même en optant pour une seule base de données pour l'ensemble des contextes, si on a besoin d'une information d'un autre contexte, en utilisant le id tel que customerId dans notre exemple,  pour aller effectuer la requête. Ce qui est une bonne approche pour bien séparer les domaines.

Si vous avez besoin d'augmenter les performances, vous pouvez utiliser un load balancer ou un reverse proxy pour démarrer de multiple instance de votre application avant d'opter pour des microservice. 

 Les packages sont organisés par couche. Certains préfèrent une organisation par fonctionnalité.

Responsabilité de l'adapteur pour la bd

  • Prendre les données en entrée
  • Les convertir dans le format de la bd
  • Les envoyer à la bd
  • Retourner les donner de sortie de la bd

Si vous désirez utiliser un ORM,  il faut faire le pont entre la structure objet et ceux utilisés pour le domaine, car tel que déjà dit, la couche du domaine ne devrait pas être imprégnée d'un framework. Cependant, j'ai pu constater à quelques reprises que ce n'était pas toujours respecté. 

Dans une architecture hexagonale, on se retrouve avec un petit domaine, je trouve alors l'emploi d'un ORM moins intéressant. Si vous optez pour cette solution, MapStruct peut grandement simplifier la conversion en vos objets métier.

Dans notre exemple, nous avons utilisé r2dbc, afin que l'application soit de type reactive. Contrairement au ORM, les relations ne sont pas géré, il faut le faire manuellement.

Autrement, Spring Data JDBC peut être une autre solution.

À partir d'un Id, nous avons été chercher un objet de type InvoiceEntity, il a été ensuite convertir en Invoice pour la couche domaine, nous aurions pu le convertir dans un autre format selon les besoins au niveau du web. Certains pourraient trouver que le passage entre ces couches est inutile dans ce cas. Un appel direct à partir du web vers la bd pourrait être effectué. Je suis d'accord, à moins de gros problème de performance, je continuerais d'utiliser cette approche, une fois que des raccourcies sont mis en place, ils deviennent rapidement la norme et on se trouve avec une architecture loin des attentes initiales.


Communication / synchronisation

Vous voudriez développer un autre module tel que le Customer. Comment les différents modules pourraient communiquer entre eux?

Dépendant si le type de communication: synchrone, asynchrone, vous pouvez optez pour des requêtes HTTP, service d'agent de message (RabbitMQ, JMS...)

Conclusion

Vous êtes une startup, vous voyez en grand. Vous regardez ce qu'utilise Netflix et vous voulez faire pareil. Vous n'avez aucun produit, aucune ligne de code de fait, mais vous pensez que la seule solution pour gérer votre hypothétique centaine de millions de requêtes est les microservices.

Il ne faut pas négliger la complexité niveau infrastructure: une base de données par microservice, la gestion des multiples instances des microservices, la communication entre eux et leur surveillance. Ces particularités amènent à avoir une équipe devops très efficace.

L'utilisation d'une architecture hexagonale pourrait grandement simplifier la mise en place de votre application avant qu'un passage au microservice soit réellement nécessaire. Le but de l'article était de vous montrer une façon différente de faire une architecture logiciel, libre à vous de l'adapter selon vos besoins.


 
 

lundi 8 octobre 2012

L'assurance qualitée avec Sonar

Sonar est un outil pour gérer la qualité du code source pour différents langages tels que Java, PHP, vb. Il utilise plusieurs logiciels tels que pmd, findbug, covertura... pour y parvenir.

Ce logiciel permet de détecter la duplication de code, de déceler le manque de documentation, les anomalies potentielles, règle de programmation, ce que couvrent les tests unitaires et la complexité du code.
Sonar peut s'intégrer à différents outils d'intégration continue tel que Hudson.

Il intègre sa propre base de données, H2 mais il est recommandé d'utiliser une autre base de données ne serait que pour avoir de meilleure performance.

Pour la présentation du produit, H2 sera utilisé. De plus aucun serveur d'intégration continue n'est utilisé.
Télécharger sonar et décompressé l'archive.

La particularité de ce système est qu'il est très visuel. Il peut aider à mieux identifier où doit être mis du temps afin d'accroître la qualité du projet.

Démarrage

Dans le répertoire bin de sonar, sélectionner le répertoire correspondant à votre environnement.

Cliquer sur le fichier sonar.bat si vous utilisez Windows, sinon sur sonar.sh.

Le système est alors accessible via un navigateur web: http://localhost:9000

Projets

Pour chacun des projets que vous désirez importer, si vous utiliser maven lancez la commande
mvn sonar:sonar

La liste des projets importés sera alors affichée dans l'écran de démarrage de Sonar.

Treemap

La treemap est une vue 2D. La taille de chaque carrée est une mesure quantitative selon différents critères des catégories: complexité, design, documentation, duplication, test d'intégration, gestion, revue...

La couleur représentante une mesure qualitative.


Dashboard

C'est la vue par défaut d'un projet. Les différentes mesures quantitatives présentées plutôt y sont affichées.

Hotspot

La vue hotspot présente les éléments à corriger d'un projet.

  • classes ayant le plus de violations
  • règles avec le plus de violations
  • classes ayant une forte complexité
  • classes ayant le plus de code dupliqué
  • classes le moins documentées


Time machine

La vue time machine permet de voir l'évolution du projet. Il est possible de comparer différentes versions du projet. Cette vue est particulièrement importante afin de savoir si le projet s'améliore ou part à la dérive.



Component

La vue component donne des informations similaires au dashboard, cependant elles sont données par  module.


Violations drilldowns

La vue Violations drilldowns indique les violations du projet en plus d'indiquer leur importance. Il est possible de modifier le degré d'importance des violations. Le nombre d'anomalies par type, le nombre de classes par importance, ainsi que le nombre d'anomalies par classe sont affichés.



Cloud

La vue cloud spécifie l'importance à accorder à une classe. La taille du nom de la classe varie en fonction de son importance.

Le cloud Quick wins affiche les classes qui augmenteront la qualité du code tout en demande peu d'effort. La taille du nom de la classe varie en fonction de sa complexité alors que sa couleur indique sa couverture

Le cloud Top risk affiche les classes ayant un risque d'erreur. La taille du nom indique la complexité par méthode alors que sa couleur indique le respect des règles.



Design

La vue design indique les dépendances entre les différents modules d'un projet. Certaines dépendances sont jugées inappropriées.


Des plugins gratuit et payant avec plus de fonctionnalités aussi disponibles. Le plugin Refactoring Assessment est par exemple disponible sous ces modes.




Dans sa version gratuite, des données sont fournies afin de montrer les possibilités de la version payante.  La version payante effectue automatiquement certains traitements.

Autres plugins

La puissance de Sonar est en partie due au nombreux plugins existant. Ils permettent d'étendre les fonctionnalités de l'outil. Voici quelques plugins très intéressants que je vous conseille.


SCM Stats Plugin

Technical Debt Plugin

Quality Index Plugin

SIG Maintainability Model Plugin

Useless Code Plugin

Timeline Plugin



samedi 22 septembre 2012

Design Pattern Tête la première




Ce livre présente les patrons suivants
  • Observateur
  • Décorateur
  • Fabriques
  • Singleton
  • Commande
  • Adaptateur, Façade
  • Patron de méthode
  • Itérateur, composite
  • État
  • Proxy
  • Composés


Au lieu d'aborder une approche théorique comme de nombreux livres, ils ont opté pour une approche pratique.

Il utilise des techniques afin de rendre plus aisé l'apprentissage. Les informations sont expliquées de diverses façons.

Les images sont abondamment utilisées. Il y a des mini-jeux ainsi que des questions afin de faire réfléchir le lecteur sur la matière vue.
Des conseils sont données tout au long du livre. Des dialogues ainsi que des histoires sont employés afin d'emmener le lecteur à se forger une idée sur les points clés.
Les moyens pédagogiques employés rend  la lecture du livre beaucoup plus agréable, sans compté que ça simplifie la compréhension et la mémorisation des patrons.

Les exemples de code utilisent le langage Java, cependant il devrait être compris sans difficulté par ceux utilisant c#.

Cette approche est employée pour chaque patron. Cette stratégie devrait être employée dans plus de bouquins.

À ce jour, c'est le meilleur livre sur les patrons que j'ai pu lire. Si vous devez en lire qu'un seul, pensez à celui-ci.

Certes seuls les patrons les plus populaires sont présentés, mais vous obtiendrez une base solide afin de les utiliser dans vos développements.

dimanche 16 septembre 2012

Netbeans

Netbeans
Chaque nouvelle version de netbeans apporte un nombre grandissant d'amélioration. Il a quelques années, Eclipse a su s'accaparer une grande part du marché des ide en Java.

Depuis, Oracle travaille d'arrache pied pour remonter la pente. Leur effort semble récompensé, les réactions dans les forums et sondage sont très positif. La facilité de créer des interfaces graphiques depuis la  version 5x à peu aider à contribuer à ce renversement.

Une panoplie de module peuvent être installé à la demande: C/C++, java mobile, php, ruby, grail, groovy. De plus le profiler ainsi le créateur de gui installé par défaut permette de faciliter le développement.

Hudson, Ant, Maven, Bugzilla sont bien intégré à l'outil.

Netbeans possède un éditeur graphique qui utilise le groupLayout. Il permet en quelques cliques de créer des interfaces très complexe.



Il y a certe moins de plugin qu'Eclipse, mais les meilleurs existent aussi pour cet ide.

De multiple wizard existe pour créer les fonctionnalités CRUD.

JavaFX est supporté.

La plateforme Netbeans peut être utilisé afin de créer des applications Swing. Il pousse à utiliser divers principe du génie logiciel tel que les modules, les plugins et les services. Il standardise les tâches courantes de création d'application graphiques: gestion des fenêtres, menu, mise à jour, gestion des langues. La gestion des modèles des composants graphiques est générique. Le même modèle peut être utilisé pour un jtree, jtable...

Si vous n'avez jamais utilisé Netbeans, essaye le, votre expérience de développement en sera grandissante. Autrement, faite la mise à jour.

vendredi 15 juin 2012

Specification By Example




Ce livre explique l'importance d'insérer le client dans le processus du développement logiciel. Les bénéfices ainsi que les bonnes pratiques sont définis. Le plus intéressant du livre est de mon point de vue les études de cas. Diverses entreprises donnent leur commentaire, comment elle était avant l'emploi de cette méthode et les résultats de son usage.

Des concepts clés tel que définir les spécifications en groupe, mettre des exemples dans les spécifications, valider fréquemment les spécifications ainsi que plusieurs autres bonnes pratique sont élaborés dans ce livre.

Je crois qu'avoir vécu quelques projets informatiques peut contribuer à mieux apprécier ce livre et ainsi remarquer qu'on est pas les seul à avoir des problèmes similaires et à vouloir les fixer.Oui la documentation est importante, mais une image peur remplacer beaucoup de mot et éviter des erreurs.

Les cas concrets ne font que renforcer que les méthodologies agiles peuvent vraiment être un avantage dans la gestion d'un projet informatique. Les cas sont différents ainsi que la structure et la des équipes ce qui permet de mieux pouvoir se projeter dans un de ces exemples C'est un livre qui donne de précieux conseils.

mardi 2 août 2011

Language Implementation Patterns




Ce livre présente différents patrons utilisés pour l'interprétation, analyse et la traduction de langage de programmation. Le livre utiliser ANTLR afin d'aller à l'essentiel et réduire la complexité. Les différentes étapes de la syntaxe du langage à l'exécutable sont passées en revue.

La première partie est consacrer au parsing le langage différente technique sont présenté tel que "backtracking, memoizin, predicated"... La deuxième partie consiste à analyser ce qui a été lu. La troisième partie est liée à la création d'interpréteur (haut niveau et bytecode). La dernière partie consiste à traduire à un langage à un autre.

Ce livre permettra à celui qui veut créer un langage de mieux décerner le patron qu'il pourra utiliser. Certains patrons sont plus adaptés à certains types de syntaxe. Le lecteur pourra avoir une idée pour implémenter un interpréteur pour un langage dynamique.

La traduction d'un langage à un autre permet grâce à l'exemple d'avoir une meilleure idée de la façon de faire pour un langage plus complexe.

J'ai trouvé que les explications n'étaient pas toujours claires et manquaient d'exemple réellement concret. Néanmoins, il constitue une excellente entrée pour quiconque veut implémenter un langage de programmation.

Peu de livres présentent d'une façon si épurée autant de concept, ce livre fait moins de 400 pages. La concurrence fait souvent plus du double ou triple et est axé trop souvent sur la théorie.

C'est un livre à compléter par un livre plus axé sur la pratique.

jeudi 14 avril 2011

97 Things Every Programmer Should Know




Ce livre est composé de 97 bonnes pratiques que tout développeur devrait connaître. Les astuces sont présentées par des programmeurs expérimentés. Elles sont courtes, mais précises.

 Ce n'est pas pas nécessairement des trucs sur la façon de coder, mais bien des idéologies à mettre en place afin d'arriver à produire du meilleur code, avoir un esprit plus rigoureux qui permettra de résoudre un problème donné de façon plus optimale.

Ce ramassis d'astuces permettra à quiconque du débutant à l'expert de parfaire ses connaissances dans l'art du développement. Au lieu de prendre des conseils à droite et à gauche et qui prendrait plusieurs années, ici on a l'essentiel qui est condensé.

Par exemple, on y dit qu'il est est important de bien maitriser son ide, savoir estimer, améliorer son code en enlevant le superflu. Ce livre présente plusieurs facettes du génie logiciel.
C'est un livre à posséder et à relire.

Migration d’une application Spring Boot 2.2 vers Spring Boot 4.0 : retour d’expérience concret

 En 2019, nous avons développé une application métier basée sur : Java 8 Spring Boot 2.2.7 Gradle 6.6.1 Thymeleaf Boots...