RRiBbit Internals

Internally, RRiBbit has several processing layers that sit between the RequestResponseBus and the listener that gets executed. All of these layers are specified by interfaces and almost all of those interfaces have multiple implementations. In this chapter, we will take you through each of the layers and discuss all implementations that RRiBbit has to offer.

Lets start off with an image that gives a graphical view of the layers:

Internal Architecture of RRiBbit

RequestResponseBus: On top, there is the RequestResponseBus, that you call when you want to make a request. The request that you make (the hint, the desired return type and the parameters) are all packed into a Request object. There is only one implementation of this interface, the DefaultRequestResponseBus.

In version 1 of RRiBbit, the next 2 layers, RequestDispatcher and RequestProcessor, did not exist and the RequestResponseBus talked to the ListenerObjectRetriever and ListenerObjectExecutor directly. For version 2, two extra layers were added to implement Remoting, the ability to call listeners that run on other machines. The RequestDispatcher represents the client-side of this layer and the RequestProcessor represents the server-side. More information on Remoting in the next chapter!

The DefaultRequestResponseBus supports multiple RequestDispatchers and every request will be sent to all of them. The responses will then be aggregated. Please keep performance considerations in mind when adding multiple RequestDispatchers to a DefaultRequestResponseBus, especially when using RRiBbit Remoting. In that case, every request will be sent to all remote machines and the DefaultRequestResponseBus will have to wait until all of them have returned.

If this is undesirable, an alternative is to create multiple DefaultRequestResponseBuses, each with its own RequestDispatcher. This does however, require the sender of the request to have knowledge about where the receiver is located (i.e. which RequestResponseBus to call). Although this contradicts the principle behind eventbuses (in that the sender does not have to know anything about the receiver), it could be a worthwile sacrifice for performance considerations.

RequestDispatcher: It's the job of the RequestDispatcher to give the Request to the RequestProcessor. There are currently 4 implementations of RequestDispatcher:

  • LocalRequestDispatcher: For local use. Does nothing, but just passes on the Request to a LocalRequestProcessor via a standard Java method call.

  • RmiRequestDispatcher: Passes on the request via Java RMI to a remotely available RmiRequestProcessor.

  • JmsRequestDispatcher: Passes on the request via JMS to a remotely available JmsRequestProcessor.

  • HttpRequestDispatcher: Passes on the request via HTTP to a remotely available HttpRequestProcessorServlet.

RequestProcessor: Gets the hint and the desired return type from the Request and gives those to the ListenerObjectRetriever, to get the ListenerObjects that have to be executed. Those are then passed to the ListenerObjectExecutor, along with the parameters from the Request. The RequestProcessor implementations correspond to the RequestDispatcher implementations:

  • LocalRequestProcessor: Does the above.

  • RmiRequestProcessor: Is not actually an implementation of RequestProcessor, because Java RMI does not allow Remote objects to implement non-Remote interfaces. Instead, an RmiRequestProcessor runs as a standalone Thread that receives Requests through RMI.

  • JmsRequestProcessor: Implements the MessageListener interface, so that it can be hooked up to a JMS infrastructure and handle Requests coming from a JmsRequestDispatcher.

  • HttpRequestProcessorServlet: Is not an implementation of RequestProcessor, but actually a Servlet that listens to Requests from the HttpRequestDispatcher.

ListenerObjectRetriever: This object can figure out which listeners need to be executed for a certain Request. It does that based on the hint and the desired return type. There are 2 implementations. Both of them support multiple ListenerObjectCreators, so that the creation of listeners can be done in a modular way, i.e. each group of listeners can have its own ListenerObjectCreator instead of one ListenerObjectCreator having to know about all of them. A ListenerObjectRetriever will return all listeners from all ListenerObjectCreators that match the given Request.

  • DefaultListenerObjectRetriever: Naieve implementation. Just examines each available ListenerObject to see if it matches.

  • CachedListenerObjectRetriever: Extends the default one by providing caching of search results in a Map, so that similar future requests can be processed much quicker.

ListenerObjectCreator: We've already seen this one in the previous chapter. It creates ListenerObjects from methods annotated with @Listener annotations. Please read the FAQ and the Javadocs for more information, to prevent unexpected behaviour. Implementations:

  • ObjectBasedListenerObjectCreator: Accepts Objects and scans their classes for methods annotated with @Listener. Uses those very same objects to execute those listeners on.

  • InstantiatingClassBasedListenerObjectCreator: Accepts classes or whole packages and scans them for methods annotated with @Listener. Target objects are obtained by simply attempting to instantiate the classes using the default constructor. If this is impossible, then the methods of the class in question will be ignored.

  • SpringBeanClassBasedListenerObjectCreator: Also accepts classes or whole packages and scans them for methods annotated with @Listener. Target objects are obtained by asking a Spring ApplicationContext for an implementing bean. See the previous chapter for more information on this.

ListenerObjectExecutor: Executes the ListenerObjects and returns a Response object containing all return values and Throwables that were obtained from the execution. It is up to the RequestResponseBus to decide what to do with the Response and that depends on the particular send-method that was being called. Some methods return only a single result, whereas others return a Collection of results. See the Introduction for more on this. There are 2 implementations:

  • SequentialListenerObjectExecutor: Executes ListenerObjects one after the other.

  • MultiThreadedListenerObjectExecutor: Executes ListenerObjects in parallel threads, for better performance. Please note that this will break your transactional context, if one was present. Each individual execution can still have its own transactional context.

And that's it! The building blocks of RRiBbit. RRiBbitUtil.createRequestResponseBusForLocalUse() will set you up with the following defaults:

public static RequestResponseBus createRequestResponseBusForLocalUse(ListenerObjectCreator listenerObjectCreator, boolean setInRRB) {

        ListenerObjectRetriever listenerObjectRetriever = new CachedListenerObjectRetriever(listenerObjectCreator);
        ListenerObjectExecutor listenerObjectExecutor = new MultiThreadedListenerObjectExecutor();
        LocalRequestProcessor localRequestProcessor = new LocalRequestProcessor(listenerObjectRetriever, listenerObjectExecutor);
        RequestDispatcher requestDispatcher = new LocalRequestDispatcher(localRequestProcessor);
        RequestResponseBus requestResponseBus = new DefaultRequestResponseBus(requestDispatcher);
        if(setInRRB) {
                RRB.setRequestResponseBus(requestResponseBus);
        }
        return requestResponseBus;
}

If you want different implementations, now you know how to wire them together. Or, you could even implement your own versions of the various interfaces!

If you want to use Remoting, that means that the RequestProcessor and everything below will actually run on a different machine! Currently, Java RMI, JMS and HTTP implementations are provided, complete with failover, loadbalancing and SSL. If you want to know how to set that up, please see next chapter!