instrumentisto / medea-jason

Mozilla Public License 2.0
2 stars 2 forks source link

Abstract `ControlApi` #55

Closed ilslv closed 2 years ago

ilslv commented 2 years ago

Synopsis

Define ControlApi and CallbackClient traits to be able to provide not only grpc protocol.

Checklist

ilslv commented 2 years ago

@tyranron user-facing API will consist of 3 different traits:

  1. ControlApi
    #[async_trait]
    pub trait ControlApi<OnJoin, OnLeave> {
    //                   ^^^^^^ Could be `GrpcCallbackUrl`, `HttpCallbackUrl` or anything else. 
    async fn create_room(
        &self,
        spec: Room<OnJoin, OnLeave>,
    ) -> Result<Sids, ErrorResponse>;
    // ...
    }

Implementor of this trait will be provided to the user, so they could control the media server.

  1. CallbackClientFactory
pub trait CallbackClientFactory<OnJoin, OnLeave> {
    fn build(
        &self,
        url: Either<OnJoin, OnLeave>,
    ) -> LocalBoxFuture<'static, CallbackResult<Arc<dyn CallbackClient>>>;
}

Implementor of this trait will be provided by user, so media server could communicate back. Also it takes &self as an argument to be able to store additional context. Without this the only way to communicate between media server and consumer of the ControlApi would be OnJoin and OnLeave generics. But current implementation imposes additional bounds like Eq, Hash and Clone, as they are stored in the HashMap, which isn't ideal for the shared context.

  1. CallbackClient
#[async_trait(?Send)]
pub trait CallbackClient {
    async fn send(&self, request: CallbackRequest) -> CallbackResult;
}

This trait is just a result of the CallbackClientFactory::build to share events.


Main idea behind splitting CallbackClientFactory and CallbackClient is that CallbackClientFactory::build is called once per connected user (ignoring the reconnection stuff). So with grpc feature frontend client connects to the ClientApi of the media server, CallbackClientFactory::build creates grpc connection between control and media servers and only then CallbackClient::send is called on that connection for each event of the corresponding user. This is how current implementation works in Medea, but hardcoded with GrpcCallbackFactory and CallbackClientFactory::build doesn't receive &self.


All in all, the final setup for Medea with grpc control server should look something like that:

MedeaBuilder::new()
    .control_with_shutdown(|
        service: impl ControlApi<GrpcCallbackUrl, GrpcCallbackUrl>, 
        graceful_shutdown: impl Future<Ouput = ()>
    | {
        tonic::transport::Server::builder()
            .add_service(ControlApiServer::new(service))
            //           ^^^^^^^^^^^^^^^^ generated by `tonic`
            .serve_with_shutdown(bind_addr, graceful_shutdown.map(drop))
            .await
    })
    .callback_factory(GrpcCallbackFactory)
    .run()
    .await
ilslv commented 2 years ago

Discussed:

  1. Don't generalise over OnJoin and OnLeave, as there is no point in that.
  2. Remove CallbackClientFactory from the public API and leave only CallbackClient. Factory is currently used to emulate connection pool, which should be client side implementation responsibility.
ilslv commented 2 years ago

FCM

Define `ControlApi` and `CallbackApi` traits in `medea-control-api-proto` crate (#55)

- provide common Rust definitions for Control API types