Suggestion: multiple Interface trait implementations
I've been playing with shaku for weeks now and I do think this is a huge enabler for Hexagonal Architecture in Rust.
The only limitation I came across is the inability to declare multiple Interface implementations for a Component struct. While in Rust would be perfectly possibile for struct FooService to implement the two traits Fizz and Buzz, I'm not able to annotate FooService with something like:
#[derive(Component)]
#[shaku(interfaces = [Fizz, Buzz])]
pub struct FooService{}
impl Fizz for FooService {}
impl Buzz for FooService {}
What do you think about it?
Best regards and keep up the good work! 💪
That's an interesting case. I'll think about implementing it. The current architecture doesn't support this because you can only implement Component (effectively) once per type since the interface type is an associated type.
I've done some experimentation and this is feasible, but adds some extra verbosity (have to be more explicit about which interface when talking about a component).
Have you tried making a trait that requires both traits, and making a component out of that? Example:
trait FizzBuzz: Fizz + Buzz {}
impl<T: Fizz + Buzz> FizzBuzz for T {}
#[derive(Component)]
#[shaku(interface = FizzBuzz)]
pub struct FooService;
impl Fizz for FooService {}
impl Buzz for FooService {}
Hey @AzureMarker thanks for your answer!
Your suggestion though, does not allow you to write components that depend only on one of the two interfaces. Something like:
#[derive(Component)]
#[shaku(interface = BarInterface)]
pub struct BarService{
#[shaku(inject)]
fizz_service: Arc<dyn Fizz>,
}
Several architectures like Hexagonal architecture or CQRS rely on declaring different interfaces that segregate the scope of operations. It's perfectly fine to implement them within a single concrete struct but, when using them from other services or REST handlers, you can "pick" just the interface that you need. This way you can reduce the dependecy spectrum.
Moreover, this approach improves the testability of your services/repositories, because you can mock just an interface, with respect to the whole service.
I surveid other dependecy-injection crates and some of them allow for this behavior (teloc or waiter_di), but they don't have the same ergonomy that shaku provides.
I agree that this is useful, just concerned about ergonomics and introducing more complexity. Here's a possible workaround using the current shaku version that does what you want, though it's a little verbose: https://gist.github.com/AzureMarker/e0afe806723498084f2ad9ff7854ff94
It's an interesting pattern though. If the re-implementation of Fizz and Buzz could get removed, then this could potentially be automated behind the macros.