Blastoff: Hello, World!
Distribunaut uses DRb and Rinda to do most of its heavy lifting. The good news is that because you have already learned all about DRb and Rinda, you can easily jump into experimenting with Distribunaut.
As you'll remember from our look at Rinda, we need to start a RingServer before we can run any code. Distribunaut ships with a convenient binary to help make starting, stopping, and restarting a RingServer easy:
$ distribunaut_ring_server start
If you wanted to stop the RingServer, you would do so with the following command:
$ distribunaut_ring_server stop
You can probably guess how to restart the server. You should restart the RingServer between all these examples, just so things don't go a bit funny on you:
$ distribunaut_ring_server restart
So, with a RingServer running nicely as a daemon in the background, let's kick off things with a simple "Hello World" application. Let's start with a server. Keep in mind that, as we talked about earlier in the book, when we are using DRb and Rinda, applications can act as both a server and a client. So when we use the term "server" here, we are merely using it to describe a bit of code that serves up some content. So what does our HelloWorld class look like with Distribunaut? Let's see:
require 'rubygems' require 'distribunaut' configatron.distribunaut.app_name = :hello_world_app class HelloWorld include Distribunaut::Distributable def say_hi "Hello, World!" end end DRb.thread.join
First we require rubygems and then the Distribunaut library itself. After that we hit the first of two lines that make Distribunaut special.
Each Distribunaut "application" needs a unique name. When we talk about applications within Distribunaut, we are actually talking about a Ruby VM/process that contains one or more Distribunaut classes. The name of that application should be unique to avoid confusion. We will look at what can happen with redundant application, and class, names a bit later in this chapter.
To manage its configurations, Distribunaut uses the Configatron3 library. We set the application as follows:
configatron.distribunaut.app_name = :hello_world_app
This needs to happen only once per Ruby VM. If you set it multiple times, strange things can happen, so be careful. In our sample code we are setting the application name to :hello_world_app. We could just as easily set it to something like :server1 if we wanted to make it more generic for other Distribunaut classes we were planning on running in the same Ruby VM.
After we have set up our application name, the only other thing we have to do is include the Distribunaut::Distributable module in our HelloWorld class. Then we are ready to try to get a "Hello, World!" remotely.
Before we get to our client code, let's take a quick look at what the preceding HelloWorld class would've looked like had we used raw DRb and Rinda:
require 'rinda/ring' class HelloWorld include DRbUndumped def say_hi "Hello, World!" end end DRb.start_service ring_server = Rinda::RingFinger.primary ring_server.write([:hello_world_service, :HelloWorld, HelloWorld.new, 'I like to say hi!'], Rinda::SimpleRenewer.new) DRb.thread.join
Although the HelloWorld class part of it is relatively the same, much more noise is required at the end to get our HelloWorld instance into the RingServer. At this point it is also worth pointing out that in the Rinda version of HelloWorld we had to create a new instance of the class. This means that we can't call any class methods that HelloWorld may have. This includes the ability to call the new method and get a new instance of the HelloWorld class. We are stuck with that instance only. We did not do anything of the sort with the Distribunaut version of the class. In fact, you probably have noticed that we didn't make any calls to get it into the RingServer. We'll talk about why that is shortly. First, let's look at our client code:
require 'rubygems' require 'distribunaut' hw = Distribunaut::Distributed::HelloWorld.new puts hw.say_hi
If we were to run this code, we should see the following output:
What just happened there? Where did the Distribunaut::Distributed::HelloWorld class come from? How did it know to print "Hello, World!" when we called the say_hi method? All great questions.
The Distribunaut::Distributed module is "special." When you preface a constant such as HelloWorld in that module, it queries the RingServer and attempts to find a service that matches that constant. So, in our case it searched the RingServer for a service called HelloWorld. It found the HelloWorld class we created earlier and returned a reference to it. With that reference we could call the new method on that class, which returned a new instance of the HelloWorld class. And then we could call the say_hi method.
So if we didn't explicitly place our HelloWorld class in the RingServer, how did we access it? And how were we able to call a class method on it, when we know that you have to put instances only into a RingServer? The same answer applies to both questions. When we included the Distribunaut::Distributable module into the HelloWorld class, it created a Singleton wrapper class on-the-fly that then proxies all methods called on that proxy class onto the original HelloWorld class. With that we can put the Singleton instance into the RingServer. Then we can call class methods, which allows us to do things like call the new and get back a new instance of the class.
Having all of this happen automatically also helps clean up the usual supporting code you need to write to both set an instance into the RingServer and retrieve that instance later. Just look at what a plain-vanilla DRb and Rinda implementation of the client would look like:
require 'rinda/ring' DRb.start_service ring_server = Rinda::RingFinger.primary service = ring_server.read([:hello_world_service, nil, nil, nil]) server = service puts server.say_hi
This is not only more code, but also uglier code.