RPC Scope
and Listener-event Scope
(a single message or a handler call) for gRPC client and server apps.
Copyright 2021 Piotr Morgwai Kotarbinski, Licensed under the Apache License, Version 2.0
latest release: 15.0
(javadoc)
See CHANGES for the summary of changes between releases. If the major version of a subsequent release remains unchanged, it is supposed to be backwards compatible in terms of API and behaviour with previous ones with the same major version (meaning that it should be safe to just blindly update in dependent projects and things should not break under normal circumstances).
Provides rpcScope
and listenerEventScope
Guice Scope
s for both client and server apps.
Oversimplifying, in case of streaming inbound (streaming requests to servers and streaming responses to clients), listenerEventScope
spans over the processing of a single message from the stream or over a single call to any registered handler (eg with setOnReadyHandler(...)
, setOnCancelHandler(...)
etc), while rpcScope
spans over a whole given RPC.
Oversimplifying again, in case of unary inbound, these 2 Scope
s have roughly similar span (although if any handlers are registered, they will have a separate listenerEventScope
).
See this DZone article for an extended high-level explanation and the javadocs of ListenerEventContext, ServerRpcContext, ClientRpcContext for technical details.
Contains the above Scope
s and gRPC Interceptor
s that start the above Context
s.
An interface and a decorator for Executor
s that automatically transfer active Context
s when executing tasks.
Binds tasks and callbacks (Runnable
s, Callable
s, Consumer
s etc) to Context
s that were active at the time of a given binding. This can be used to transfer Context
s semi-automatically when switching Thread
s, for example when passing callbacks to async functions.
- Create a
GrpcModule
instance and pass itslistenerEventScope
andrpcScope
to otherModule
s asshortTermScope
andlongTermScope
respectively (see DEVELOPING PORTABLE MODULES). - Other
Module
s may use the passedScope
s in their bindings:bind(MyComponent.class).to(MyComponentImpl.class).in(longTermScope);
- All gRPC
Service
s added toServer
s must be intercepted withGrpcModule.serverInterceptor
similarly to the following:.addService(ServerInterceptors.intercept(myService, grpcModule.serverInterceptor /* more interceptors here... */))
- All client
Channel
s must be intercepted withGrpcModule.clientInterceptor
orGrpcModule.nestingClientInterceptor
similarly to the following:ClientInterceptors.intercept(channel, grpcModule.clientInterceptor)
public class MyServer {
final Server grpcServer;
public MyServer(int port /* more params here... */) throws Exception {
final var grpcModule = new GrpcModule();
final var injector = Guice.createInjector(
grpcModule,
new ThirdPartyModule(grpcModule.listenerEventScope, grpcModule.rpcScope),
(binder) -> {
binder.bind(MyComponent.class)
.to(MyComponentImpl.class)
.in(grpcModule.rpcScope);
// more bindings here
}
/* more modules here... */
);
final var myService = injector.getInstance(MyService.class);
// myService will get Provider<MyComponent> injected
// more services here...
grpcServer = ServerBuilder
.forPort(port)
.addService(ServerInterceptors.intercept(
myService, grpcModule.serverInterceptor /* more interceptors here... */))
// more services here...
.build();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {/* shutdown code here... */}));
grpcServer.start();
}
public static void main(String[] args) throws Exception {
new MyServer(6666 /* more params here... */).grpcServer.awaitTermination();
}
// more code here...
}
public class MyClient {
public static void main(String[] args) throws Exception {
final var managedChannel = ManagedChannelBuilder
.forTarget(args[1])
.usePlaintext()
.build();
EntityManagerFactory factoryToClose = null;
try {
final var entityManagerFactory = createEntityManagerFactory(args[0]);
factoryToClose = entityManagerFactory;
final var grpcModule = new GrpcModule();
final var channel = ClientInterceptors.intercept(
managedChannel, grpcModule.nestingClientInterceptor);
final var stub = MyServiceGrpc.newStub(channel);
final Module myModule = (binder) -> {
binder.bind(EntityManagerFactory.class)
.toInstance(entityManagerFactory);
binder.bind(EntityManager.class)
.toProvider(entityManagerFactory::createEntityManager)
.in(grpcModule.listenerEventScope);
binder.bind(MyDao.class)
.to(MyJpaDao.class)
.in(Scopes.SINGLETON);
// more bindings here
};
// more modules here
final var injector = Guice.createInjector(
grpcModule, myModule /* more modules here... */);
final var myResponseObserver = injector.getInstance(MyResponseObserver.class);
// myResponseObserver will get MyDao injected
stub.myUnaryRequestStreamingResponseProcedure(args[2], myResponseObserver);
myResponseObserver.awaitCompletion(5, MINUTES);
} finally {
managedChannel.shutdown().awaitTermination(5, SECONDS);
if ( !managedChannel.isTerminated()) {
System.err.println("channel has NOT shutdown cleanly");
managedChannel.shutdownNow();
}
if (factoryToClose != null) factoryToClose.close();
}
}
// more code here...
}
class MyComponent {
@Inject ContextBinder ctxBinder;
void methodThatCallsSomeAsyncMethod(/* ... */) {
// other code here...
someAsyncMethod(arg1, /* ... */ argN, ctxBinder.bindToContext((callbackParam) -> {
// callback code here...
}));
}
}
Dependencies of this jar on guice and grpc are declared as optional, so that apps can use any versions of these deps with a compatible API.
See sample app