RabbitMQService is a Java framework that facilitates the communication between client and server via a RabbitMQ connection. This framework abstracts the implementation of client to server communication, allowing developers to spend more time writing the interesting pieces of their application and less time managing service connections.
RabbitMQService is an ideal communication framework for IoT development. Unlike many RESTful implementations, this communication framework does not requre the client and device to be connected to the same network. Instead of a device hosting a web server, the device connects to a RabbitMQ channel and processes requests persisted to the messaging queue. If the RabbitMQ Service is running on a server accessible to the public web, then requests can be made from client to device without the need to be connected to the same local network.
There are other benefits to using a messaging queue. One useful benefit -- especially for IoT development -- is the way a queue handles communication failures. If a device temporarily goes offline then any requests made to the device will persist in the message queue and will be processed once the device comes back online. Another useful benefit is the queues ability to manage contention by queueing requests.
IoT devices are generally small and underpowered, most a single-core processor and less than 1G of memory. Running a web server (e.g. Apache, Nginx) is possible but limited resources make them ineffective. With so few resources, a Raspberry Pi running an Apache HTTP server will timeout under a small load. Communicating through a message queue consumes far fewer resources because the queue persists requests remotely until they're processed by the device. Since the queue persists requests until they're processed, the device can process requests one-by-one without fear of dropping requests; in essence, the device can pace itself to ensure it's not being overloaded with requests.
This gif shows how requests are sent from a client to a device. Clients submit requests to the RabbitMQ Server where the requests are temporarily persisted until the IoT Device processes them. If the IoT Device cannot connect to the internet, then the RabbitMQService queues the requests until the IoT Device comes back online and processes them. This fault tolerant and contentionless communication mechanism is ideal for IoT devices with unreliable internet connections and limited computational resources.
A RabbitMQService is defined by a denifitions JSON file. This JSON file defines the application's name, package, configuration, api, data models, and exceptions. These definitions are exposed to client applications, they are contracts that define the behavior of an application. A Maven Plugin ingests the JSON definition file and generates a Java package that contains classes and interfaces implementing the defined behavior. There are two important files in the generated output: an interface and a client.
An interface defines the public methods of the application; services implement the interface and process requests made from a client. Interface methods must return a data model defined in the definitions file (or void) and they can optionally throw exceptions if they are defined.
Clients also implement the interface generated by the definition file, but they're responsible for submitting requests, parsing responses, and handling errors. Clients have the same behavior as the server; the same methods return the same data models and throw the same exceptions. Implementing a client is trivial but requires a decent amount of boiler plate code. As such, clients are automatically generated via the Maven plugin and are included as a dependency to any application that wants to use the call sevice.
Using this JSON definition file as an example, the following Java package is produced after the definitions file is generated. Since the configuration, client, API, and data models are all automatically generated, all a developres needs to do is implement the API and start the application. To make requests to the application, developers import and use the generated client. Examples of a service implementation and client usage are shows below.
public class RMQExampleService implements RMQExampleApi {
private final Random random = new Random();
private final ConcurrentHashMap<Integer, Person> people = new ConcurrentHashMap<>();
public Person getPerson(int id) {
return people.get(id);
}
public Person updatePerson(Person person) throws PersonNotFoundException {
if(!people.containsKey(person.getId())) {
PersonNotFoundException e = new PersonNotFoundException();
e.setId(person.getId());
throw e;
}
people.put(person.getId(), person);
return people.get(person.getId());
}
public Person putPerson(Person person) {
person.setId(random.nextInt());
people.put(person.getId(), person);
return person;
}
public static void main(String[] args) {
String host = RMQExampleConfiguration.Host.TEST.getValue();
String channel = RMQExampleConfiguration.Channel.TEST.getValue();
RMQApplication.start(host, channel, new RMQExampleService(), RMQExampleApi.class);
}
}
public class RMQExampleClientConsumer {
public static void main(String[] args) throws Exception {
RMQExampleClient client = new RMQExampleClient(Host.TEST, Channel.TEST);
try {
// Put a person
Person person = new Person();
person.setFirstName("Zachary");
person.setLastName("Miller");
person = client.putPerson(person);
System.out.println(
person.getFirstName() + " " + person.getLastName() + " (" + person.getId() + ")"
);
// Get a person
int id = person.getId();
person = client.getPerson(id);
System.out.println(
person.getFirstName() + " " + person.getLastName() + " (" + person.getId() + ")"
);
// Update a person
person = client.getPerson(id);
person.setFirstName("Zack");
System.out.println(
person.getFirstName() + " " + person.getLastName() + " (" + person.getId() + ")"
);
// Update nonexistent person
person.setId(12345);
client.updatePerson(person);
}
catch(PersonNotFoundException e) {
System.out.println("The person " + e.getId() + " does not exist.");
}
finally {
client.close();
}
}
}