If I have to pick on idea that worked best over the past years then it is probably the API project. Basically since the original OSGi enRoute, I make sure all my bnd work spaces have one (1!) project that contains the API packages for that workspace. No other project is allowed to export any of their local packages except in very special circumstances. The API project should try to not have any dependencies on other projects or external libraries. The other projects in the workspace can only talk to each other using API from the API project.
This model has a lot of advantages. It makes the build a lot faster because there are no project-project dependencies. It also prevents the big ball of mud dependency hell where everything ends up depending on everything else and then some.
Originally I recommended to export the API packages by the bundles that provided the API. After all, a provider is tightly coupled to the API version. However, a few years ago a customer finally convinced me that this was not a good idea because it made testing harder. Setting up tests was a lot easier if the API of the untested parts was separate from the provider. My objection was that this required more metadata to make the resolver work seamlessly. However, it turned out that the resolver for the runtime assembly actually worked very well using the service dependencies and not package dependencies.
I am now convinced that having a separate bundle for the API in a workspace works better than exporting the packages if the bundles are only used in the same organization. However, I still tend to export the API packages for bundles that venture out in the wild. That said, since the resolver is working so well my original objection to API only bundles is disappearing. So nowadays I tend to provide the API separate as well. Who knows, maybe one day I will no longer export the API packages from the providers …
While I can see your position @Peter Kriens, I have to respectfully disagree and here’s why. I feel the model you’re proposing only really works well in “application development” scenarios. It breaks rather quickly when writing libraries and/or frameworks, or even extensible or modular applications. I think it’s important to bear that in mind.
The model you propose means your entire application project is definable as an agglomeration of specific api package versions. If that’s not your intent then you may feel constrained with this approach.
For example, you may deliberately wish to limit a given implementation to seeing a very specific subset of package imports as a means to limit coupling. With a single monolithic API you risk accidentally increasing the coupling of an implementation. I’ve personal, long term experience with this issue; like where none of the 1000 8k jars in your project can be satisfied by anything less than 80% of the entire project.
Remember, the api you can see from a given compiler context is not merely an impediment to developer productivity, it’s a useful control structure.
This is true. However, 99% of the developers make applications, not frameworks or libraries that are used by others. You are the exception, not the rule. The rules for sharing in application development environments are quite different as I mentioned. Having an API project does not have any of the disadvantages you mentioned since it is never shared outside their environment. It still has all the advantages of modularity and faster builds.
And for a well working public framework it is imho crucial to have a foundational API that evolves very carefully in a bacward compatible way and is absolutely minimal. I’ve become convinced that plugins should bring their own implementation dependencies. I think we all have too many painful cases to show for to see that what was called DLL hell is basically an unsolvable problem. Packing your dependencies in your bundle is maybe not efficient but it solves a shitload of problems for, nowadays, a pretty low price.
You can see this strategy in almost any public platform nowadays. A simple phone app is 40Mb because it won’t share dependencies. Yeah, my bundles got fatter but at least they do not cause the many headaches I see in the enterprise world where sharing transitive implementation dependencies for even the most trivial of functions is the norm, and most often without any metadata to speak off. I still miss JPM4J where I could immediately reject a potential library with one glance at the humongous transitive dependency graph.
For me, it all starts by only sharing service APIs and stateless libraries with a proper abstract API. But I know the hard way that companies rarely are willing to take the upfront cost to get there, although it is more than worth it.
I think we’re mostly in agreement, or at least strive for the same goals. The main difference is our perspective. I’ve been very fortunate to have customers in the embedded world where the perspective is very different from the imho very messy enterprise world.