Skip to content

Generated client structs should implement a Client trait to allow abstraction #671

@GregOwen

Description

@GregOwen

Feature Request

Crates

tonic-build, possibly tonic

Motivation

I have a service that runs as a cluster of nodes and exposes 2 APIs: an external API that clients use to talk to the cluster and an internal API that the cluster nodes use to talk to each other. In order to keep the internal API separate from the external, I've made each one a separate gRPC service. Using Tonic, this gives me an ExternalClient type and an InternalClient type.

I'd like to be able to write helper code that's generic over the two different flavors of client. The motivating example here is to allow nodes (and external clients) to keep a lazily-initiated cache of connections to all the nodes in the cluster. The external clients will keep a map of ExternalClients and the cluster nodes will keep a map of InternalClients. The cache needs to be able to create new clients, so it needs to be able to call the connect method.

Right now, it looks like the generated code initializes each flavor of client as a separate struct and defines connect on the struct:

pub mod internal_client {
    #![allow(unused_variables, dead_code, missing_docs)]
    use tonic::codegen::*;
    pub struct InternalClient<T> {
        inner: tonic::client::Grpc<T>,
    }
    impl InternalClient<tonic::transport::Channel> {
        /// Attempt to create a new client by connecting to a given endpoint.
        pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
        where
            D: std::convert::TryInto<tonic::transport::Endpoint>,
            D::Error: Into<StdError>,
        {
            let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
            Ok(Self::new(conn))
        }
    }
    impl<T> InternalClient<T>
...

I can get the behavior I want by writing my own CanConnect trait and implementing it manually for InternalClient and ExternalClient, then making my cache take a T: CanConnect type arg, but it would be nice to have a generic Client trait implemented automatically by Tonic (that way it can also include any other methods that end up being common among all clients).

Proposal

  1. Add a new public Client trait (possibly as part of tonic::transport::channel?) that defines a connect method (and any other methods that are common across all derived gRPC clients)
  2. During codegen, generate an implementation of this trait for each derived gRPC client struct (this could either replace the impl FooClient<tonic::transport::Channel> block or delegate to it)

Alternatives

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions