In large projects, dependencies between modules and code organization can prove a large challenge. This was the reason for creating RRiBbit, an Open Source Java application framework that eliminates compile-time dependencies and simplifies code structure. It is inspired by the Event Bus pattern, but improves upon this by being compatible with existing code and allowing bidirectional communication between components. If you're unsure about what an Event Bus exactly is, then you can familiarize youself with them here.
I'm sure you're wondering what RRiBbit has to offer that other Event Bus based frameworks, such as the Spring Event Bus or the JEE6 Event Bus, don't have. The answer is:
In other frameworks, you have to implement verbose Listener interfaces with onEvent() or onRequest() methods. In RRiBbit, you can just annotate your listener methods with a RRiBbit @Listener annotation.
Other frameworks don't work well with existing code because of the above. You have to refactor to the new pattern. With RRiBbit, you can just add @Listener annotations to existing methods and they will be listeners!
In other frameworks, the listeners can't actually send something back to the sender, so you have to hack around that, by modifying the parameter object or something. In RRiBbit, listener methods can just return a value by using a plain old Java return and that value will be passed to the sender of the event/request.
Other frameworks don't support executing listeners in multiple threads. RRiBbit can be configured to run listeners in separate threads if multiple listeners match an event/request, for optimal performance.
Other frameworks don't support remoting, they only work within the running virtual machine. RRiBbit listeners can actually run on other machines as well and invoking them does not require the least bit of effort! Furthermore, RRiBbit Remoting comes with with failover and loadbalancing without any additional configuration. It also supports SSL/TLS for optimal security.
The name RRiBbit comes from "Request-Response-Bus". Let's dig right into it, with an extremely simple example that demonstrates RRiBbit's capabilities. More details will be explained afterwards.
Let's assume we have an OrderPage, where people can place orders. When someone submits an order, it needs to be registered by the UserService and paid by the PaymentService. The PaymentService asks the MailService to send an email and the MailService asks the UserService for an email address. Hmm, maybe not so simple after all, but let's take a look:
public class OrderPage { private PaymentService paymentService; private UserService userService; public void submitOrder() { Integer userId = 1; BigDecimal amount = BigDecimal.TEN; paymentService.doPayment(userId, amount); userService.registerPayment(userId, amount); } } public class PaymentService { private MailService mailService; public void doPayment(Integer userId, BigDecimal amount) { //Do payment... mailService.sendPaymentEmail(userId, amount); } } public class UserService { public String getEmailAddress(Integer userId) { return "foo@bar.com"; } public void registerPayment(Integer userId, BigDecimal amount) { //Register payment in database... } } public class MailService { private UserService userService; public void sendPaymentEmail(Integer userId, BigDecimal amount) { String emailAddress = userService.getEmailAddress(userId); //Send email... } }
The "talking to interfaces" part is omitted for simplicity, in reality, you will probably have this. If the application stays as simple as this, it's quite manageable, but I think you can imagine an application with a hundred business-logic classes, with thousands of methods and a build time long enough to take a lunch break. In those cases, you want to get rid of your dependencies, so that you can develop and build components independently. Let's see what RRiBbit can do:
public class OrderPage { private RequestResponseBus rrb; public void submitOrder() { Integer userId = 1; BigDecimal amount = BigDecimal.TEN; rrb.send("doPayment", userId, amount); rrb.send("registerPayment", userId, amount); } } public class PaymentService { private RequestResponseBus rrb; @Listener(hint="doPayment") public void doPayment(Integer userId, BigDecimal amount) { //Do payment... rrb.send("sendPaymentEmail", userId, amount); } } public class UserService { @Listener(hint="getUserEmail") public String getEmailAddress(Integer userId) { return "foo@bar.com"; } @Listener(hint="registerPayment") public void registerPayment(Integer userId, BigDecimal amount) { //Register payment in database... } } public class MailService { private RequestResponseBus rrb; @Listener(hint="sendPaymentEmail") public void sendPaymentEmail(Integer userId, BigDecimal amount) { String emailAddress = rrb.send("getUserEmail", userId); //Send email... } }
Basically, every method call has been replaced by a call to the RequestResponseBus. The send() method takes a String as the first parameter, which corresponds to the hint of the appropriate Listener and a variable number of Objects as parameters to the corresponding method. Getting a response back (the email address) is no problem. It is generically typed and allows the classes to focus on their tasks without having to worry about the rest of the application. We have total decoupling, no class depends on one of the others anymore and we can easily swap one Listener for another and as you can see, we are using our existing methods! The RequestResponseBus has many more methods for sending requests, which we will discuss later.
We will first take the example one step further! When you send a request to the RRB, it will execute all Listeners that match that request, so you can call multiple Listeners in one call:
public class OrderPage { private RequestResponseBus rrb; public void submitOrder() { Integer userId = 1; BigDecimal amount = BigDecimal.TEN; rrb.send("processPayment", userId, amount); } } public class PaymentService { @Listener(hint="processPayment") public void doPayment(Integer userId, BigDecimal amount) { //Do payment... } } public class UserService { @Listener(hint="getUserEmail") public String getEmailAddress(Integer userId) { return "foo@bar.com"; } @Listener(hint="processPayment") public void registerPayment(Integer userId, BigDecimal amount) { //Register payment in database... } } public class MailService { private RequestResponseBus rrb; @Listener(hint="processPayment") public void sendPaymentEmail(Integer userId, BigDecimal amount) { String emailAddress = rrb.send("getUserEmail", userId); //Send email... } }
Look at the code now! The OrderPage only has to call "processPayment" and that's it! The services too, can just focus on their own problems, without having to look to the others. High cohesion, low coupling. When multiple Listeners match, the execution order is unspecified, so if that is a problem, you have to use two separate calls. RRiBbit does support concurrent execution of Listeners, so if the execution order does not matter, you get multi-threaded performance for free!
If you want a certain Listener to have two or more hints for matching, no problem! You can give a Listener as many hints as you want and it will match on all of them. For example, when a Listener method needs to be executed upon different requests, then you can give it multiple hints, so that it will get executed when either request is made.
So, what other methods does the RequestResponseBus have? I'll discuss them now shortly. For more comprehensive documentation, please refer to the Javadocs.
With this method, you can send a request to the RequestResponseBus and get exactly one Object back as a response. All Listeners that have the specified returnType and match the given parameters will get invoked. When multiple Listeners match the request, only one return value is returned. It's unspecified which one.
Same as above, but now you get a Collection of all return values of all invoked Listeners back.
All Listeners that match the given parameters will get invoked and you get nothing back.
All Listeners that match the given hint (specified in the Listener annotation) and the given parameters will get invoked, you get one return value back.
Same as above, but now you get a Collection of all return values of all invoked Listeners back.
By now, you can guess the meaning of the others:
void sendForNothingWithHint(String hint, Object... parameters);
<T> T sendForSingleOfClassWithHint(Class<T> returnType, String hint, Object... parameters);
<T> Collection<T> sendForMultipleOfClassWithHint(Class<T> returnType, String hint, Object... parameters);
And the last one:
This one is the one used in the above code. It is required to do the exact same thing as sendForSingleWithHint, meaning that it's just a convenience method. sendForSingleWithHint was chosen, because it's probably the most widely used one.
It's important to remember that in every case, all Listeners that match the request will get executed, even when you only want a single response. This is to improve predictability. You can use send() (sendForSingleWithHint()) in creative ways: If multiple listener methods match a request, but only one of them returns something (the other ones are of type "void"), then you can call multiple Listeners in one call and still know exactly what you'll get back, since there's only one return value! send() will simply return null if none of the Listeners return something.
So, how about exception handling? Simple. If one of the executed Listeners throws a Throwable, then that Throwable is thrown as is. If more than one Listener throws anything, then a MultipleThrowablesOccurredException is thrown, containing all thrown Throwables.
We're almost there! We will now discuss some classes that you need to make all this work! Don't worry, it's really simple and 2 lines of code will get you on your way with RRiBbit!
Lets start with the ListenerObjectCreator, this is the object that creates the listeners. Listeners are represented by so-called ListenerObjects, that contain everything they need to be able to handle requests, such as:
Their return type
The java.lang.reflect.Method object that needs to be run
A target Object to run that Method on
(Optional) The hint String from the Listener annotation
A ListenerObjectCreator creates ListenerObjects from methods with Listener annotations. There are several of them, for Object based, Class based, Package based and even Spring bean based creation. That last one is declared in your Spring configuration and takes the Spring beans as target objects for the ListenerObjects, to run the Methods on. This means that all Spring proxy stuff, such as AOP and Transactional stuff, will work fine with RRiBbit! Also, you may have run into Spring's "Inner Transactional Calls" problem, where you want to invoke a @Transactional method from a method in the same class. Now Spring Transactions will not work, because the method call will not go through the Spring proxy. When you use a RRiBbit call instead, the Spring @Transactional method will work as expected. Please refer to the Javadocs for a detailed description of the available ListenerObjectCreators.
Once the ListenerObjectCreator is created, you can let RRiBbit do the rest of the work for you. If you want to use RRiBbit only within the running virtual machine and do not want to call Listeners that run on different machines, you can just pass your newly created ListenerObjectCreator to RRiBbitUtil.createRequestResponseBusForLocalUse() and you're good to go! You get multithreading and caching all for free.
RRiBbit even has a class called RRB, that provides static, global access to a RequestResponseBus instance, so that you don't have to pass it around everywhere. You can then simply use RRB.get() from anywhere in your code to pass a request to the bus.
If you want to learn more about how to use RRiBbit, the internals of RRiBbit, or RRiBbit Remoting, please see the Documentation or if you want to download RRiBbit, you can go here.