shaku icon indicating copy to clipboard operation
shaku copied to clipboard

Derive a Component implementation that can be injected as Arc<Self>

Open austinjones opened this issue 3 years ago • 1 comments

One of the first things I tried to do with Shaku was to provide an injected Client instance from the mongodb crate.

I wanted my database code to directly access the Client struct. I couldn't use Provider, because the database driver would be reconnected on each request. And I couldn't do it with #[derive(Component)] macro because the derive macro requires a trait interface and injects the value as Arc<dyn Trait>.

I found a solution by implementing a custom Component implementation, which sets the Interface to Self:

use mongodb::Client;

[derive(Debug, Clone)]
pub struct MongoClient(pub Client);

impl MongoClient {
    pub fn new() -> anyhow::Result<Self> {
        let client = Client::with_options(...)?;
        Ok(MongoClient(client))
    }
}

impl<M: Module> Component<M> for MongoClient {
    type Interface = Self;
    type Parameters = ();

    fn build(
        _context: &mut shaku::ModuleBuildContext<M>,
        _params: Self::Parameters,
    ) -> Box<Self::Interface> {
        let runtime: MongoClient = MongoClient::new()
            .map_err(|err| Box::new(err))
            .expect("DB error");

        Box::new(runtime)
    }
}

impl Deref for MongoClient {
    type Target = Client;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

It would be useful if #[derive(Component)] could generate this code. Perhaps like this:

#[derive(Component, Clone, Debug)]
#[shaku(interface = Self)]
pub struct MongoClient(pub Client);

Then this value would be usable in a Component:

#[derive(Component)]
#[shaku(interface = Database)]
pub struct DatabaseImpl {
    #[shaku(inject)]
    mongo: Arc<MongoClient>,
}

austinjones avatar Jan 15 '21 17:01 austinjones

I have been thinking about extending the derive macro to allow non-trait types (see https://github.com/p0lunin/teloc/issues/8), and it should be pretty easy to allow this.

In your example, you need to perform some initialization (opening the DB connection). That doesn't work well with a derive macro, so it still requires a manual implementation.

Further, you probably want to use a database pool. See the architecture used in the provider guide for an example of this. The DB connection is obtained via provider, which obtains the connection from a DB connection pool (singleton).

AzureMarker avatar Jan 15 '21 22:01 AzureMarker