remoc icon indicating copy to clipboard operation
remoc copied to clipboard

Built-in servers and clients for common transports

Open baptiste0928 opened this issue 2 years ago • 5 comments

Hello! While using Remoc in my application, I realized that some connection logic was duplicated:

  • I need to implement some basic connection logic each time I create a server/client type. This includes for example graceful shutdown or automatic reconnection.
  • I use Remoc to communicate between multiple services, where each service handles a single task. Therefore, all my Remoc servers send a channel or a rtc client as soon as the connection is created. Other channels may be created later, but the base channel is only used to send a more appropriate type.

I think these use cases are quite common, so I propose to include types to make their implementation easier:

  • Allow to initialize a connection with other types than base channel, for example directly with an RPC server/client or a broadcast channel. Maybe create Handler traits with methods that will be called directly after a connection is established.
  • Implement generic Client and Server types that are configured with a specific transport and handler, and manage the connection. This includes graceful shutdown and automatic reconnection, for example.
  • Abstract transports with a Transport trait, that could be used in Connect or directly with a Server/Client. Implementation for common transports such as TCP may be provided.

Built-in handlers for common usages such as RPC or broadcasting stream may be provided.

I think adding features like this would make remoc much easier to use by hiding some things that may seem complex to beginners, without sacrificing the flexibility of the library, since all these features would be optional, and could even be used partially. Remoc is a project that can be useful to many people and fulfills many use cases. Making it easier to get started with would in my opinion make it much more used and would make it a serious alternative to tarpc or tonic.

baptiste0928 avatar Jan 22 '22 19:01 baptiste0928

Hi.

I am happy to hear that you are using Remoc in your application and thanks for providing feedback!

... I think these use cases are quite common, so I propose to include types to make their implementation easier:

  • Allow to initialize a connection with other types than base channel, for example directly with an RPC server/client or a broadcast channel. Maybe create Handler traits with methods that will be called directly after a connection is established.

We could implement this by sending or receiving a value over the base channel directly after the connection is established. However, this would require the addition of methods such as io_send, framed_send and io_recv, framed_recv (and possibly more variations) to the Connect struct. I am not sure if this will make the API easier to grasp than a call to Connect::io followed by tx.send on the server side and rx.recv on the client side. Probably the best solution would be to create an utility trait ConnectUtilExt that provides such convenience methods.

  • Implement generic Client and Server types that are configured with a specific transport and handler, and manage the connection. This includes graceful shutdown and automatic reconnection, for example.

This would be doable for a transport provided by Tokio (i.e. TCP), but what about other transports such as TLS or WebSockets? We would have to add crate dependencies to libraries providing these transports, creating a lot of dependency and crate version problems.

Automatic re-connection would assume that the server sends a RTC server over the base channel straight after a client connects. But what about more complex scenarios? For example, the server could expect the client to authenticate over the base channel (by sending a Hello(username, password) message) before it sends the RTC server.

  • Abstract transports with a Transport trait, that could be used in Connect or directly with a Server/Client. Implementation for common transports such as TCP may be provided.

See above.

Built-in handlers for common usages such as RPC or broadcasting stream may be provided.

Here the question pops up: what is common usage? For some people it will be an RTC client/server, for others a broadcast channel and yet for others a watch channel. Catering for all these use cases would require a large amount of handlers, thereby cluttering the API.

I think adding features like this would make remoc much easier to use by hiding some things that may seem complex to beginners, without sacrificing the flexibility of the library, since all these features would be optional, and could even be used partially. Remoc is a project that can be useful to many people and fulfills many use cases. Making it easier to get started with would in my opinion make it much more used and would make it a serious alternative to tarpc or tonic.

I agree that connection setup is an area where Remoc could benefit from some utility functions for common tasks. However I am not sure if these utility functions should be put into Remoc itself or if we should start a separate remoc-util crate that can have more external dependencies and be more opinionated about such things.

What do you think about such a solution?

surban avatar Jan 24 '22 11:01 surban

I agree that connection setup is an area where Remoc could benefit from some utility functions for common tasks. However I am not sure if these utility functions should be put into Remoc itself or if we should start a separate remoc-util crate that can have more external dependencies and be more opinionated about such things.

I think a separate crate is good, as long it is clearly mentioned in the main crate.

Here the question pops up: what is common usage? For some people it will be an RTC client/server, for others a broadcast channel and yet for others a watch channel. Catering for all these use cases would require a large amount of handlers, thereby cluttering the API.

It's obviously not possible to cover all the use cases, but generic and modular utilities could be provided, with complete implementations for rather common use cases. I don't know how it could be done with remoc, but tower is a great example of reusable components for building networking services (I don't think that tower could directly be used with remoc as it expect a request-response model - maybe for rpc).

Like what tower does, I think the implementation should focus in a first time on providing a good design of traits to define reusable components, then implement some of the most common ones (like automatic re-connection or sending a value directly after the connection is established).

baptiste0928 avatar Jan 25 '22 15:01 baptiste0928

  • Allow to initialize a connection with other types than base channel, for example directly with an RPC server/client or a broadcast channel. Maybe create Handler traits with methods that will be called directly after a connection is established.

I have implemented a connection extension trait that provides this functionality and created an RTC example that uses it. Please have a look at it and tell me what you think about it. In my opinion this is generic enough to be included in Remoc itself.

With respect to the other points you mentioned, would you be willing to provide some code that makes your use case easier? For now in a separate crate. Then we could start discussing what is best kept in utility crates and what can be moved to the main library.

surban avatar Jan 26 '22 14:01 surban

That looks good! I'll see what I can do on my side, I'll keep you informed :+1:

baptiste0928 avatar Jan 26 '22 16:01 baptiste0928

Another idea would be to plug into the cargo generate ecosystem and provide some templates for use with Remoc. If you already have some code maybe you could extract it and create a template from it.

surban avatar Jan 26 '22 16:01 surban