A Tour of Distributed
Programming with Ruby
with Mark Bates
Mark Bates
Mark Bates

Rubyist since 2005
Mark Bates

Rubyist since 2005

Freelance Consultant
Mark Bates

Rubyist since 2005

Freelance Consultant

Musician
Mark Bates

Rubyist since 2005

Freelance Consultant

Musician

Author
Distributed
Programming
with Ruby
Addison-Wesley (2010)
http://books.markbates.com
Programming
in CoffeeScript
Addison-Wesley (2012)
http://books.markbates.com
WHO? WHAT? WHEN? WHY?
      NOT SO MUCH HOW
No HOW?
What’s the point?
RMI - REMOTE METHOD INVOCATION
KNOWN ALIASES: DRB, DRUBY, DISTRIBUTED RUBY
What?
What?

Call methods remotely
What?

Call methods remotely

Standard Library, no additional packages
What?

Call methods remotely

Standard Library, no additional packages

Easy and Powerful
What?

Call methods remotely

Standard Library, no additional packages

Easy and Powerful

EXTREMELY DANGEROUS!!
When/Why?
When/Why?
Fast and Easy
When/Why?
Fast and Easy

Built into standard library
When/Why?
Fast and Easy

Built into standard library

Almost zero configuration
When/Why?
Fast and Easy

Built into standard library

Almost zero configuration

No need for controllers, JSON, XML, web servers,
etc...
When/Why?
Fast and Easy

Built into standard library

Almost zero configuration

No need for controllers, JSON, XML, web servers,
etc...

Closed Environment
Demo
require 'drb'
require 'time'

class UserService

  def authenticate(user, password)
    puts "attempting to authenticate: #{user}"
    if user == 'markbates' && password == 'password'
      return {:user => 'markbates',
              :email => 'mark@markbates.com',
              :created_at => Time.parse('August 24 1976')}
    end
    return '!!SECURITY BREACH!!'
  end

end

puts 'Starting UserService...'

DRb.start_service("druby://127.0.0.1:61676", UserService.new)
DRb.thread.join
require 'drb'

user_service = DRbObject.new_with_uri("druby://
127.0.0.1:61676")

loop do
  puts "username:"
  user = gets.chomp
  puts "password:"
  pass = gets.chomp
  puts user_service.authenticate(user, pass)
end
require 'drb'
require 'time'
require './user'

class UserService

  def authenticate(user, password)
    puts "attempting to authenticate: #{user}"
    if user == 'markbates' && password == 'password'
      return User.new({:user => 'markbates',
                       :email => 'mark@markbates.com',
                       :created_at => Time.parse('August 24
1976')})
    end
    return '!!SECURITY BREACH!!'
  end

end

puts 'Starting UserService...'
DRb.start_service("druby://127.0.0.1:61676", UserService.new)
DRb.thread.join
class User


  attr_accessor :user, :email, :created_at

  def initialize(attributes)
    attributes.each {|k, v| self.send("#{k}=", v)}
  end

  def email
    @email
  end

  def to_s
    "<User: user:#{self.user}, email:#{self.email},
created_at:#{self.created_at}>"
  end

end
class User


  attr_accessor :user, :email, :created_at

  def initialize(attributes)
    attributes.each {|k, v| self.send("#{k}=", v)}
  end

  def email
    @email
  end

  def to_s
    "<User: user:#{self.user}, email:#{self.email},
created_at:#{self.created_at}>"
  end

end
class User
  include DRbUndumped

  attr_accessor :user, :email, :created_at

  def initialize(attributes)
    attributes.each {|k, v| self.send("#{k}=", v)}
  end

  def email
    @email
  end

  def to_s
    "<User: user:#{self.user}, email:#{self.email},
created_at:#{self.created_at}>"
  end

end
Danger!
require 'drb'
# !!! UNSAFE CODE DO NOT RUN !!!
ro = DRbObject::new_with_uri("druby://127.0.0.1:61676")
class << ro
  undef :instance_eval
end
ro.instance_eval("`rm -rf *`")
RINDA
KNOWN ALIASES: NONE
What?
What?


DRb Service Registry
What?


DRb Service Registry

“configure-less”
What?


DRb Service Registry

“configure-less”

Redundant
When/Why?
When/Why?

Simple to Setup
When/Why?

Simple to Setup

No “hard-coded” IPs/Ports
When/Why?

Simple to Setup

No “hard-coded” IPs/Ports

Self-Describing
When/Why?

Simple to Setup

No “hard-coded” IPs/Ports

Self-Describing

Redundant
When/Why?

Simple to Setup

No “hard-coded” IPs/Ports

Self-Describing

Redundant

Interprocess Communication
Service 1


                             Service 2




               RingServer




Service 3

                            Service 4
Where can I find Service X?
                                                RingServer
                                              Listening on a
                                              broadcast UDP
                   Service X: 192.168.1.12
                                                 address



    Client
192.168.1.100

                Hi Service X @ 192.168.1.12

                                                Service @
                                               192.168.1.12
                 Hi There 192.168.1.100!
Demo
require 'drb'
require 'rinda/ring'
require 'rinda/tuplespace'

DRb.start_service
puts "Starting RingServer..."
Rinda::RingServer.new(Rinda::TupleSpace.new)
puts "...RingServer started."
DRb.thread.join
require   'drb'
require   'time'
require   './user'
require   'rinda/ring'

class UserService
  include DRbUndumped

  def authenticate(user, password)
    puts "attempting to authenticate: #{user}"
    if user == 'markbates' && password == 'password'
      return User.new({:user => 'markbates',
                       :email => 'mark@markbates.com',
                       :created_at => Time.parse('August 24 1976')})
    end
    return '!!SECURITY BREACH!!'
  end

end

DRb.start_service
ring_server = Rinda::RingFinger.primary
ring_server.write([:user_service, :UserService,
                    UserService.new,
                    'A simple Auth Service'],
                  Rinda::SimpleRenewer.new)

DRb.thread.join
require 'rinda/ring'

DRb.start_service
ring_server = Rinda::RingFinger.primary
service = ring_server.read([:user_service, nil, nil, nil])
user_service = service[2]

loop do
  puts "username:"
  user = gets.chomp
  puts "password:"
  pass = gets.chomp
  puts user_service.authenticate(user, pass)
end
require 'rinda/ring'
DRb.start_service
ring_server = Rinda::RingFinger.primary
services = ring_server.read_all([nil, nil, nil, nil])
puts "Services on #{ring_server.__drburi}"
services.each do |service|
  puts "#{service[0]}: #{service[1]} on
#{service[2].__drburi} - #{service[3]}"
end
Thank You
Distributed
Programming
with Ruby
http://books.markbates.com
http://metabates.com
http://github.com/markbates

DRb and Rinda