shaku icon indicating copy to clipboard operation
shaku copied to clipboard

Suggestion: multiple Interface trait implementations

Open leonardoarcari opened this issue 4 years ago • 4 comments

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! 💪

leonardoarcari avatar Sep 19 '21 15:09 leonardoarcari

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.

AzureMarker avatar Sep 19 '21 17:09 AzureMarker

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 {}

AzureMarker avatar Sep 19 '21 22:09 AzureMarker

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.

leonardoarcari avatar Sep 27 '21 09:09 leonardoarcari

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.

AzureMarker avatar Sep 28 '21 01:09 AzureMarker