-
Notifications
You must be signed in to change notification settings - Fork 99
Description
We sometimes extend our gRPC client functionality to include things like automatically retrying on connection failures. It would be convenient if we could centralize these implementations as an extension method which is callable on any gRPC client function call.
This isn't trivially possible right now because gRPC methods don't all implement a specific trait or type so one can't target them. Part one of this issue would therefore involve prototyping a few approaches and deciding whether its worth it or not.
I'll list a couple of ideas I've had bouncing around here, that doesn't necessarily mean they're good ones.
Standardize the return result
If we standardize gRPC methods to return GrpcResult<T> then we could implement the extension trait on that. At minimum we need the error type to return whether it was a connection issue, or not, so we know when to retry.
This would be similar to tokio's timeout extension method, which wraps the future in a timeout.
trait GrpcMethod
This would be more involved, but would let us standardize and add nicer telemetry to operations. In general, our gRPC methods all broadly follow the process of deserializing request, do some work, serialize the response.
Using a trait isn't required, but it would let us standardize the error types and process. However, there are still unknowns to me, like how would one inject the actual protobuf client, and which method to call on it. This should be possible, but I haven't fully explored this yet.
enum GrpcDecodingError {..}
enum GrpcError {
Decoding(GrpcDecodingError),
...
}
trait GrpcDecode<Raw, Domain> {
fn decode(proto: Raw) -> Result<Self, GrpcDecodingError> {...}
}
trait GrpcEncode<Raw, Domain> {
fn encode(domain: Domain) -> Raw {...}
}
trait GrpcMethod {
// Raw protobuf inputs and outputs
type Request;
type Response;
// Actual domain inputs and outputs.
//
// Must satisfy encoding/decoding from the protobufs.
type Input: GrpcDecoding<Self::Request, Self::Input>;
type Output: GrpcEncoding<Self::Response, Self::Output>;
async fn call(&self, client: T, input: Self::Input) -> Result<Self::Output, GrpcError> {
// We can add a tracing span for each step here, so we know
// how long encoding, work, and decoding take.
let request = input.encode();
let response = client.call(request).await?; // Client bit is unclear.
Self::Output::decode(response)
}
}