L’acheminement de messages dans Petals ESB : #2 Le routage


Intro

Dans l’article précédent sur l’acheminement de messages dans Petals ESB, j’introduisais les notions, aujourd’hui nous allons regarder en détails la couche de routage du bus. Cette couche est largement appelée Router par les développeurs du Bus, et le sera donc dans cette article et dans la suite de la série.

L’archi simplifiée

L’architecture du Router utilise la notion très répandue et  fort utile de modules ie le Router invoque séquentiellement une liste de modules pour élire les services à appeller. A la fin de la traversée on se retrouve avec toutes les informations nécessaires pour appeler les services. On peut modifier cette liste en ajoutant ou supprimant des modules par configuration. Un schéma simplifié donne un Router avec cette tête :

Plus de détails sur l’utilisation des modules dans Petals et leur injection dans un vieil article (à non pas si vieux…).

L’implémentation

L’implémentation de base de Petals ESB est composée de 2 modules en émission :

  1. Le module qui interroge le registre de services et retourne une liste de point d’accès (endpoint dans le jargon service) en fonction des informations contenues dans le message d’entrée.
  2. Le module de résolution de la couche de transport. En fonction de la localisation des endpoints trouvés par le module précédent, un contexte est créé pour définir la couche de transport à utiliser (cette couche de transport sera détaillée dans le prochain article de la série).

On peut imaginer un grand nombre d’utilisations de ces modules. Personnellement, je les utilise à outrance par exemple pour :

  • Ajouter des informations de timestamp sur les messages
  • Notifier une couche de monitoring que des messages sont échangés entre consommateurs et fournisseurs de service
  • Logger les appels
  • Mettre à jour le choix de la couche de transport avec ma propre implémentation du transport de messages inter container

Outro

Vous en savez un peu plus sur le routage dans le bus de service Petals. il faut vraiment retenir que cette couche est vraiment extensible et customisable sans grand effort, en effet pas besoin de recompiler le coeur de Petals pour ajouter des fonctionnalités pour la résolution des endpoints. Dans le prochain article de la série, nous regarderons de plus prêt comment sont échangés les messages entre les instances de Petals.

Publicités

Extensions de Petals ESB : LifeCycleListener


Petals ESB offre des possibilités d’extensions assez impressionnantes venant du fait que l’on utilise le Framework à composants Fractal (bon c’est pas Spring mais on peut faire des choses intéressantes avec des produits issus de la recherche). Dans cet article je vais m’intéresser à la customisation du démarrage et de l’arrêt de Petals en proposant une extension sympa et ce sans modifier énormément le code du kernel de Petals (Ca peut aussi servir de tutorial Fractal pour les débutants…).
Pourquoi? Parce-que actuellement pour moi Petals ESB est le framework SOA que j’étends pour lui ajouter des fonctionnalités qui ne sont pas fournies par défaut. La fonctionnalité du jour doit me permettre d’attendre que tous les composants par défaut soit démarrés pour démarrer les miens : Je dois installer des composants JBI (ne marchera pas si je laisse Fractal les installer trop tôt), lier des services (ne marchera pas si mes composants ne sont pas installés et si le service de management de Petals n’est pas démarré), lancer des tâches de fond, etc…

l’API du kernel de Petals propose l’interface org.ow2.petals.kernel.api.server.PetalsStateListener :

package org.ow2.petals.kernel.api.server;

/**
 * Listener notified when a Petals container finish to start or to stop.
 */
public interface PetalsStateListener {

    /**
     * Callback method on PEtALS start up
     */
    void onPetalsStarted();

    /**
     * Callback method on PEtALS stop
     *
     * @param success
     *            {@code true} if PEtALS has stopped successfully
     * @param exception
     *            the exception if the stop has failed, {@code null} otherwise
     */
    void onPetalsStopped(boolean success, Exception exception);
}
<pre>

Cette interface n’est malheureusement utilisée que statiquement et une seule fois pour informer le ‘launcher’ que le kernel est bien démarré. On a pourtant org.ow2.petals.kernel.api.server.PetalsServer qui définit la méthode addPetalsStateListener(PetalsStateListener). je vais donc utiliser cette API et modifier simplement l’implémentation de org.ow2.petals.kernel.api.server.PetalsServer pour ajouter des listeners qui seront appelés lors du démarrage et de l’arrêt de Petals.

Pour ce faire deux approches sont possibles :

  1. Rechercher tous les composants Fractal qui implémentent org.ow2.petals.kernel.api.server.PetalsStateListener
  2. Créer un manager qui contient une liste ordonnée de tous les org.ow2.petals.kernel.api.server.PetalsStateListener

On va ici se pencher sur la solution 2 car la solution 1 ne permet d’ordonner facilement les listeners à appeler (ca peut valoir le coup de les appeler dans un sens bien définit!).

1/ Le manager de listeners

Simplement un composant Fractal qui détient la liste des listeners définis. Ces listeners sont définis par configuration dans les fichiers ADL de Petals. Son interface :

package foo.bar.petals.kernel.listener;

import java.util.Set;

import org.ow2.petals.kernel.api.server.PetalsStateListener;

/**
 * @author chamerling - eBM WebSourcing
 *
 */
public interface LifeCycleListenerManager {

    /**
     * The binding prefix of the component which is listening petals state
     */
    static final String PREFIX = "petals-state-listener-";

    /**
     * Get an ordered set of listeners
     *
     * @return
     */
    Set<PetalsStateListener> getListeners();

}

et son implémentation :

package foo.bar.petals.kernel.listener;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.objectweb.fractal.fraclet.annotation.annotations.FractalComponent;
import org.objectweb.fractal.fraclet.annotation.annotations.Interface;
import org.objectweb.fractal.fraclet.annotation.annotations.LifeCycle;
import org.objectweb.fractal.fraclet.annotation.annotations.Monolog;
import org.objectweb.fractal.fraclet.annotation.annotations.Provides;
import org.objectweb.fractal.fraclet.annotation.annotations.Requires;
import org.objectweb.fractal.fraclet.annotation.annotations.type.Cardinality;
import org.objectweb.fractal.fraclet.annotation.annotations.type.Contingency;
import org.objectweb.fractal.fraclet.annotation.annotations.type.LifeCycleType;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.petals.kernel.api.server.PetalsStateListener;
import org.ow2.petals.util.LoggingUtil;

/**
 * @author chamerling
 *
 */
@FractalComponent
@Provides(interfaces = { @Interface(name = "service", signature = LifeCycleListenerManager.class) })
public class LifeCycleListenerManagerImpl implements LifeCycleListenerManager {

    @Monolog(name = "logger")
    private Logger logger;

    private LoggingUtil log;

    @Requires(name = PREFIX, signature = PetalsStateListener.class, cardinality = Cardinality.COLLECTION, contingency = Contingency.OPTIONAL)
    private final Map<String, Object> listeners = new Hashtable<String, Object>();

    @LifeCycle(on = LifeCycleType.START)
    protected void start() {
        this.log = new LoggingUtil(this.logger);
        this.log.debug("Starting...");
    }

    @LifeCycle(on = LifeCycleType.STOP)
    protected void stop() {
        this.log.debug("Stopping...");
    }

    /**
     * {@inheritDoc}
     */
    public Set<PetalsStateListener> getListeners() {
        HashSet<PetalsStateListener> result = new HashSet<PetalsStateListener>();
        if (this.listeners != null) {
            Iterator<String> iter = this.listeners.keySet().iterator();
            while (iter.hasNext()) {
                String key = iter.next();
                Object o = this.listeners.get(key);
                if (o instanceof PetalsStateListener) {
                    result.add((PetalsStateListener) o);
                } else {
                    // warning
                    this.log.warning(o + " is not an instance of "
                            + PetalsStateListener.class.getCanonicalName());
                }
            }
        }
        return result;
    }
}

A noter que la Map ‘listeners’ est remplie par Fractal au démarrage de Petals.

2/ Modification de PetalsServerImpl

Dans org.ow2.petals.kernel.server.PetalsServerImpl je rajoute la méthode initListeners() :

   private void initListeners() {
        // get the component which owns the listeners
        Component listenerManagerComponent = FractalHelper.getRecursiveComponentByName(
                this.petalsContentController, Constants.FRACTAL_LISTENERS_MANAGER);
        if (listenerManagerComponent != null) {
            try {
                LifeCycleListenerManager manager = (LifeCycleListenerManager) listenerManagerComponent
                        .getFcInterface("service");

                if (manager != null) {
                    Set<PetalsStateListener> listeners = manager.getListeners();
                    for (PetalsStateListener petalsStateListener : listeners) {
                        this.addPetalsStateListener(petalsStateListener);
                    }
                } else {
                }
            } catch (Exception e) {
            }
        }

    }

Ce code permet de retrouver mon ‘listener manager’ (puisque PetalsServerImpl n’est pas ‘Fractalisé’) et de remplir la liste des listeners. Allez maintenant, on va dire que j’appelle cette méthode d’initialisation juste avant d’appeler les listeners (dans start() de PetalsServerImpl):

    public void start() throws PetalsException {
        // get listeners which have been defined by configuration (Fractal)
        this.initListeners();

        // notify the listener if there is one
        for (PetalsStateListener petalsListener : this.petalsListeners) {
            petalsListener.onPetalsStarted();
        }
    }

3/ Configuration des listeners

Dans les fichiers de définition des composants Fractal, je peux maintenant instancier mes listeners et les ajouter à la liste des listeners gérée par le manager de listeners :

	<!-- lifeCycle Listeners -->
	<component definition="foo.bar.petals.kernel.FooService" name="Foo" />
	<component definition="foo.bar.petals.kernel.BarService" name="Bar" />

	<!-- Listeners manager -->
	<component definition="eu.soa4all.dsb.petals.kernel.listener.LifeCycleListenerManagerImpl" name="LifeCycleListenerManagerImpl" />

	<!-- Fill the map -->
	<binding client="LifeCycleListenerManagerImpl.petals-state-listener-foo" server="Foo.service" />
	<binding client="LifeCycleListenerManagerImpl.petals-state-listener-bar" server="Bar.service" />

Le composant foo.bar.petals.kernel.*Listener implémentent  org.ow2.petals.kernel.api.server.PetalsStateListener et sont ajoutés à la liste des listeners managés par LifeCycleListenerManagerImpl lors de la définition des liens (lignes ‘binding’). Tous les listeners ajoutés au manager doivent avoir une valeur de l’attribut client commençant obligatoirement par LifeCycleListenerManagerImpl.petals-state-listener- (remplissage de la Map par Fractal).

Facile et pratique, non?!