Rinda 101 1

Posted by Jim Tue, 12 Feb 2008 00:12:00 GMT

Background

When building a software system composed of multiple decoupled components, the need typically arises for interprocess coordination and communication. As an example, say that we have a customer requirement that allows emails to be sent based on certain events or conditions that occur while the user is interacting with a web application. We build an email component that takes care of the details of actually sending of the email. The web application could use the email component directly, but that would introduce a dependency that we’d rather avoid. How can the two processes communicate without creating a dependency between the two?

If you’re using Ruby, there’s an option that is built right into the standard library. The Rinda module implements the Linda distributed computing paradigm in Ruby. That’s great, but what is Linda? Linda is a coordination language developed by a couple of Yale researchers in the early 1990s. Linda itself is built on the concept of a tuplespace. A tuple is an ordered list of objects, and a tuplespace contains a set of tuples which can be accessed concurrently. A basic set of operations were defined by the language to allow reads, writes and takes.

Getting back to the example, how can Rinda be utilized to enable communication between the web application and the email component? First, a tuplespace will need to be setup that both components can access. When a condition arises within the web application that requires an email to be sent on its behalf, a specific tuple will be written to the tuplespace. The email component is looking for tuples that match a specific pattern. When it finds the tuple written by the web application that matches this pattern, it processes the request and reports back to the tuplespace when finished. In this way, we end up with only data coupling between the two processes, which is a much looser coupling than a direct dependency. This type of approach bears some resemblance to a blackboard system.

Now that we know a little bit about what Rinda can buy us, let’s dive in and try it out.

Hello World

Let’s put together a simple server and client to demonstrate how each works.

Here’s a simple server (server.rb):

require "rinda/tuplespace"

port = 4000
ts = Rinda::TupleSpace.new
DRb.start_service("druby://:#{port}", ts)
puts "Rinda listening on #{DRb.uri}..."
DRb.thread.join

We start by setting the default port to 4000. We then create a new TupleSpace object. The call to start_service starts a local dRuby server listening on port 4000. Passing the TupleSpace as the second parameter sets the server’s front object to the TupleSpace. The URI of the server is output, and the program waits for a kill signal, and we have a Rinda server ready to be accessed by a client. To start the server:

$ ruby server.rb

Here’s a simple interactive client (client.rb). It allows input to be gathered on standard input, with the form .

require "rinda/rinda"
include Rinda

port = 4000
ts = DRbObject.new(nil, "druby://:#{port}")

while message = gets
  begin
    args = message.split(" ")
    method = args.shift.to_sym
    topic = args.shift.to_sym
    message = args.shift
    message = nil if message == "nil"
    tuple = [topic, message]

    puts ts.send(method, tuple)
  rescue Exception => e
    puts e.message
  end
end

We’re using the send method in order to invoke the method specified on standard input. There’s also a conversion between nil as a string into nil proper. This is necessary because of wildcards, which we’ll get to in a minute. With the server running, run the client to start a new interactive session:

$ ruby client.rb

Below is a sample session (output is indented for clarity):

write message hello
    Rinda::TupleEntry:0x4f998
write message world
    Rinda::TupleEntry:0x4cfa4
read message nil
    message
    hello
take message nil
    message
    hello
take message nil
    message
    world

The inputs above translate to these method calls:

ts.write([:message, "hello"]) => Rinda::TupleEntry:0x4f998
ts.write([:message, "world"]) => Rinda::TupleEntry:0x4cfa4
ts.read([:message, nil]) => "hello"
ts.take([:message, nil]) => "hello"
ts.take([:message, nil]) => "world"

Note that reads do not remove the tuple from the tuplespace. The tuple is left untouched such that other clients can access the same data. This is an important concept when dealing with synchronization that we’ll return to in a future post.

The structure of the patterns used in the read and take methods deserve some additional attention. When the tuplespace is queried through a read or a take, there are two criteria used to determine if a tuple matches. First, the length of the tuple must match the pattern’s length. In the example above, all patterns are pairs (2-tuples), so only tuples that are pairs will qualify. Second, the tuple must match the pattern specified. When specifying a pattern, nil is used as a wildcard, which will match anything.

Assume that our tuplespace has the following tuples:

1) [:message]
2) [:message, "hello"]
3) [:message, "hello", "world"]

If the pattern [nil] is specified, e.g. ts.read([nil]), only #1 will match.

If the pattern [:message, nil] is specified, only #2 will match. Of course, [:message, “hello”] would also result in #2 matching as well, while [:message, “world”] would not.

Using [:message, nil, nil], [:message, “hello”, nil], or [:message, nil, “world”] would all result in #3 matching. So it is important to remember that patterns are matched on both length and content of the tuple.

Conclusion

We haven’t even scratched the surface of what can be done with Rinda. This introduction only gets the simplest client and server talking to each other. Stay tuned for more.

Sharing a Database Among Multiple Rails Applications 4

Posted by Jim Wed, 21 Feb 2007 01:40:00 GMT

A sign of the success of Ruby on Rails is the size of applications being written with it. Currently, we are working on a project with many interconnecting modules that we split up into separate Rails applications. However, the business domain was the same among the projects, resulting in the different projects sharing the same database. This article discusses the way we’ve accomplished this.

Create a Lib Project
We first need a place to pull our common classes into. This will not be a Rails application; it will just be a Ruby module that your Rails application will require. Our directory structure will look something like this:

/projects/my_lib
--> lib
--> spec

Now we can move on to the actual refactoring.

Pull the Models Out
The first step is the easiest: pull out the models from the app/models directory of your Rails application into the lib directory of your new library. In the config/environments.rb file within your Rails application, you will need to add this new directory to your Ruby search path by adding this line to the end of the file.

$: << File.dirname(__FILE__) + '/your_lib_path'

More on the Ruby search path later.

Pull the Tests Out
Let’s move on to making our specs pass. Now that we don’t have the Ruby environments loaded up as part of our test helpers (spec_helper for the rspecers out there), we need to establish an Active Record connection ourselves. Create a spec helper for our lib specs and start it with the connection to the database like so.

require 'rubygems'
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter  => "postgresql",
  :host     => "localhost",
  :username => "rails",
  :password => "",
  :database => "_test"
)

This is the same way Rails sets up your database connections for active record when you load the Rails environment. Now your specs just need to require this new spec_helper in order to get Active Record functioning.

Use Models Across Projects
Now you are free to use the same database across many projects. To do this, you just need to either require a single lib file that loads all the models, or you could put the lib directory in your Ruby search path.

Ruby Search Path vs. Lib Require File
The new common library will need to be included in your rails application. There are two methods we have used to do this. The first is to create a library file that does nothing more than requires all of the common lib files, looking something like the following.

require File.expand_path(File.dirname(__FILE__) + '/my_lib/lib/file_a'
require File.expand_path(File.dirname(__FILE__) + '/my_lib/lib/file_b'
require File.expand_path(File.dirname(__FILE__) + '/my_lib/lib/file_c'
require File.expand_path(File.dirname(__FILE__) + '/my_lib/lib/file_d'
require File.expand_path(File.dirname(__FILE__) + '/my_lib/lib/file_e'

The problem with this approach is it requires you to sequence the require statements in the appropriate order, or else you will get errors about missing objects. The benefit is that you load the entire environment in one place rather than adding require statements at the top of each file to include the needed external dependencies. These code files that do nothing but require files are no stranger to Ruby; however, they must be managed properly as they become temperamental and irritable when dependencies are mixed up.

The other approach we have used is what I recommended above. In your Rails application’s environment, add the lib directory to your search path, and include the require statements as needed in each model file. Of course, by including one file you will be implicitly including all of the dependencies. Using this technique, you can eliminate files that just require other files.