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

Multibinding support? #515

Open
ursusursus opened this issue Aug 8, 2022 · 6 comments
Open

Multibinding support? #515

ursusursus opened this issue Aug 8, 2022 · 6 comments

Comments

@ursusursus
Copy link

ursusursus commented Aug 8, 2022

Coming from android / Dagger side of the world, the thing I'm missing to facilitate a highly modular codebase is multibinding.
Essential, what it is, is that the implementation is registered as its protocol type - the usual stuff.

However, I'm then able to be pull out (inject) all these protocol-implementing instances as a set or array, i.e. Set<SomePlugin>

This allows for a plugin architecture which is a necessity in a multi-app modular shared codebase

@ursusursus
Copy link
Author

ursusursus commented Aug 8, 2022

The manual workaround is to have

protocol SomePluginsProvider {
   func get() -> Set<SomePlugin>
}

and then

struct App1SomePluginsProvider : SomePluginsProvider {
    func get() -> Set<SomePlugin> {
      [Feature1Plugin(),  Feature2Plugin() ]
   }
}

struct App2SomePluginsProvider : SomePluginsProvider {
    func get() -> Set<SomePlugin> {
      [Feature1Plugin(),  Feature2Plugin(), Feature3Plugin() ]
   }
}

but as you see, this kind of defeats the modularity of Assemblies, as me as a Feature1 developer need to plug the instance at N places where N is the number of apps in the project; which is doable but annoying and should be a DI library feature

@welshm
Copy link
Contributor

welshm commented Sep 13, 2022

What would you want to do with this Set<SomePlugin> ? Just trying to envision how it can be done

@ursusursus
Copy link
Author

It's mostly used for dependency inversion when modularizing, and you wish for Assembly to be packaged with the module.

For example

// Analytics module
class Analytics {
   private let trackers: [AnalyticsTracker]
   init(trackers: [AnalyticsTracker] {
      self.trackers = trackers
   }
}

class AnalyticsAssembly: Assembly {
   func assemble(container: Container) {
      container.register(Analytics.self) { r in Analytics(r.resolveSet(AnalyticsTracker.self) }
   }
}
// Firebase tracker module depends on Analytics
class AnalyticsAssembly: Assembly {
   func assemble(container: Container) {
      container.registerIntoSet(AnalyticsTracker.self) {  _ FirebaseAnalyticsTracker() }
   }
}
// Dynatrace tracker module depends on Analytics
class AnalyticsAssembly: Assembly {
   func assemble(container: Container) {
      container.registerIntoSet(AnalyticsTracker.self) {  _ DynatraceAnalyticsTracker() }
   }
}

The whole point of the inversion is to enable codebases with multiple apps, where those apps vary in features. Say App A will only include "dynatrace tracker", and App B will include "dynatrace tracker" and "firebase tracker".

So, from this you can see that I cannot just have constructor with all the dependencies listed as one normally would

class Analytics {
      init(firebaseTracker, dynatraceTracker)
}

Therefore it needs to be turned into an array and pushed to DI assemblies. However this means I need to have AppAAnalyticsAssembly and AppBAnalyticsAssembly, which is redundant and not really nice; hence the multibinding feature of Dagger

@bradfol
Copy link
Contributor

bradfol commented Nov 23, 2022

Was also looking into how this feature might exist in Swinject. Agreed that a container.registerIntoSet() method seems like the right way to go about it.

@bradfol
Copy link
Contributor

bradfol commented Feb 6, 2023

So I recently learned that this functionality is possible with existing Swinject by using a custom Behavior on the container. This is actually one of the examples used in the initial Behaviors proposal (it is called "Instance Aggregation"). There is also another example of a very similar approach "SwinjectResolveManyBehaviour".

@ursusursus
Copy link
Author

@bradfol what did you end up with please?

I'm not really keen on the SwinjectResolveManyBehaviour solution, as it the array is not registered as a dependency, you need to explicitly pull it via the pluginsResolver .resolveMany which I'm not even sure how that would work, unless you make the reference public?

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

3 participants