Let's say I have a library, which provides two independent plugin interfaces, 2 implementations per plugin, and one parent POM file.
There are also some abstract tests in the "core", which the plugins have to implement and pass to be considered compliant.
From code perspective, the plugin interfaces don't depend on core at all.
Only core depends on plugins.
My assumptions are:
- the core abstractions go into the "core" artifact and its tests are packaged in a test-jar.
- each implementation of a "plugin" goes into a separate artifact (4 artifacts in this example).
- the parent POM also goes into a separate artifact.
I have considered several options about how to structure the dependencies between each artifact, which can be boiled down to these 2:
Leave it at just 6 artifacts. Every "plugin" depends on "core". Every "plugin" and "core" all depend on parent artifact.
This makes it possible for the library users to only specify 2 artifacts in their pom.xml/build.gradle, because "core" is a transitive dependency.
BUT, given I have some changes to the "core" which cause a version bump, I would have to update every plugin implementaton to bump the dependency version. Also, if users didn't specify the core explicitly, they now depend on outdated core.Extract plugin interfaces into separate artifacts, so that implementations no longer depend on core. Which now creates 8 artifacts.
Now, unlike the previous approach, library users can no longer skip the "core" dependency in their pom.xml/build.gradle - it is now a given that it has to be specified. Which means, they would have to depend on 3 artifacts.
But, overall, any update of the core no longer forces a cascading update of the plugins. The plugin implementations need version bumps only if their respective interface updates.
The downside is probably that I now have 2 more artifacts to maintain.
My questions are:
- Which approach is the more correct one? Does it depend on project size or some other factors?
- Are there other approaches?
- Is it bad that users have to depend on plugins & "core" explicitly, even if plugins transitively bring "core" in the first approach?
- Anything that is intrinsic to the problem and cannot be solved? (like, is it a given that 8 artifacts are to be maintained, with no way to minimize that?)
- Is it correct to provide abstract tests in the "test-jar", if I want to make sure that all plugin implementations comply with the interface contracts? Or do I have to copy-paste the tests in each plugin implementation?
Reply to @vokail
Generally, If you release a new version of the core, you must release a new version of the plugin, right?
Currently the code is structured in such a way, that plugin have no dependencies on the core. With 1st scheme, if core updates, plugins must update. With 2nd scheme - if core updates, plugins don't care.
I think it's possible to have more than two plugins implementations
for plugin developers they need to use only this as dependency directly
True & true
plugin-api need only core-api
Currently, I cannot invert the dependency in such a way. Plugins know nothing about the core, except the plugins API.
As a note, there are 2 plugin APIs. Their code doesn't depend on core and their code doesn't depend on each other.
With 1st scheme, all plugin APIs are inside a single core artifact.
With 2nd scheme all plugin APIs are in separate artifacts (so it's 1 core artifact, and 2 separate API artifacts = 3 artifacts in total).
core-api can be implemented by more than one core-impl ( in the future )
Mhm... Don't see it in the future.
It's better to depend for my plugin implementation from an interface only, not from a core one
To clarify, this is what I meant.
From library user perspective, 1st scheme looks like this:
// Implementaton of "A" api, variant 1
implementation 'library:plugin-a1-impl:1.0.0'
// Implementaton of "B" api, variant 2
implementation 'library:plugin-b2-impl:1.0.0'
// Both plugins transitively bring in "library:core:1.0.0".
// But if for example core:1.1.0 is released, it has to be included explicitly
2nd scheme looks like this:
// Implementaton of "A" api, variant 1
// Transitively brings in "library:plugin-a-api" - a new artifact
implementation 'library:plugin-a1-impl:1.0.0'
// Implementaton of "B" api, variant 2
// Transitively brings in "library:plugin-b-api" - a new artifact
implementation 'library:plugin-b2-impl:1.0.0'
// Core has to be explicitly specified, nobody depends on it, only core depends on plugins
implementation 'library:core:1.0.0'
just do one artifact and let people depend on that only ( as example to minimize that ).
Currently there are separate projects that depend on the library, and they use different plugin implementations. Users pick between different implementation of the same APIs depending on shared dependencies.
For example, there's A, and there are 2 implementations: A-oranges, A-apples. If the project already uses oranges, it imports A-oranges. If it already uses apples, it imports A-apples.
In other words, the plugins are more like adapters between the library and external projects.
Another depiction of the differences between 2 options:
Squares represent ".jar" artifacts. Circles inside a square represent interfaces/classes and their dependencies on each other.
It could be said, that the code is DIP compliant - both core and plugin implementations depend on abstractions.
It's only a question of artifact structuring - is it worth extracting abstractions into separate artifacts as well?