Tracked at MAGNOLIA-6027 - Getting issue details... STATUS
Guice (and Magnolia's current usage of it) doesn't allow multi-binding out of the box. Guice forces us to register every component with a unique key (the
<type> element in our module descriptors), and this key is used to resolve injection. If you register
FooBar with the
Foo key, where
class FooBar implements Foo for example, you can only inject
FooBar by declaring a dependency to the
This is a problem if a component needs to be aware of multiple implementations of a common interface, which is something that's often desirable in a plugin-based environment like Magnolia; there are cases where multiple implementations of an interface should be contributed by additional modules. What we've been doing so far in such cases is either
- ignore pluggability and "hard-code" the list of known implementations
- force configuration of a list of implementations where configuration or order shouldn't matter
- ignore the I in IoC and have each implementation register themselves with an injected "manager"
This means one can't "discover" all instances of a certain type in the container.
Current use-cases and workarounds we've implemented so far
Here are some cases where multi-binding is not what we need and where the current solution actually makes sense. Common reasons are "order matters" (and needs to be controlled/configured) or "further configuration of components is required".
- Filters. Order of filters need to be controlled and predictable. Module dependency order is not enough.
- REST endpoints (this is arguable, because mappings are (or can be) configured via annotations, and further configuration might not be needed or could be delegated to a module's own configuration)
- Traits in Personalization - filters (setting the trait) need configuration and order
- Registries (templates, dialogs, ...) are typically for configured items.
Current use-cases with workarounds
Essentially, this feature is for components which are declared as
<component> in a module descriptor and can be injected.
onfigurationSourceBuilderimplementations should not be limited to those in the
magnolia-configurationmodule. We would like to be able to decouple them and have other modules contribute more or different implementations.
RegistryFacade: the implementations' constructor takes a set of
Registryimplementation as an argument, but the component itself is not instantiated by Guice ! It is instead instantiated "manually" at its usage point (currently the config overview app), by explicitly calling it with the known registries. This means that the current implementation will never know about registries that are not depended upon by the UI (e.g REST endpoints, Traits, ...)
- Note that a) the
Registryinterface is actually new in 5.4, but b) each implementation is still registered with the key they used in prior versions, e.g
TemplateDefinitionRegistry. This is why it's still possible to simply depend on (inject)
- Note that a) the
- Links: there are actually no real work arounds at the moment, but link generators would benefit from this; modules could register their own link generators(e.g content apps could register one that's able to deal with nodes form their own workspace, etc)
This Guice extension enables multibinding: https://github.com/google/guice/wiki/Multibindings. It still requires code to do the binding, as always with Guice, nothing is magic; one has to explicitly bind each implementation to a common key.
The common type, which is neither the key or the implementation class of the component, could be annotated with
@MultiBinding. When registering components implement such an annotated interface, we would signal to Guice to do such a multiple-binding, using the annotated interface as the additional multi-binding key.
Here's an example that avoids terms like "manager" and "registry", since it tends to make the whole topic confusing (because talking about a RegistryOfRegistriesManager can get confusing).
Given the following interface
Say we have module-a declaring this implementation of
and module-b declaring this one:
Both modules declare a
<component> in the module descriptor.
If we wanted to have an
Aquarium interface to feed all the fish known by our system, we'd have a dependency problem when trying to implement it, right ?
Unless AquariumImpl was provided by yet another module, which knows specifically about module-a and module-b... but typically, we'll want the
Aquarium in the same module as our
Fish interface. One (ugly) way around it would be to revert the inversion of control in every
... this means every fish would be calling the aquarium to signal itself. Every actor would be calling their agent, despite IoC trying to enforce the Hollywood principle: "don't call us, we'll call you".
The suggested solution would enable the following:
And an implementation of aquarium, declared as a component in the same module as the
Fish interface, would be enabled like the following, without having to know about which particular implementations exist in the system:
It should be noted that use-cases should not encourage depending on
Set<Fish>, but rather on
Aquarium (or some other abstraction around all the fish we know). There is (probably?) no way to prevent abusing the system other than through documentation and education.
Set<Fish> doesn't provide any order guarantee. I assumed it'd inherently be ordered by module dependency order, but I can't verify that by looking at the implement code of
Keep in mind that components still get registered only with the provided key, and optionally-additionally, with a key of
Set<WhateverWasAnnotatedWith@MultipleBinding>; that means that
- if 2 or more
<component>sare declared with
<type>foo.bar.Fish</type>, the latter still wins. Registration of components is done in module-dependency order, which is how we enable one module to override a component declared by another.
- if all our fish is declared with their own keys (e.g
GoldFish), and another component declares a dependency to a single instance of
Fish, that dependency won't be resolved, since there is no
Fishkey known to Guice.
It is currently unclear what the implications are if some of the components are lazy and some not; presumably the "surrounding" component (aquarium) will get all implementations (and thus "wake up" the lazy ones). We also need to verify what happens with components in different scopes. I would however not advise usage of this feature for anything else that system-wide components for now anyway.