Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: multiple Interface trait implementations #29

Open
leonardoarcari opened this issue Sep 19, 2021 · 4 comments
Open

Suggestion: multiple Interface trait implementations #29

leonardoarcari opened this issue Sep 19, 2021 · 4 comments

Comments

@leonardoarcari
Copy link

leonardoarcari commented Sep 19, 2021

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

@AzureMarker
Copy link
Owner

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
Copy link
Owner

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

@leonardoarcari
Copy link
Author

leonardoarcari commented Sep 27, 2021

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.

@AzureMarker
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants