So I understand the splitting of dependencies that should be propagated (api) from those that shouldn’t (implementation) but the example is extremely non-illustrative of how this could be useful. Namely, if the code referenced by an implementation dependency is accessible via any public code-path, all you’re doing is creating a strong risk of class/method not found issues down the road. Taking the example given, any call to the public API method in which “ExceptionUtils.rethrow(e)” is called potentially exposes the downstream caller to an unexpected runtime exception that using the “java” plugin would have avoided.
How could this work better?
First, it seems to me that the most useful case for this particular mechanism is for “optional” dependencies, where there is a sample implementation or specific integration available alongside a core library that a downstream user may not want to be exposed to. Could the example be updated to something that actually makes sense, such as this?
Second, this only covers 1/2 of the challenge of writing an API – hiding dependencies that shouldn’t be used at all transitively. It does not solve the other 1/2 of the challenge which is preventing dependencies that NEED to be visible transitively from interfering with conflicting downstream dependencies. Could we potentially promote the “shadow” plugin to a first-class citizen by merging it with this plugin? This would make a lot of sense – hiding internal dependencies via namespace mangling (perhaps via a third configuration) would give a cohesive framework for developing APIs whose dependencies don’t interfere with downstream implementations.