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

How do you depend on another module from another JAR? #193

Closed
jaygazula27 opened this issue Mar 28, 2022 · 8 comments · Fixed by #194
Closed

How do you depend on another module from another JAR? #193

jaygazula27 opened this issue Mar 28, 2022 · 8 comments · Fixed by #194
Assignees
Milestone

Comments

@jaygazula27
Copy link

Let's say I'm depending on another JAR which is also using avaje-inject for dependency injection, is it possible to inject those classes in my module?
According to the "Module dependsOn" section, I can use the dependsOn attribute of the @InjectModule annotation to achieve this (I think). However, it appears this was removed in an earlier commit. How can I go about this now?

If an example is helpful, here's what I'm trying to do: I have a maven project with multiple submodules. There are three submodules - gui, api, and core. The gui and api submodule each have their own main() method and they do not depend on each other, however, both of them ultimately make calls to classes in the core submodule.

Here's the very simplified structure:

.
├── core
│   ├── pom.xml
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   ├── com
│   │   │   │   │   └── example
│   │   │   │   │            |── CoreChild.java
│   │   │   │   │            |── CoreParent.java
│   │   │   │   └── module-info.java
├── gui
│   ├── pom.xml
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   ├── com
│   │   │   │   │   └── example
│   │   │   │   │          ├── Child.java
│   │   │   │   │          ├── Main.java
│   │   │   │   │          ├── Parent.java
│   │   │   │   └── module-info.java
└── pom.xml

In the core submodule, CoreParent injects CoreChild creating a very small dependency graph.
Now in the gui submodule, I would like Parent to inject Child AND CoreParent (from the core submodule), and Main sets everything up via BeanScope.

Based on the docs, it appears I can use the @Factory and @Bean annotations to construct CoreParent within the gui submodule, but I do not wish to initialize it manually.

Is it possible to achieve what I'm trying to do? Hopefully my example is helpful, but let me know if I need to clarify anything. Thank you!

@rbygrave
Copy link
Contributor

I can use the dependsOn attribute of the @InjectModule annotation to achieve this (I think). However, it appears this was removed in an earlier commit. How can I go about this now?

Yes, that is outdated, I need to update those docs. @InjectModule now has provides and requires which take Class<?> rather than the strings that it used before. This was done to allow for compile time checking such that for example, when compiling the gui module that it allows and expects classes defined in provides to be used in injection even though they are not present in the module being compiled.

so including the javadoc we have:

/**
 * Used to explicitly specify if it depends on externally provided beans or provides.
 *
 * <h3>External dependencies</h3>
 * <p>
 * Use {@code requires} to specify dependencies that will be provided externally.
 *
 * <pre>{@code
 *
 *   // tell the annotation processor Pump and Grinder are provided externally
 *   // otherwise it will think we have missing dependencies at compile time
 *
 *   @InjectModule(requires = {Pump.class, Grinder.class})
 *
 * }</pre>
 *
 * <h3>Custom scope depending on another scope</h3>
 * <p>
 * When using custom scopes we can have the case where we desire one scope to depend
 * on another. In this case we put the custom scope annotation in requires.
 * <p>
 * For example lets say we have a custom scope called {@code StoreComponent} and that
 * depends on {@code QueueComponent} custom scope.
 *
 * <pre>{@code
 *
 *   @Scope
 *   @InjectModule(requires = {QueueComponent.class})
 *   public @interface StoreComponent {
 *   }
 *
 *
 * }</pre>
 */
public @interface InjectModule {

  /**
   * Explicitly specify the name of the module.
   */
  String name() default "";

  /**
   * Explicitly define features that are provided by this module and required by other modules.
   * <p>
   * This is used to order wiring across multiple modules. Modules that provide dependencies
   * should be wired before modules that require dependencies.
   */
  Class<?>[] provides() default {};

  /**
   * The dependencies that are provided externally or by other modules and that are required
   * when wiring this module.
   * <p>
   * This effectively tells the annotation processor that these types are expected to be
   * provided and to not treat them as missing dependencies. If we don't do this the annotation
   * processor thinks the dependency is missing and will error the compilation saying there is
   * a missing dependency.
   */
  Class<?>[] requires() default {};

  /**
   * Internal use only - identifies the custom scope annotation associated to this module.
   * <p>
   * When a module is generated for a custom scope this is set to link the module back to the
   * custom scope annotation and support partial compilation.
   */
  String customScopeType() default "";

}

Now in the gui submodule, I would like Parent to inject Child AND CoreParent (from the core submodule), and Main sets everything up via BeanScope.

Yes, so something like:

@InjectModule(name="core", provides=CoreParent.class)

and ...

@InjectModule(name="gui", requires=CoreParent.class)

avaje-inject uses ServiceLoader to find and load all the modules (gui, core and api) .. and orders those modules based on requires and provides. So should wire the core first and gui after.

@rbygrave
Copy link
Contributor

A really really old example is at https://github.com/dinject/examples/tree/master/modular-coffee-app ... and I need to move that and update it. (the library used to be called "dinject")

@jaygazula27
Copy link
Author

@rbygrave Okay, got it. That makes sense.
My only concern is that this can result in a huge list of classes listed for both the provides and requires attribute? Especially since the bulk of my logic will actually be in the core submodule which will then be called by the other submodules. Would it possible (in the future) for just the package or something similar to be listed and still have the compile time check in place?

Either way, appreciate the response. I'll follow what you have said then.

@rbygrave
Copy link
Contributor

this can result in a huge list of classes listed

Yes. That would not be fun.

Would it possible (in the future) for just the package or something similar to be listed and still have the compile time check in place?

Yes, I'm sure we can.

I'll give that a go. What I'm pondering is:

  • Do we ever want both package and specific components in requires/provides ? I don't think do.
  • Should instead it be a boolean flag for package wise vs specific component classes ...
  • Should "package" requires/provides actually be a better default than specific classes (current behavior) ... probably

So yes, I'll have a play around and see how that goes.

@rbygrave rbygrave self-assigned this Apr 3, 2022
rbygrave added a commit that referenced this issue Apr 5, 2022
…requires/provides

This means that we don't need to reference each and every dependency in requires and instead can put one
into requiresPackages such that any dependency in that package or a subpackage of that package is deemed
to be supplied by another module.
@rbygrave
Copy link
Contributor

rbygrave commented Apr 5, 2022

With the referenced PR we have ended up adding a requiresPackages as an alternative to requires.

When a class is put into requiresPackages then any dependency in that package or sub-package is deemed to be supplied by another module (and will not cause a compile time error). We can use this when there are lots of dependencies that go across multiple modules and don't want to list each and every one in the requires/provides.

Note that provides matches back to both requires or requiresPackages.

I'll look to release this as version 8.1.RC1 to confirm this is a good plan in practice, plus look to move the multi-module example (from the old dinject examples place).

@rbygrave
Copy link
Contributor

rbygrave commented Apr 5, 2022

@rbygrave rbygrave added this to the 8.1 milestone Apr 5, 2022
@jaygazula27
Copy link
Author

Awesome, thank you so much for the quick turnaround with this feature, @rbygrave!
I'll definitely try this out when it's available.

@rbygrave
Copy link
Contributor

rbygrave commented Apr 6, 2022

Oh, it is available already as 8.1.RC1. I released it off that branch ... https://search.maven.org/artifact/io.avaje/avaje-inject/8.1.RC1/jar

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