Owen Gage

Rust runtime type selection for dependency injection

I had a surprising amount of trouble recently trying to structure an Axum application to pick an implementation of a type at runtime. This is a quick article on how I fixed that.

Cutting to the chase, this is how it looks:

// Trait we wish to inject at runtime.
#[async_trait]
pub trait Notifier: Send + Sync {
    async fn notify(&self, subject: &str, msg: &str);
}

// Our Axum application state
#[derive(Clone)]
pub struct AppState {
    pub notifier: Arc<dyn Notifier>,
    // whatever else
}

We can create the required Arc<dyn Notifier> with something like this:

pub async fn notifier_from_env() -> Arc<dyn Notifier> {
    let notification_method = env::var("NOTIFICATION_METHOD").unwrap();

    match notification_method.as_ref() {
        "aws" => {
            let creds = get_aws_creds().unwrap()
            Arc::new(AwsSnsNotifer::new(creds).await)
        }
        "log" => Arc::new(LogNotifier),
        _ => panic!("unknown notification method"),
    }
}

We can implement a mock version of this for unit testing using something like this:

pub struct MockNotifier {
    notifications: Mutex<Vec<(String, String)>>,
}

#[async_trait]
impl Notifier for MockNotifier {
    async fn notify(&self, subject: &str, msg: &str) {
        self.notifications
            .lock()
            .unwrap()
            .push((subject.to_string(), msg.to_string()));
    }
}

Our MockNotifier implements Send and Sync because its fields do, so there's no further complexity needed. We could have instead had no mutex on the vector and implemented the trait on Mutex<MockNotifier> instead.

It's also possible to not have the Notifier trait require Send and Sync, and instead add it to the Arc types, such as Arc<dyn Notifier + Send + Sync>. It's up to you if you'd rather this be part of the overall trait.

Originally I was trying to have types such as Arc<Mutex<Box<dyn Notifier>>> in my application state, which was adding extra indirection where I didn't need it, and making unit testing more difficult.