-
Notifications
You must be signed in to change notification settings - Fork 101
Services
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:
- It should be of same type as defined by the rpc definition (in this case,
Foo::UserList
). - It should be a hash, respond to
to_hash
, or respond toto_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. - It should respond to the
to_proto
method. The object returned byto_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).