Écrivez votre propre maman!

MOM est mal compris et MOM n'a aucun crédit. Vous avez peut-être déjà entendu celui-ci, mais dans l'arène des systèmes distribués, c'est en fait vrai! En effet, le middleware orienté message (MOM) n'a pas toujours bénéficié du même niveau de sophistication et de prise en charge que les autres technologies utilisées dans les cadres de communications distribuées.

Mais les temps changent. Avec l'introduction d'offres de fournisseurs sophistiquées et robustes, l'intérêt pour les systèmes MOM augmente rapidement. Les bonnes implémentations MOM fournissent une interface d'applications de haut niveau, des garanties de qualité de service et une multitude de services tels que la sécurité, la mise en file d'attente des messages et la prise en charge des répertoires qui sont nécessaires pour les communications distribuées de «niveau industriel».

Cadres de communication distribués

Le but d'un cadre de communication distribué est de fournir un bon moyen pour les parties d'un système distribué de communiquer. Les frameworks orientés objet accomplissent cette tâche en fournissant aux objets distribués un moyen de se transmettre des messages.

Les frameworks orientés objet distribués qui retiennent le plus l'attention sont ceux qui modélisent la messagerie comme des appels de méthode. CORBA et RMI sont deux excellents exemples de ce type de cadre (voir Ressources). Ces systèmes sont souvent appelés systèmes d'appel de procédure à distance (RPC). La magie de ces systèmes est qu'ils font des appels de procédure (ou de méthode) distants qui semblent être des appels de procédure locale (LPC).

Les RPC sont architecturés sur le modèle client / serveur. Par exemple, les objets CORBA qui exposent des méthodes à appeler par des objets distants sont appelés (et sont) des serveurs.

Présentation de MOM

Contrairement aux RPC, les MOM ne modélisent pas les messages comme des appels de méthode; au lieu de cela, ils les modélisent comme des événements dans un système de livraison d'événements. Les clients envoient et reçoivent des événements, ou «messages», via les API fournies par le MOM. Le MOM peut présenter des services d'annuaire qui permettent aux clients de rechercher une autre application qui agit en tant que serveur, ou il peut présenter des «canaux» polyvalents qui permettent à un groupe de clients de communiquer en tant qu'homologues, ou il peut présenter les deux options.

Toutes les applications communiquent directement entre elles à l'aide du MOM. Les messages générés par les applications ne sont significatifs que pour les autres clients car le MOM lui-même n'est qu'un routeur de messages (et dans certains cas un système de mise en file d'attente de messages également).

Les MOM sont de toutes formes et tailles

Tous les MOM partagent deux caractéristiques fondamentales: ils permettent la transmission de messages et la transmission de messages n'est pas bloquante. Au-delà de ces principes de base, les fournisseurs peuvent implémenter n'importe quel nombre d'interfaces et de services différents.

De nombreux MOM fournissent une interface de publication et d'abonnement permettant aux applications de publier et de recevoir les messages qui les intéressent. Cette interface peut prendre la forme d'un système basé sur des canaux ou d'un système plus simple dans lequel un client enregistre les types de messages il est intéressé à recevoir.

Les MOM de base fournissent uniquement la messagerie directe, aucun service supplémentaire. Les MOM avancés offrent la mise en file d'attente des messages et la livraison garantie, ainsi que la sécurité, le rassemblement de données multiplateforme, l'évolutivité et d'autres avantages.

MOMs en un coup d'œil

Voici une référence rapide pour vous aider à comprendre ce que sont les MOM.

Avantages MOM

  • Simple : les clients publient et s'abonnent

    publier et s'abonner est une abstraction de haut niveau utile pour ce que les applications doivent faire pour communiquer.

  • Facile : aucune configuration compliquée requise

    Les MOM sont faciles à installer et à utiliser, contrairement aux systèmes complexes basés sur RPC comme CORBA.

  • Générique : le même MOM peut être utilisé pour plusieurs applications

    Étant donné que tout système MOM donné n'est essentiellement qu'un transport de message générique, il peut être réutilisé dans différentes applications sans aucun travail supplémentaire.

  • Flexible : tout type de message peut être transmis

    Tout message peut être transmis par le MOM. Parce que le MOM ne comprend pas les messages, peu importe ce qu'ils sont.

Inconvénients de MOM

  • Générique : les applications doivent comprendre les messages

    Faire en sorte que les applications utilisent des messages au lieu d'appels de méthode peut être délicat, surtout si l'application repose sur le fait que les appels de méthode bloquent.

  • Inconnu : ne modélise pas les appels de méthode

    Les développeurs peu familiarisés avec les messages peuvent avoir du mal à comprendre comment les utiliser efficacement.

  • Asynchrone : les messages ne sont pas bloquants

    Les messages sont naturellement non bloquants. Cela rend plus difficile l'écriture d'applications nécessitant des appels de blocage.

  • Trop simple : pas de rassemblement de données

    Même les systèmes RPC simples rassemblent correctement les données. Les MOM simples peuvent simplement envoyer des messages dans lesquels les octets sont dans le désordre du point de vue du récepteur.

  • Non standard : les fournisseurs sont partout

    Les implémentations de fournisseur MOM font tout ... et rien.

    Caveat Emptor

    est la phrase à garder à l'esprit lors de l'examen des différentes offres des fournisseurs.

Quand les MOM sont-elles appropriées?

  • Lors de la communication, les applications doivent utiliser des messages
  • Lorsque le personnel de programmation n'est pas lié aux systèmes client / serveur et RPC
  • Lorsque CORBA / RMI et les systèmes associés sont trop complexes
  • Quand les systèmes RPC simples sont trop rudimentaires

Considérations de conception pour notre maman

Maintenant que l'arrière-plan n'est plus là, commençons à assembler notre MOM, le bus de messages . Nous utiliserons le MOM pour permettre la communication entre les clients de tableau blanc distribués. (Voir Ressources pour des liens vers des informations sur l'application de tableau blanc avec laquelle nous avons travaillé au cours des derniers versements.)

La considération motrice pour le bus de messages est qu'il fournit une interface de communication de haut niveau pratique aux objets d'application qui l'utiliseront.

Because a channel makes sense as the central service that the Message Bus should provide, the interface to the Message Bus is the Channel class. The client uses the Channel class to access every high-level function of the Message Bus, from subscribing and publishing to listing available channels in the system.

The Channel class exposes class methods that affect the Message Bus as a whole, or pertain to all channels. Each channel instance represents a single channel in the system and exposes channel-specific methods.

Two interfaces, ChannelListener and ChannelsUpdateListener, are provided for the purposes of subscribing to receive messages on a channel and receiving notification of channel addition, respectively.

The image below illustrates the Message Bus system architecture.

Under the hood

Under the hood, the Message Bus application uses class methods and data structures of

Channel

to keep track of channels. Listeners to a channel implement the

ChannelListener

interface, and objects that want to receive updates about channel adds implement the

ChannelsUpdateListener

interface. Registered listener objects are called back by

Channel

whenever anything interesting happens. All communication with the outside world is done with a transport-specific implementation of the

MessageBus

interface, such as

MessageBusSocketImpl

.

Each MessageBus implementation passes messages by talking to a corresponding message-passing server, called a broker, over a shared network transport such as sockets or URL/servlets. The broker routes messages among MessageBus instances, each of which corresponds to a Channel class.

Because these transport-specific implementations all implement the MessageBus interface, they are interchangeable. For example, a servlet-based MessageBus and broker can be used by Channel in place of the sockets-based MessageBus and broker.

Our Message Bus is a simple peer-to-peer system based on channels, making it suitable for use in a peer-to-peer application such as a collaborative system.

Using the Message Bus in a client application

These steps allow a client to use the Message Bus:

  1. Set up an instance of MessageBus.

     Channel.setMessageBus (new MessageBusSocketImpl (BROKER_NAME, BROKER_PORT)); 

    In this call, a new MessageBus implementation is created, with the broker identified by the arguments to the constructor call.

  2. Subscribe to a channel.

     Channel textChannel = Channel.subscribe ("text_channel", this); 

    This call returns an instance of the channel corresponding to the channel name argument. If the channel does not exist, it is created in the system.

    Passing this as an argument means that that caller is itself a ChannelListener. The caller can subscribe not just itself but any ChannelListener to the channel, or any number of listeners to a single channel.

  3. Publish a message to the channel.

     textChannel.publish (new String (myID + " says Hello!")); 

    Publishing a message is easy and entails nothing more than calling publish() on the chosen channel instance. Note that the message can be any type of object, as long as other clients on the channel can understand it, and the server has access to the message class file(s) (as detailed in the Using the Message Bus section)

Additional optional steps include:

  • Unsubscribe a listener from a channel.

     textChannel.unsubscribe (ChannelListener); 

    This method unsubscribes the named ChannelListener from the channel, which means that the listener will receive no new messages. Listeners should be unsubscribed in this manner when they are no longer needed.

  • Get a listing of channel names.

     Enumeration Channel.getChannelNames (); 

    This method returns the names of all channels available on the Message Bus.

  • Subscribe to receive newly added channels.

     Channel.subscribeChannelsUpdate (ChannelsUpdateListener); 

    A ChannelsUpdateListener can subscribe to get updates when channels are added to the Message Bus.

  • Stop receiving newly added channels.

     Channel.unsubscribeChannelsUpdate (ChannelsUpdateListener); 

    A ChannelsUpdateListener can be unsubscribed from channel addition updates. Listeners should be unsubscribed in this manner when they are no longer needed.

  • Add more listeners to a channel.

     textChannel.subscribe (ChannelListener); 

    This method allows the caller to subscribe additional listeners to a channel.

     String textChannel.getName (); 

    This method returns the name of this channel instance.

Interface ChannelListener

The ChannelListener interface must be implemented by any object that wants to be updated when a message comes in on a particular channel.

public interface ChannelListener { public void messageReceived (Channel channel, Object message); } 

In most cases, a client that asks for a Channel instance will subscribe itself to the channel and implement this interface itself, but it isn't necessary. In keeping with JDK 1.1 event adapters, a client can subscribe another object to a channel so that it will consume messages generated by the channel.

In fact, a single listener object can subscribe to multiple channels, which will call the listener's messageReceived() every time a message comes in on any of the channels. The messageReceived () method call provides access to the channel where the message appeared, allowing messageReceived () to separate messages by originating channel.

Interface ChannelsUpdateListener

ChannelsUpdateListener must be implemented by any object that wants to be updated when a channel is added.

public interface ChannelsUpdateListener { public void channelAdded (String name); } 

Class Channel

The Channel class serves two purposes:

  • It provides a simple abstraction as an interface to the client using the Message Bus
  • It maintains global state about available channels and passes messages from channels to the MessageBus implementation and receives updates from the MessageBus implementation

Channel instances are created and stored by Channel's static code. References to them are passed out by Channel.subscribe () as requested by the client. Each Channel instance is unique within the JVM process.

public class Channel {

protected static boolean busSet = false; protected static MessageBus bus; protected static Hashtable channels = new Hashtable (); protected static Vector channelsUpdateListeners = new Vector ();

public static synchronized void setMessageBus (MessageBus mb) throws IOException { if (!busSet) { bus = mb; bus.initBroker (); busSet = true; } else System.out.println ("Can't set MessageBus more than once per runtime!"); }

public static String getBrokerName () { return bus.getBrokerName (); }

public static Enumeration getChannelNames () { return channels.keys (); }

These class methods allow the MessageBus instance to be set once for each runtime, and return information about the bus and channel names, respectively.

 public static synchronized Channel subscribe (String name, ChannelListener cl) throws IOException { Channel ch; if (channels.containsKey (name)) ch = (Channel) channels.get (name); else { bus.addChannel (name); ch = new Channel (name); channels.put (name, ch); } ch.subscribe (cl); return ch; } 

Cette méthode de classe renvoie l'instance de canal correspondant au nom du canal. Il crée le canal et appelle MessageBuspour l'ajouter au système s'il n'existe pas déjà. Dès que le canal est créé, son auditeur initial y est enregistré.

// appelé par les clients pour enregistrer ChannelsUpdateListener public static void subscribeChannelsUpdates (ChannelsUpdateListener cul) {channelsUpdateListeners.addElement (cul); }

// appelé par les clients pour désinscrire ChannelsUpdateListener public static void unsubscribeChannelsUpdates (ChannelsUpdateListener cul) {channelsUpdateListeners.removeElement (cul); }