Module shaku::guide::submodules[][src]

Getting started with submodules

This guide assumes you have already read the general getting started guide.

Modules can contain other modules, called submodules. For example, a top-level RootModule may use an AuthModule to provide authentication services. The submodule's implementation may be known, or the submodule may be hidden behind a trait (aka module interface).

Why use submodules?

Unlike components or providers, the implementation of a submodule does not need to be known to the module. You can easily swap out, say, an OAuth authenticaion implementation for a fake during development. This is more powerful than overriding components/providers because with overriding you cannot remove services from the dependency graph. For example, the OAuth AuthManager implementation may use an OAuthKeyStore internally. When you swap the module out for a fake, the OAuthKeyStore is no longer part of the dependency graph. If the AuthManager was instead overridden, the keystore would still be created or would need to be overridden as well, despite being internal or private to the OAuth AuthManager.

The example

use shaku::{module, Component, HasComponent, Interface};
use std::sync::Arc;

trait MyComponent: Interface {}
trait AuthManager: Interface {}
trait AuthModule: HasComponent<dyn AuthManager> {}

#[derive(Component)]
#[shaku(interface = MyComponent)]
struct MyComponentImpl {
    #[shaku(inject)]
    auth_manager: Arc<dyn AuthManager>
}
impl MyComponent for MyComponentImpl {}

module! {
    RootModule {
        components = [MyComponentImpl],
        providers = [],

        use AuthModule {
            components = [AuthManager],
            providers = []
        }
    }
}

In this example, RootModule knows the implementation of its MyComponent component, but it does not know the implementation of AuthManager or the AuthModule that it gets it from. The AuthModule implementation is passed in when RootModule is built.

Providing submodule implementations

To build a module, you need to give it a reference to each submodule implementation. The module macro will generate a builder function which takes in the submodules and outputs a ModuleBuilder

#[derive(Component)]
#[shaku(interface = AuthManager)]
struct AuthManagerImpl;
impl AuthManager for AuthManagerImpl {}

module! {
    AuthModuleImpl: AuthModule {
        components = [AuthManagerImpl],
        providers = []
    }
}

let auth_module = Arc::new(AuthModuleImpl::builder().build());
let root_module = RootModule::builder(auth_module).build();

let my_component: &dyn MyComponent = root_module.resolve_ref();

AuthModuleImpl has no submodules, thus its builder function has no arguments. RootModule has one submodule, thus its builder function takes in an Arc<dyn AuthModule>.

Note: AuthModuleImpl uses a feature of the module macro to automatically implement AuthModule. It does this by adding : AuthModule after the name of the module. This is shorthand for the statement impl AuthModule for AuthModuleImpl {}.