|[p2-dev] p2, dependency injection exploration|
I have been doing an initial exploration on how to restructure p2 to take advantage of dependency injection. I'm solliciting advice, feedback, help on the overall approach.
The goals behind this work are:
- bring more modularity to p2 (allow replaceability of some pieces)
- support for multiple p2 instances in one process (PDE use cases)
- remove examplary setup
Non goals at this point:
- pick a DI mechanism
- change the code structure
In order to understand the shape of p2 in a DI world and the potential catches behind such an approach, I have created a new project  that contains mockups of some of the main p2 components where their dependencies are injected.
I have not gone through all the p2 bundles but focused on the Engine and layers below as well as the repositories. I focused on those because they seemed to be representative enough of the kind of patterns throughout our code. During this exploration did not try to reorganize the structure of the code or rearrange the dependencies but instead stayed as close as possible to what we have today. Maybe such deeper changes are in the end needed to be more DI friendly, but I basically looked for the references to ServiceHelper or BundleContext.getService and turned that into an injected dependency for the component. Note that I currently pass in all the dependencies in the constructor but this is not a statement of the choice of DI style.
>From my current analysis we seem to need two kinds of injection: structural and runtime. The structural injection takes care of the objects that form the immutable part of the system and are known at the time we are setting up the system. One occurence is for example the Location needed to create the SimpleProfileRegistry.
The runtime injection is needed to handle the creation of objects when the system is running (understand here, when the p2 engine is running or when the director is running) and whose setup is dependent on what is happening. For example the Collect phase needs to take a repository manager, or the CheckTrustPhase needs to take some instances of security services. Also in our case it seems that most of the objects falling in this last category result (or could result) from instantiation through the extension registry.
Now onto the code . There are currently two interesting starting points: the Location of the profile registry, and the id of the default profile. The best places to start looking at the code are probably the Engine and SimpleProfileRegistry classes. For cases of runtime dependencies, RepositoryManager could give you some hint but I have not tried to mimic the code that goes and create instances of the repository factories.
After this experiment, I feel pretty good about the advantage about such a reorg and the potential flexibility this could bring to extenders especially when we are starting to think about having an official p2 API.
That said there are still quite a few grey areas which I will enumerate below:
- What is the entity that is putting things together? What do people consume?
In today's p2, the examplarysetup bundle aggregates everything together to instantiate a running p2. The main problem is that it registers services on behalf of the other bundles and acquire some others. In a DI style world, a similar aggragator role will still be needed to create a default running p2. In guice this seems to be called a module, in DS a Component, etc. and these aggregation is what users of the code will be depending on, therefore tying p2 to a DI mechanism. Aren't these aggregations somehow going against what we are trying to achieve in terms of modularity? Do we have to define aggregations for each p2 component that I have identified so far?
Yet another problem is how do people consume p2? Currently they acquire the unique service being registered for the profile registry or the repository manager.
- How are the objects that are needed for the creation of executable extensions found?
For example, the engine phases reach out to wide variety of service. For example the CheckTrustPhase uses services from the security bundles. In a world of DI, who would be responsible for passing those in? The same goes for the Collect phase which needs to be given a set of repositories?
- Is there any recommended pattern to deal with EventBus and listeners.
In the current p2 code, a unique provisioning bus is created and bundles like the GC acquire the service and attach a listener. This is fairly straightforward and allows for anyone interested in listening to just come at any time.
However in a system where there can be multiple engines running at the same time, what happens:
1) do I have one bus for my VM?
In this case, the initial pattern of grabbing THE bus and listening still works, though the events being broadcasted need to be more specific as some listeners would probably get things they don't care about.
2) do I have one bus for each engine?
In this case, how are the listeners registered? How would a bundle interested in listening to a particular Engine know which bus to listen on? How could it come after the fact?
 org.eclipse.equinox.p2.di.experiment in the incubator (CVS path: org.eclipse.equinox/incubator/p2/bundles/org.eclipse.equinox.p2.di.experiment) or see also the attached psf file(See attached file: diProject.psf)
Description: Binary data