Protoplasm
Protoplasm makes is easy to define an RPC server/client which is backed by protobuf through Beefcake.
Defining your service endpoints
The current service model is very simple. You can send only one type of protobuf object (the request object). This object must have an enum and a series of optional command fields to allow it to send commands. Here is an example of how to create a request object type:
class Command
include Beefcake::Message
module Type
PING = 1
UPCASE = 2
end
required :type, Type, 1
optional :ping_command, PingCommand, 2
optional :upcase_command, UpcaseCommand, 3
end
In this case, your request object would be able to accept one, and only one subcommand object. Those types are PingCommand
, UpcaseCommand
and EvenCommand
.
So, in order to mark this Command
class as your request class, you'd do the following:
module Types
include Protoplasm::Types
# .. your actual classes would go here
request_class Command
request_type_field :type
end
Defining your response objects
Every subcommand can choose to relay back no objects, one object, or stream any number of objects. Those objects must all be of the same type.
To define which objects you expect back, you must add the following.
module Types
rpc_map Command::Type::PING, :ping_command, nil
rpc_map Command::Type::UPCASE, :upcase_command, UpcaseResponse
rpc_map Command::Type::EVEN, :even_command, EvenResponse, :streaming => true
In this case, this would define the ping command as returning no object, the upcase command returns a single object, of type UpcaseResponse
, and the even command return any number of EvenResponse
objects.
Server implementation
Currently there is a single server implementation EMServer
, which defines a non-blocking EventMachine based server. To create an EMServer
, you subclass Protoplasm::EMServer
and setup handlers for each of your command types. For example, a worker server could look like this:
class Server < Protoplasm::EMServer
def process_ping_command(ping_command)
# do nothing
end
def process_upcase_command(upcase_command)
send_response(:response => cmd.word.upcase)
end
def process_even_command(even_command)
(1..even_command.top).each do |num|
send_response(:num => num) if num % 2 == 0
end
finish_streaming
end
end
This server then could be started with Server.start(3000)
which would start on port 3000 and process requests.
Client
Currently there is a single client implementation: BlockingClient
. It defines a blocking TCPSocket
based client. To create a client for this example, you would do the following.
class Client < Protoplasm::BlockingClient
def initialize(host, port)
super(Types, host, port)
end
def ping
send_request(:ping_command)
end
def upcase(word)
send_request(:upcase_command, :word => word).response
end
def evens(top)
send_request(:even_command, :top => top) { |resp| yield resp.num }
end
end
Look at the full example under test/test_helper.rb
.