Description
There were some changes in #3657 that make it harder to develop gRPC services and harder to find new unimplemented methods - I wanted to start a discussion around the new default and figure out why the change was made. I do understand this is in an unreleased version, so I figured a discussion would be better than a bug report or feature request.
From my perspective, this is a number of steps backwards for reasons I will outline below.
When implementing a gRPC service in Go, I often start with a blank slate - the service has been defined in proto files, the go and gRPC protobuf definitions have been generated, all that's left to do is write the code. I often use something like the following so the compiler will help me along, telling me about missing methods, incorrect signatures, things like that.
package chat
func init() {
// Ensure that Server implements the ChatIngestServer interface
var server *Server = nil
var _ pb.ChatIngestServer = server
}
This can alternatively be done with var _ pb.ChatIngestServer = &Server{}
but that theoretically leaves a little bit more memory around at runtime.
After this, I add all the missing methods myself (returning the unimplemented status) and start adding implementations to them until I have a concrete implementation for all the methods.
Problems with the new approach
- As soon as you embed the Unimplemented implementation, the Go compiler gets a lot less useful - it will no longer tell you about missing methods and because Go interfaces are implicit, if you make a mistake in implementing a method (like misspelling a method name), you will not find out until runtime. Additionally, because these are public methods, if they're attached to a public struct (like the commonly used
Server
), they may not be detected as an unused method. - If protos and generated files are updated, you will not know about any missing methods until runtime. Personally, I would prefer to know if I have not fully implemented a service at compile time rather than waiting until clients are running against it.
- IDE hinting is worse with the new changes - IDEs will generally not recommend a method stub for an unimplemented method if it's provided by an embedded struct because even though it's technically implemented, it does not have a working implementation.
I generally prefer compile time guarantees that all methods are implemented over runtime checks.
Benefits of the new approach
- Protos and generated files can be updated without requiring updates to server implementations.
Proposal
By default, the option requireUnimplementedServers
should default to false
. This option is more valuable when dealing with external protobufs which are not versioned (maybe there should be a recommendation to embed the unimplemented struct in this instance) and makes it harder to catch mistakes if you are developing a canonical implementation of a service which should implement all the available methods.
At least for me, the problems with the new approach vastly outweigh the benefits I've seen so far.