Section 20.3. Rinda: A Ruby Tuplespace


20.2. Case Study: A Stock Ticker Simulation

In this example, we assume that we have a server application that is making stock prices available to the network. Any client wanting to check the value of his thousand shares of Gizmonic Institute can contact this server.

We've added a twist to this, however. We don't just want to watch every little fluctuation in the stock price. We've implemented an Observer module that will let us subscribe to the stock feed; the client then watches the feed and warns us only when the price goes above or below a certain value.

First let's look at the DrbObservable module. This is a straightforward implementation of the Observer pattern from the excellent book Design Patterns, published by Addison-Wesley and authored by the so-called "Gang of Four" (Gamma, Helm, Johnson, and Vlissides). This is also known as the Publish-Subscribe pattern.

Listing 20.1 defines an observer as an object that responds to the update method call. Observers are added (by the server) at their own request and are sent information via the notify_observers call.

Listing 20.1. The drb Observer Module

module DRbObservable   def add_observer(observer)     @observer_peers ||= []     unless observer.respond_to? :update       raise NameError, "observer needs to respond to `update'"     end     @observer_peers.push observer   end   def delete_observer(observer)     @observer_peers.delete observer if defined? @observer_peers   end   def notify_observers(*arg)     return unless defined? @observer_peers     for i in @observer_peers.dup       begin         i.update(*arg)       rescue         delete_observer(i)       end     end   end end

The server (or feed) in Listing 20.2 simulates the stock price by a sequence of pseudorandom numbers. (This is as good a simulation of the market as I have ever seen, if you will pardon the irony.) The stock symbol identifying the company is used only for cosmetics in the simulation and has no actual purpose in the code. Every time the price changes, the observers are all notified.

Listing 20.2. The drb Stock Price Feed (Server)

require "drb" require "drb_observer" # Generate random prices class MockPrice   MIN = 75   RANGE = 50   def initialize(symbol)     @price = RANGE / 2   end   def price     @price += (rand() - 0.5)*RANGE     if @price < 0       @price = -@price     elsif @price >= RANGE       @price = 2*RANGE - @price     end     MIN + @price   end end class Ticker # Periodically fetch a stock price   include DRbObservable   def initialize(price_feed)     @feed = price_feed     Thread.new { run }   end   def run     lastPrice = nil     loop do       price = @feed.price       print "Current price: #{price}\n"       if price != lastPrice         lastPrice = price         notify_observers(Time.now, price)       end       sleep 1     end   end end ticker = Ticker.new(MockPrice.new("MSFT")) DRb.start_service('druby://localhost:9001', ticker) puts 'Press [return] to exit.' gets

If you are on a Windows platform, you may have difficulty with the exit idiom used here. The gets on Windows tends to hang the main thread. If you experience this, you should use a DRb.thread.join instead (and use a control-C to kill it).

Not surprisingly, the client (in Listing 20.3) begins by contacting the server. It gets a reference to the stock ticker object and sets its own desired values for the high and low marks. Then the client will print a message for the user every time the stock price goes above the high end or below the low end.

Listing 20.3. The drb Stock Price Watcher (Client)

require "drb" class Warner   include DRbUndumped   def initialize(ticker, limit)     @limit = limit     ticker.add_observer(self)   # all warners are observers   end end class WarnLow < Warner   def update(time, price)       # callback for observer     if price < @limit       print "--- #{time.to_s}: Price below #@limit: #{price}\n"     end   end end class WarnHigh < Warner   def update(time, price)       # callback for observer     if price > @limit       print "+++ #{time.to_s}: Price above #@limit: #{price}\n"     end   end end DRb.start_service ticker = DRbObject.new(nil, "druby://localhost:9001") WarnLow.new(ticker, 90) WarnHigh.new(ticker, 110) puts "Press [return] to exit." gets

You may wonder about the DRbUndumped module referenced in Listing 20.3. This is included in any object that is not intended to be marshalled. Basically, the mere presence of this module among the ancestors of an object is sufficient to tell drb not to marshal that object. In fact, I recommend you look at the code of this module. Here it is in its entirety:

module DRbUndumped   def _dump(dummy)     raise TypeError, "can't dump"   end end


The stock watcher application we saw in this section is long enough to be meaningful but short enough to understand. There are other ways to approach such a problem. But this is a good solution that demonstrates the simplicity and elegance of distributed Ruby.




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net