-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gRPC Client / Server Code gen and api #17
Comments
@brandmooffin tagging for visibility. |
I see in your example that for the server implementation you a CompletableDeferred or a ResponseChannel as an argument in the method to override from gRPC. Taking a look at https://github.com/rouzwawi/grpc-kotlin which seems to be a project with similar objectives I think their method signature is easier to understand by people less familiar with gRPC.
I think shifting the response from an argument to the return of the function makes it more understandable. |
@lifk Thanks for the feedback Thats a similar approach to the I was reluctant to go down that route for a couple of reason. Using the functions return as a result inverts gRPCs error propagation semantics. Instead of letting users build their error responses and return them explicitly, that method requires them to "throw" an error response. Couple of problems I see with that.
The current design was built around the goal of accurately mapping gRPC semantics and primitives to their coroutine counter parts. It was intended to lower the complexity while still allowing grpc-java users an easy way to convert existing services. I also wanted to maintain the testability of service methods. Being able to mock the request and response channels allows users to test their business logic exclusively Another small benefit to the current approach is message builders being implicitly available for building response vs having to import them explicitly.
|
I see that you have good points on why to follow that approach and I think that either way it's a big improvement over using the grpc-java implementation directly. One of my reasons to suggest return types is that the compiler is gonna enforce you to have that return |
You bring up a good point. If I understand you correctly, I think you might be covered anyways by the safety net that surrounds service method invocation. I wanted to leave a fallback to clean up resources for cases such as yours.
|
Having a fallback is a really good idea but I didn't mean exactly that, let's consider the following example
In the first implementation I can make the mistake of not handling anything in the catch and the application stays there waiting, in the second one the compiler will enforce a return and that will make me notice that I'm missing something. Maybe if you see too much issues into handling the exceptions maybe you could introduce a Result type to handle the response, something like:
|
Sorry for the delayed reply. I was taking time off for the holiday. I did some further digging and have a couple of things I wanted to bring up. Your question actually helped me find some gaps that needed to be addressed in the service code. While I think you bring up valid argument for wanting compile time checking for service methods, I think a small ext function might provide the functionality your looking for. suspend fun <T> CompletableDeferred<T>.respondWith(block: suspend CoroutineScope.() -> T) {
coroutineScope {
try {
complete(block())
} catch (e: Throwable) {
completeExceptionally(e)
}
}
} Using this method your service method implementation can be checked at compile time. override suspend fun sayHello(request: HelloRequest, completableResponse: CompletableDeferred<HelloReply>) =
completableResponse.respondWith {
if (request.name.matches(validNameRegex)) {
HelloWorldProtoBuilders.HelloReply {
message = "Hello there, ${request.name}!"
}
} else {
throw Status.INVALID_ARGUMENT.asException()
}
} What are your thoughts on this? @lifk On a seperate note, while digging into this I realized that the current service examples have some bugs in regards to structured concurrency. |
Other ConcernsIn order to have proper parallel decomposition (Outlined here) the services need to wrap their rpc implementations in a override suspend fun sayHello(request: HelloRequest, completableResponse: CompletableDeferred<HelloReply>) {
coroutineScope {
if (request.name.matches(validNameRegex)) {
completableResponse
.complete { message = "Hello there, ${request.name}!" }
} else {
completableResponse
.completeExceptionally(Status.INVALID_ARGUMENT.asRuntimeException())
}
}
} This raises the following questions.
It looks like these issues are present in other grpc coroutine implementations as well, my aim is to provided an implementation that balances all the best practices from both technologies. |
I think the extension function can be a good idea to allow optionally for the consumers of the library to call service methods in a more secure way. About your concerns about coroutineScope, I think implementing the scope per service is not useful, as you say the grpc service is gonna have a lifespan as big as the server lifetime so we wouldn't get any benefit from being able to cancel the scope together with all the children coroutines because the only case where the grpc service is stopped is when the server shuts down. I didn't work a lot yet with newer versions of coroutines so I don't have a lot of knowledge on best practices about this problem but my intuition as the consumer of the library would be that the method of the grpc service is wrap in a coroutine scope that represents a single call, that way if the call is cancelled all the children coroutines will die. I think if this behavior is handled by the generated code it should be properly documented to avoid confusion about in which scope is the code being executed. |
So after a bit of feedback from a few other parties I think Im going to move forward with changing the service method signature to have an explicit return type. There were some valid points brought up.
In regards to your comment on implementing Apparently the confusion regarding ambiguous scope resolution in classes implementing coroutine scope has been brought up before. There is currently an issue open for adding a warning to intellij for such cases. KT-27493 It seems like worrying about consumers following best practices for this specific case wont be much of an issue in the near future. Im close to having everything updated for the new signatures. Once Im done benchmarking and testing the manual control flow with the new implementation, Ill update this issue for visibility |
So the latest release https://github.com/marcoferrer/kroto-plus/releases/tag/v0.2.2-RC1 |
Just published the latest snapshot with the near final version of the proposed APIs. One key change was that services no longer implement Release: v0.2.2-RC2
|
Im going to close out this design issue now that coroutine support has been fully release. Any new suggestions or api changes can take place in their own issue |
This issue is for tracking any discussions related to #16 PR.
The PR introduces new apis with subtle differences to those introduced in the stub ext generator.
Please reference the client / server example included. It contains a sample of the expected output for discussion.
Here are some key points and examples from the PR
CoroutineScope
interfacegrpc-java
patterns, while still fully embracing coroutine idioms and features.SendChannel.send { }
andCompletableDeferred.complete { }
RpcMethod
annotation fromgrpc-java
. RpcMethod.javaThe text was updated successfully, but these errors were encountered: