When creating a generic wrapper for grpc servers (for the purposes of 12-factor app configs and logging, for example), the exported grpc.Server.RegisterService method cannot be used from external packages because the generated grpc.ServiceDesc variables (grpc.go#L175) for services aren't exported.
Export a method for accessing the service descriptor (already done for other languages such as C++).
Exporting the service descriptor directly (though that's a bit sloppy).
Example of what I mean (but with all the logging and config taken out):
package rpc
...
func (server *Server) Serve(desc *grpc.ServiceDesc, def interface{}) {
...
server.listener, err = net.Listen("tcp", server.address)
server.grpcServer = grpc.NewServer()
server.grpcServer.RegisterService(desc, def)
server.grpcServer.Serve(server.listener)
}
My microservices project currently requires no configuration in the main files (everything is encapsulated into the child packages). I'd like to find a way to do this generically so I don't have to copy paste all the config and logging for each service I create. I'm going to work on making a fork for now that just adds this functionality, but seeing it in the main package would be super nice. Thanks!
I made the change on my forks for the two repos it touches:
I renamed the variable to follow Go style guildlines in a separate commit, but the above commits at least show the scope.
Can you explain more about why you want access to these? How will you be using them?
Sure thing. In my microservices project, I have a base "server" package that all microservices instantiate with. The server package contains a server.Initialize() method for initializing the databases, logging, configuration, etc. So with that context, the server package also configures the TCP hookups for gRPC (and starts serving on that TCP listener by the microservice calling a Serve function). I'd like to be able to pass in the service definition without having to know the gRPC service type. For example, I can't call RegisterAccountService for an account service from the base server package, I can only call the RegisterService method that can accept a general service descriptor, and pass the account service's service descriptor into the Serve function. That's why I made changes to accept that access pattern.
Additionally, I made modifications to the compiler such that only one service can be created per proto package and generated go package. It follows the Go package methodology a bit better, but ultimately that's my own design constraint / choice that I don't expect to follow through to upstream. Really I just think making the service descriptor accessible can give developers more design freedom. Thanks for responding.
Similar requirement in our side. A (generic) registration method without knowing the exact service type is needed.
It's also needed if you plan on doing heavy / lookaside load balancing using a generic registration pattern (just more ideas).
This might be a little cumbersome, but would this fall short of your needs, functionally speaking? If so, how?
func (m *MyServer) AddService(rf func(s grpc.ServiceRegistrar)) {
rf(m.grpcServer)
}
....
func main() {
m := NewMyServer()
m.RegisterService(func(s grpc.ServiceRegistrar) {
pb.RegisterFooServer(s, myFooServiceImpl{})
})
}
OR
func (m *MyServer) Registrar() grpc.ServiceRegistrar {
return m.grpcServer
}
....
func main() {
m := NewMyServer()
pb.RegisterFooServer(m.Registrar(), myFooServiceImpl{})
}
It's also needed if you plan on doing heavy / lookaside load balancing using a generic registration pattern (just more ideas).
You might be interested in our load balancing plugins: https://godoc.org/google.golang.org/grpc/balancer
This might be a little cumbersome, but would this fall short of your needs, functionally speaking? If so, how?
func (m *MyServer) AddService(rf func(s grpc.ServiceRegistrar)) { rf(m.grpcServer) } .... func main() { m := NewMyServer() m.RegisterService(func(s grpc.ServiceRegistrar) { pb.RegisterFooServer(s, myFooServiceImpl{}) }) }OR
func (m *MyServer) Registrar() grpc.ServiceRegistrar { return m.grpcServer } .... func main() { m := NewMyServer() pb.RegisterFooServer(m.Registrar(), myFooServiceImpl{}) }
This totally works, and I did consider it - but I found it cleaner to just modify the package. The modified grpc plugin is part of my base docker image, so it works.
Given that you can slurp up the ServiceDescs with something like this:
type serreg grpc.ServiceDesc
func (r *serreg) RegisterService(sd *grpc.ServiceDesc, interface{}) {
r = sd
}
var sd = &grpc.ServiceDesc{}
func init() {
pb.RegisterFooService(serreg(sd), pb.FooServer(nil))
}
It's probably fine to just go ahead an export these symbols. Maybe we can add a disclaimer about the Metadata field, or even say that it's only intended for direct use with grpc.RegisterService, and not to be introspected or modified (even as a copy).
At work, we're currently in the process of generalizing our gRPC initialization code as we have a lot of microservices that share basically identical grpc init code.
I'd love to be able to call a single method from the generalized library to init and serve a grpc service, needing to pass in as few parameters as possible in the process.
Right now, the consumer of the library needs to needlessly implement a Register interface whose only purpose is to call the pb.RegisterConcreteServer method for no other reason other than it's not possible to call RegisterService directly due to the unexported grpc.ServiceDesc.
Having access to it would make our API simpler to use.
Most helpful comment
Given that you can slurp up the ServiceDescs with something like this:
It's probably fine to just go ahead an export these symbols. Maybe we can add a disclaimer about the
Metadatafield, or even say that it's only intended for direct use withgrpc.RegisterService, and not to be introspected or modified (even as a copy).