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

Support plugin provided guice module #4133

Merged
merged 5 commits into from
Oct 3, 2024
Merged

Support plugin provided guice module #4133

merged 5 commits into from
Oct 3, 2024

Conversation

aromaa
Copy link
Member

@aromaa aromaa commented Sep 27, 2024

This allows plugins to provide a Guice module to expose services to their dependencies and dependents which are injected to their plugin instance.

Example 1, plugin A can provide implementation for a service which plugin B can consume.

@Plugin("plugina")
public final class PluginA {

    //One can also use `PrivateModule` to configure internal services
    //and choose which ones are exposed to the public.
    public static final class Module extends AbstractModule {

        @Override
        protected void configure() {
            //Bind to your own implementation classes.
            this.bind(PluginAService.ExampleA.class).to(ServiceImpl.class);

            //Binding to the plugin instance is also legal, it points to the same instance.
            this.bind(PluginAService.ExampleB.class).to(PluginA.class);
        }
    }
}

@Plugin("pluginb")
public final class PluginB {
    //Dependencies: PluginA { LoadOrder.AFTER }

    @Inject
    public PluginB(final PluginA pluginA, final PluginAService.ExampleA exampleA,
        final PluginAService.ExampleB.class exampleB) {
        //PluginA instance can be directly accessed.
        //The service from plugin A is injected.
        //pluginA == exampleB: true
    }

    public static final class Module extends AbstractModule {

        @Override
        protected void configure() {
            Multibinder.newSetBinder(this.binder(), PluginBService.class);
        }
    }
}

Example 2, plugin C can discovered services provided by other plugins.

@Plugin("pluginc")
public final class PluginC {

    @Inject
    public PluginC(final Set<Service> services) {
        //services has PluginD
    }

    public static final class Module extends AbstractModule {

        @Override
        protected void configure() {
            //Bind empty binder in case there are no other plugins.
            Multibinder.newSetBinder(this.binder(), Service.class);
        }
    }
}

@Plugin("plugind")
public final class PluginD {
    //Dependencies: PluginC { LoadOrder.BEFORE }

    public static final class Module extends AbstractModule {

        @Override
        protected void configure() {
            Multibinder.newSetBinder(this.binder(), Service.class).addBinding().to(PluginD.class);
        }
    }
}

@gabizou
Copy link
Member

gabizou commented Sep 28, 2024

Is there a guarantee about ordering of event listeners provided by a plugin instance like the description? Or is the guarantee that you can inject plugin instances only without effects to listener ordering?

@aromaa
Copy link
Member Author

aromaa commented Sep 28, 2024

The requirement is that if you wish to inject dependencies to your plugin then you have to declare them as LoadOrder.AFTER.

We could relax it but it requires changing the actual load algorithm, which can be independent from the ordering of ConstructPluginEvent. Changing the load algorithm is also observable side-effect. We are now actually initializing plugin A first even after plugin B declared it as LoadOrder.BEFORE. Even if we call ConstructPluginEvent for plugin B first, the constructor of plugin A could have caused observable side-effects. I would call this myself as undefined behavior but its something to take in to the consideration if we want to change it.

@aromaa
Copy link
Member Author

aromaa commented Oct 2, 2024

I have extended the support for indirect dependencies, where plugin C declares itself as having dependency on plugin B but as LoadOrder.BEFORE. This allows for "service discoverability" where the plugin B can get list of all services by its dependents.

For example the following works:

@Plugin("pluginb")
public final class PluginB {

    @Inject
    public PluginB(final Set<Service> service) {
        //services has PluginC
    }

    public static final class Module extends AbstractModule {

        @Override
        protected void configure() {
            Multibinder.newSetBinder(this.binder(), Service.class);
        }
    }
}

@Plugin("pluginc")
public final class PluginC {

    public static final class Module extends AbstractModule {

        @Override
        protected void configure() {
            Multibinder.newSetBinder(this.binder(), Service.class).addBinding().to(PluginC.class);
        }
    }
}

@aromaa aromaa merged commit 3ef5fbe into api-12 Oct 3, 2024
10 checks passed
@aromaa aromaa deleted the api12/guice-module branch October 3, 2024 19:21
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

Successfully merging this pull request may close these issues.

2 participants