- Author(s): vjpai
- Approver: ctiller
- Status: Approved
- Implemented in: C++
- Last updated: July 6, 2017
- Discussion at: https://groups.google.com/forum/#!topic/grpc-io/Z8S9P4NzGHs
Maintain separation of C++ classes and functions that are intended to be directly used by application developers from those that are supposed to be used only through codegen or for that exist for gRPC's internal implementation (e.g., interactions with the core API).
Essentially all of gRPC C++ lives in namespace grpc
, giving the
appearance that all portions are equally intended as part of the
publicly-exposed API. This gRFC aims to clarify this by categorizing
the gRPC C++ wrapping, moving some components to namespace grpc::internal
, and moving some class constructors to private
(with
friends or appropriately-named static
factories for internalized generation).
N/A
The C++ gRPC wrapping includes numerous classes and functions, but they fall into five separate categories:
- Documented for public use by end-user client/server applications
- Intended for interfacing with serialization layers such as protobuf
- Intended for use through a code-generation layer
- Intended for use in the internal implementation of gRPC C++
- Intended for interfacing with gRPC core
This gRFC aims to clarify that the first two are intended for public
use (the second one to support custom serializers), but that the last
three should not be used as such. This will be achieved by
moving classes and functions in the latter three categories to namespace grpc::internal
.
-
Accessed through codegen
RpcMethod
BlockingUnaryCall
-
gRPC C++ implementation
CallHook
CallOpSetInterface
CallOpSet
SneakyCallOpSet
CallNoOp
RpcMethodHandler
UnknownMethodHandler
ClientStreamingHandler
ServerStreamingHandler
TemplatedBidiStreamingHandler
BidiStreamingHandler
StreamedUnaryHandler
SplitServerStreamingHandler
-
gRPC C++ interface with core
CompletionQueueTag
Call
CallOpSendInitialMetadata
CallOpSendMessage
CallOpClientSendClose
CallOpServerSendStatus
CallOpClientRecvStatus
CallOpRecvInitialMetadata
CallOpRecvMessage
CallOpGenericRecvMessage
MetadataMap
Additionally, some classes in the first category are only supposed to
be created through the code generator or method handlers. To support
this, we will privatize their constructors and allow them to be
created only by internalized friend
classes or through static
factories invoked by the code generator (which are themselves moved to
a new member struct
called struct internal
in each of the classes).
ClientReader
ClientWriter
ClientReaderWriter
ServerReader
ServerWriter
ServerReaderWriter
Whereas the code generator could previously use new ::grpc::ClientReader<R>
, it will now use
::grpc::ClientReader<R>::internal::Create
as a result of this
proposal. This is slightly bulkier but it makes it clear that public
code is not supposed to directly construct or new
an object of type
ClientReader
.
In some cases, the static
factory already existed, but will now be
moved to a struct internal
member class within the class.
ClientAsyncResponseReader
ClientAsyncReader
ClientAsyncWriter
ClientAsyncReaderWriter
The effect on the above classes is that the code generator will call
::grpc::ClientAsyncResponseReader::internal::Create
instead of
::grpc::ClientAsyncResponseReader::Create
. This change is much less
significant than the above.
Interface-only classes that relate to the first category will also be
moved to the grpc::internal
namespace, except for the immediate
parents of final user-exposed classes (since those interfaces are
useful for creating mock versions).
AsyncReaderInterface
AsyncWriterInterface
ClientAsyncStreamingInterface
ClientStreamingInterface
ReaderInterface
ServerAsyncStreamingInterface
ServerStreamingInterface
WriterInterface
Despite the categories described above, the language does not provide a direct mechanism for specifying this difference, so public users can actually use any part of the wrapping that they choose. For example, Tensorflow bypasses the gRPC code generator and directly uses gRPC components in each category except for core-interactions.
Although circumventing apparent abstraction layers can be a bid to gain performance, it can also hurt achieved application performance by locking applications into deprecated gRPC implementation mechanisms instead of allowing the latest performance enhancements in gRPC C++.
The second category is remaining untouched because we have advertised
gRPC as accepting pluggable serializers, and this should remain
untouched. These include SerializationTraits
, Slice
, and other
related classes. Even though these are not used as a common part of
the documented public API, there are external users that reasonably
want these and we can continue supporting them.
This is implemented in C++ in pull-request
grpc/grpc#11572. It is intended as a safe
build-only change. In fact, only two places in our own test
suite
directly used internal classes and they have been resolved as follows:
bm_cq
: completion queue processing benchmark. Since this is a microbenchmark, allow it to accessinternal::CompletionQueueTag
- QPS worker: this benchmark was directly using
ReaderInterface
andWriterInterface
knowing that they were the parent-classes of various sub-classes that could be used in the test. Instead, switch this to atemplate
and use duck-typing instead of inheritance.
Ongoing work will be to maintain discipline in deciding whether a
class or function belongs in grpc
or grpc::internal
as well as
deciding whether a class truly needs a public constructor.
This PR will cause problems for gRPC users that are using the interfaces that are intended for internal use. This should not be a large number of gRPC users since these APIs were not documented for public use.