Thématiques principales

lundi 30 avril 2018

JEE : JMS

Nous voici aujourd'hui sur un sujet JEE très classique qu'est JMS.

JMS pour Java Messaging System est un MOM (Message Oriented Middleware), c'est à dire un support intermédiaire (Middleware) pour l'échange de messages entre différents systèmes d'informations et/ou entres différentes couches applicatives. JMS se positionne donc comme solution technique pour des architectures spécifiques telles que celles que nous avons déjà vu dans l'article précédent sur les architectures types [1] en fournissant des solution de couplage faible entre les composants, des échanges de messages asynchrones (positionnable en synchrone mais cela enlève beaucoup d'intérêt a JMS), d'être scalable (c'est à dire qu'on l'on peut facilement ajouter des composants à l'ensemble des composant déjà présent dans le système sans perturbation notable) et sécurisé.

Pour permettre la mise en relation de ces composants de façon homogènes, JMS repose sur différents modes de communications orientées autour des concepts de queue (Queue) ou de sujet (Topic) [10]. Ces deux approches apportent leur propres paradigmes afin de répondre à des besoins soit d'échanges point à point, soit d'échange sous la forme de liste de diffusion.

L'intérêt de JMS est de définir un contexte où l'information est au centre des préoccupations et non les émetteurs ou les receveurs qui n'auront pas à s'acquitter de leur présence ou non sur le réseau et n'auront pas non plus en charge d'acquitter les messages (a moins que cela ne soit prévu via un autre flux).



Comme nous venons de le voir, il existe deux modes de communication. Voyons leur principes [2].

Le premier mode est le mode Queue. Ce mode de communication est un mode point à point classique ou la queue sert d'intermédiaire entre un Émetteur de message qui va pousser ces derniers dans la queue et un receveur de message qui lui va les déposer. Ainsi le message produit sera produit une fois et consommé une fois. La persistance du message est garanti par la queue elle même qui stockera les messages si le receveur n'est pas disponible pour les recevoir.


Le second mode est le mode Topic. Ce mode de communication est un mode one to many ou le topic sert d'intermédiaire sous la forme d'une liste de diffusion entre un Émetteur de message, le publieur, et un ensemble de receveur abonné au topic. Ainsi le message produit sera produit une fois et diffusé à l'ensemble des composant abonné. Ainsi, à l'inverse de la Queue ou le message est stocké dans l'attente de la présence du receveur, dans ce mode, le message est diffusé une fois à l'ensemble des lecteurs présent sans réémission pour les absents.


Comme tout ce qui fait partie de l'environnement Java, JMS en est aussi une spécification qui s'est vu décliné selon de nombreuses implémentation (comme par exemple ici en Spring [3]). Cependant, son utilisation est relativement transparente car fournissant des concepts clefs très simple.

Ainsi, techniquement, pour utiliser JMS, nous nous reposerons sur un conteneur d'application dans lequel, il est nécessaire de déclarer une factorie permettant la construction de connexion JMS et des ressources de type destination qui seront soit des Queues, soit des Topics (ou les deux selon le besoin). Les connexions JMS ont pour rôle de permettre aux émetteur/receveur et aux publieur/abonné de communiquer entre eux. Vous allez dire a ce stade mais du coup, comment en ne connaissant que la queue ou le topic, ces acteurs peuvent ils se parler au travers d'un réseau? Tout simplement grâce à ce que l'on appelle un broker.

Le broker est le composant permettant a JMS de prendre vie. En effet, il va supporter la définition et la consolidation virtuelle des Queue et des Topic. Il peut en exister plusieurs sur un réseau, ces derniers se chargeant du routage des messages et de la balance de charge. Ainsi, à partir d'une factorerie de connexion (soit instancié directement soit récupérer via JNDI si vous êtes dans un conteneur d'application), vous allez pouvoir vous adresser directement au broker hébergeant votre queue ou votre topic afin de soit émettre un message soit le recevoir (par notification si vous êtes en asynchrone, par défaut). De nombreux broker JMS existent portant généralement également l'implémentation de la spécification JMS, ainsi, parmi implémentations les implémentations propriétaires, on notera IBM MQ [4], SwiftMQ [5] et chez les implémentations open source, ActiveMQ [6], JORAM [7], OpenJMS [8] ou RabbitMQ [9].

Le choix de l'utilisation de l'une ou l'autre des approche de communication sera guidé par la nature des données en transit et de la stratégie de traitement voulu [10]. En effet, face à une queue, un message n'étant transmis qu'à un seul des receveurs, il faut considérer que nous somme sur une architecture ou le but est de garantir un traitement sur un message peu importe qui fait le traitement. Ainsi, il sera plus intéressant d'utiliser les queues pour de la répartition de charge. A l'inverse, le topic a pour vocation d'inonder un ensemble de composant avec une même information, ainsi on préférera ce type de solution par exemple lorsqu'un utilisateur se connecte à un système et que son identité/session est diffusé à un ensemble de briques applicatives qui choisiront alors d'appliquer leur propre politique de sécurité (mais n'auront pas eu à gérer un système d'authentification).


Afin d'éclaircir les idées, je vous propose de traiter d'un petit exemple très simple proposant l'utilisation d'une Queue et d'un Topic entre deux applications embarqué dans un Server d'application. Le serveur d'application doit accepter les spécifications JEE concernant les EJB, JMS et les Servlets. J'ai choisi et testé ce code sur du weblogic et du glassfish mais vous ne devriez pas avoir de soucis particulier sur JBoss ou Tomee par exemple. On définit dans le serveur l'application une ConnectionFactory qu'on nommera "jms/ConnectionFactory" ainsi que deux destinations une de type Queue et l'autre de type Topic nommées respectivement "jms/Queue" et "jms/Topic"


Ainsi, on va réaliser deux applications. L'une contiendra un servlet dans laquelle on va injecter un EJB utilisant la factorie et les destinations. Cet EJB proposera une primitive pour initialiser les connexions et une autres pour envoyer des messages. Dans l'autre application, on ne définit que deux EJB dont les rôles seront juste de se mettre à l'écoute de la Queue et de la Topic.

Le producteur de message de la première application est alors construit comme ceci:


@Stateless
public class MyProdJMS {

 @Resource(mappedName = "jms/myConnectionFactory")
 private ConnectionFactory connectionFactory;
 private Session session;
 
 private MessageProducer tmessageProducer;
 @Resource(mappedName = "jms/Topic")
 private Topic topic;

 
 private MessageProducer qmessageProducer;
 @Resource(mappedName = "jms/Queue")
 private Queue queue;
 
 public void initQueueTopicConnection() throws JMSException {
  Connection connection = connectionFactory.createConnection();
  session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
  qmessageProducer = session.createProducer(queue);
  tmessageProducer = session.createProducer(topic);
  connection.start();
 }

 public void sendOnTopic(String arg0) throws JMSException {
  Logger.getLogger("Message a envoyer " + arg0);
  TextMessage message = this.session.createTextMessage(arg0);
  this.tmessageProducer.send(message);
 }

 public void sendOnQueue(String arg0) throws JMSException {
  Logger.getLogger("Message a envoyer " + arg0);
  TextMessage message = this.session.createTextMessage(arg0);
  this.qmessageProducer.send(message);
 }
}


Avec sa servlet utilisant le producteur de message


public class MaServlet extends HttpServlet {

 @Inject
 private MyProdJMS monEjb;

 @Override
 public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
  try {
   monEjb.initQueueTopicConnection();
   monEjb.sendOnQueue("doGetQueue");
   monEjb.sendOnTopic("doGetTopic");
  }catch (JMSException e)
  {
   System.out.println("error "+e);
  }
  this.getServletContext().getRequestDispatcher("/WEB-INF/index.jsp").forward(request, response);
 }
}


Maintenant la seconde application va utiliser JNDI pour récupérer les informations JMS et se mettre en ecoute des messages:


@MessageDriven(activationConfig =  { 
  @ActivationConfigProperty(propertyName = "destinationType",propertyValue = "javax.jms.Queue"),
  @ActivationConfigProperty(propertyName = "destinationLookup",propertyValue = "jms/Queue")
})
public class MyQueueConsJMS implements MessageListener{

 public MyQueueConsJMS(){}
 
 @Override
 public void onMessage(Message arg0) {
  org.apache.log4j.Logger.getLogger(MyQueueConsJMS.class).info("message queue entrant "+arg0);
  System.out.println("message Queue entrant "+arg0);
 }
}


et


@MessageDriven(activationConfig =  { 
  @ActivationConfigProperty(propertyName = "destinationType",propertyValue = "javax.jms.Topic"),
  @ActivationConfigProperty(propertyName = "destinationLookup",propertyValue = "jms/Topic")
})
public class MyTopicConsJMS implements MessageListener{

 public MyTopicConsJMS() {}
 
 @Override
 public void onMessage(Message arg0) {
  org.apache.log4j.Logger.getLogger(MyTopicConsJMS.class).info("message Topic  entrant "+arg0);
  System.out.println("message Topic entrant "+arg0);
 }
}


Pour pousser un peu plus loin les exemples du même type, vous pouvez vous référez à [14],[15], par contre nous nous intéresserons a l'instanciation de notre propre broker dans un article futur car il s'agit en fait souvent d'une stratégie plus adéquate pour la construction de l'ESB (Enterprise Service Bus). En effet, l'externalisation du broker permet la conception indépendante de l'aspect applicatif du système et de son aspect interconnexion. Ainsi découplé, il va etre possible de mettre en oeuvre des architectures facilitant même l'interconnexion des ESB entre eux (même si cela reste complexe à mener) via l'utilisation de passerelles [11]. Ces architectures peuvent être plus simplement sécuriser, distribué avec un ou plusieurs brokers, supporter plus simplement des politiques de répartition de charges comme nous l'avons évoqué précédemment et créer de la haute disponibilité par la mise en place de broker primaire, maître et secondaire, esclave, gérant leur propre stockage ou le partageant et fonctionnant de manière synchrone.

Pour terminer cet article, nous évoquerons les outils disponible pour tester notre ESB sur ses interfaces JMS. Nous ne les détaillerons pas car nous aurons l'occasion d'en reparler dans différentes futur article mais sachez déjà que pour élaborer votre stratégie de test, il est possible de s'appuyer sur HermesJMS (le plus connu probablement) [12] et de tester sa charge avec JMeter [16]. Nous reviendrons sur ces outils. A noter selon [17] que IBM a produit un outil nommé perf harness [18] pour comparer des fournisseurs JMS sur lequel il est possible de s'appuyer pour instancier notre propre stratégie de test, a voir.

Bien sur, il n'existe pas que JMS et l'interopérabilité d'un ESB est sa force ainsi, il est aussi possible de ne pas faire le choix de JMS en adoptant des solutions alternatives comme le présente Arnaud avec AMQP [13]. Je ne rentrerai pas dans le détail de ce MOM ne le connaissant pas mais face à une technologie, il faut aussi parfois rester ouvert aux nouvelles approches (disons plutôt moins conventionnelle car le billet en question date quand même de 2010...). Ainsi si JMS est un standard incontournable dans la sphère java, il ne faut pas oublier que toutes les applications et les ESB ne sont pas tous en Java et donc en JMS et que le plus important dans un ESB, c'est son capacité à être transverse.

Voilà, nous avons fait un petit tour de JMS, nous avons traité rapidement ses principaux points avec un petit exemple et quelques schémas relatifs aux architectures qu'il nous permet de construire. Nous avons cependant traité un peu rapidement les notions de répartition de charge, de haute disponibilité et de test de charge et vous propose de nous retrouver dans un futur article pour parler un peu de ces notions. De même, dans le contexte de ces préoccupations, nous verrons que l'optimisation de la configuration du server d'application choisi est primordiale et nous tacherons la aussi de traiter le sujet dans un autre article.

Références

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/03/architectures-types.html
[2] https://dzone.com/articles/java-message-system-introduction?edition=327491&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=Daily%20Digest%202017-09-22
[3] https://dzone.com/articles/using-jms-in-spring-boot-1?edition=305129&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=dd%202017-06-22
[4] https://www.ibm.com/us-en/marketplace/secure-messaging
[5] http://www.swiftmq.com/
[6] http://activemq.apache.org/
[7] http://joram.ow2.org/
[8] http://openjms.sourceforge.net/
[9] https://www.rabbitmq.com/getstarted.html
[10] http://middleware.smile.fr/Concepts-des-moms-et-jms/Java-messaging-system-ou-jms
[11] http://middleware.smile.fr/Concepts-des-moms-et-jms/Fonctionnalites-avancees
[12] http://activemq.apache.org/hermes-jms.html
[13] http://blog.xebia.fr/2010/02/23/amqp-une-alternative-a-jms/
[14] http://miageprojet2.unice.fr/Intranet_de_Michel_Buffa/Cours_composants_distribu%C3%A9s_pour_l'entreprise_%2F%2F_EJB_2009/TP4_EJB_%3A_Utilisation_de_JMS_et_de_Message_Driven_Beans
[15] https://timjansen.github.io/jarfiller/guide/jms/sendingreceivingejb3.xhtml
[16] https://jmeter.apache.org/
[17] https://code.i-harness.com/fr/q/9451
[18] https://developer.ibm.com/code/open/projects/perfharness/

Aucun commentaire:

Enregistrer un commentaire