scalapb / zio-grpc

ScalaPB meets ZIO: write purely functional gRPC services and clients using ZIO
Apache License 2.0
261 stars 82 forks source link

Manual channel handling in client #111

Open alexdupre opened 4 years ago

alexdupre commented 4 years ago

Scenario:

I'm quite new to zio and zio-grpc, so I might have missed some obvious things, but as far as I've seen the current implementation is not able to share a channel between two service clients, and doesn't seem to have a friendly way to keep a channel open for more than a use block but not for the entire app. The result is that the overhead in establishing multiple TLS connections (while one persistent connection per server would be enough) becomes evident. With the plain java API and scalapb wrapper this is possible, because the channel can be created/shutdown independently from the client. My current workaround is to use the scalapb async wrapper with IO.fromFuture to have the best from both worlds.

Is my analysis correct? Is this something your are going to improve or should I stick with my workaround?

thesamet commented 4 years ago

Hi @alexdupre , if you want a single channel/client to live for the entire app, you can:

  1. [Recommended: create the client at the top level as a layer using the live method and use the layer in the app as needed.
  2. create the client at the top level using the generated Client.managed, and have your entire app live inside the client's _.use() method.

Having the channel created and shutdown independently from the client can violate resource safety since the client can be called after the channel is shutdown.

You are correct that currently there's no way for two clients to share a single channel, but we can probably change that by introducing a function of the form: ZManagedChannel => Managed[Throwable, (ClientA, ClientB)] - but I'm not sure if it's needed given that each client can have their own dedicated channels if they are singletons.

Let me know if getting the clients created at the top level helps, or I must have misunderstood what you're trying to do.

alexdupre commented 4 years ago

As far as I've understood the use of the layer with live (and similarly your second alternative with managed) is ok if you need to connect to a single server, ie. there is a static 1:1 service/server relationship. In that case each service would have its own channel, but the same channels are used in the entire application.

In my case I have a dynamic set of servers (all implementing the same set of services) and I need to connect to a different subset of them from time to time, so I need a dynamic set of clients. The sub-optimal things I'm seeing are two: 1) the channel cannot be shared between services, so each service establishes a new TLS connection...the introduction of the function you proposed may help 2) even if the server has long idle timeout, the connection is dropped by the client at the end of the _.use() method, so each use block establishes a new connection...and I cannot create the client at the top application level because there is a set of servers that continuously changes at runtime.

What I'm probably looking for at a higher abstraction level is a sort of channel manager that all service clients can use.

thesamet commented 4 years ago

A single channel can represent a set of underlying TLS connections that change over time, see Java docs. You can implement your own Channel interface that switches between servers as you need and shares the underlying TCP connection.

In other words, a single Channel and a single Client created as a top level layer can still end up talking to any number of servers which you can freely control.

alexdupre commented 4 years ago

A single channel can be used to communicate with multiple servers in a load-balancing or fail-over scenario, it doesn't seem the right way to communicate with different servers when you need to target a specific one (ie. when each server is a separate entity with different state).