Skip to content
BJ Neilsen edited this page Feb 20, 2014 · 2 revisions

Services are simply classes that have endpoint methods defined. Think of them as a Rails router+controller in one. Here's what one looks like in protobuf:

package foo;
message UserRequest {
  optional string email = 1;
}
message UserList {
  repeated User users = 1;
}
service UserService {
  rpc Find (UserRequest) returns (UserList);
}

And the equivalent ruby stub for the service (see the compiling guide):

# lib/foo/user.pb.rb
module Foo
  # UserRequest and UserList Class definitions not shown for succinctness.
  class UserService < ::Protobuf::Rpc::Service
    rpc :find, UserRequest, UserList
  end
end

!!! Important Note !!! The UserService class here is a stub. You should not provide your implementation in this generated file as subsequent compiles will wipe out your implementation.

Now that you have a generated service stub, you'll want to require it and provide the implementation. Create a service implementation file in your project. In Rails I'd put this in app/services/foo/user_service.rb.

# app/services/foo/user_service.rb
require 'lib/foo/user.pb'

# Reopen the service class and provide the implementation for each defined RPC method.
module Foo
  class UserService

    # request -> Foo::UserRequest
    # response -> Foo::UserResponse
    def find
      # request.email will be the unpacked string that was sent by the client request
      users = users_by_email.map(&:to_proto)
      respond_with(:users => users)
    end

    private

    def users_by_email
      User.find_by_email(request.email)
    end

  end
end

The server creates a new instance for every request. To handle an RPC request simply provide the implementation for the same method name (camel-case version). As with Rails controllers, it's common to implement private methods to aid in code quality and simplicity.

Every instance has a request and response object used for fulfilling the call, again, similar to a rails controller action. You should never attempt to modify the request object. The response object however should be modified or replaced entirely. If you need to replace the response object (often a cleaner solution), simply use respond_with(new_response). The object passed to respond_with should conform to one of three properties:

  1. It should be of same type as defined by the rpc definition (in this case, Foo::UserList).
  2. It should be a hash, respond to to_hash, or respond to to_proto_hash. The hash will be used to construct an instance of the defined response type and should therefore conform to the appropriate fields for that type.
  3. It should respond to the to_proto method. The object returned by to_proto should be an instance of the defined response type.

If at any time the implementation encounters an error, the client can be instructed of the error using rpc_failed:

#...
def find
  if request.email.blank?
    rpc_failed 'Unable to find user without an email'
  else
    # query/populate response
  end
end
#...

Using rpc_failed ensures that the client's on_failure callback will be invoked instead of the on_success callback (see the Clients guide for more on client callbacks).

Clone this wiki locally