Service Injection
External services are regularly required in any useful application. For our bank account example we might have services that check if an ATM has the cash to dispense the desired amount or verify that a check number is valid.
#![allow(unused)] fn main() { #[async_trait] pub trait BankAccountServices: Sync + Send { async fn atm_withdrawal(&self, atm_id: &str, amount: f64) -> Result<(), AtmError>; async fn validate_check(&self, account: &str, check: &str) -> Result<(), CheckingError>; } pub struct AtmError; pub struct CheckingError; }
To provide access to these services within the aggregate they should be injected as part of the command, this can be done using a wrapper around the command that includes needed services. For our bank account example:
#![allow(unused)] fn main() { // formerly this was BankAccountCommand pub enum BankAccountCommandPayload { OpenAccount { account_id: String }, DepositMoney { amount: f64 }, WithdrawMoney { amount: f64, atm_id: String }, WriteCheck { check_number: String, amount: f64 }, } pub struct BankAccountCommand { pub payload: BankAccountCommandPayload, pub services: Box<dyn BankAccountServices>, } pub trait BankAccountServices {} }
Using this pattern the only other change needed is within the handle
method of an aggregate where we should now match
on the payload of the command rather than the command itself
async fn handle(
&self,
command: Self::Command
) -> Result<Vec<Self::Event>, AggregateError<Self::Error>> {
// match command {
match command.payload {
...
}
Of course all of your tests will now break, for all of these the command payload must be wrapped along with a mock implementation of your services. See the cqrs-demo repository for an example if you are unfamiliar with this pattern in Rust.