More TDD: Facade pattern to streamline Eclipse extension development

In the previous tip that I wrote, I extolled the benefits of rapid test-driven development using the continuous testing feature of Bndtools. This is a short follow-up post with a technique I developed to leverage this while developing an Eclipse plugin.

I preface what follows with the disclaimer: I do not claim to be an Eclipse plugin developer expert. I hope I’m not being too critical or unfair. The following is based on my honest experience with development in the Bndtools core, and in particular with the development of Bndtool’s quick fix processor (for more information on the quick fix processor, please see this informative blog post by @pkriens).

Eclipse plugin development presents some of its own challenges when it comes to doing TDD. In particular, Eclipse has the concept of extension points and the Extension Registry. I’m not sure of the history of the Extension Registry and extension points, but coming to it as a relative outsider who learned “plain” OSGi at the same time, I would have to say that its existence seems rather unfortunate, and I can only assume it’s still around because of history and inertia. I say this because in many ways, its functionality seems to me to duplicate the built-in OSGi service registry, and is therefore an unnecessary additional layer with the associated maintenance that entails.

The main difficulty I found with the extension registry is that it does not seem to be designed to be dynamic. If anyone has done any development on Bndtools, they’ve probably experienced this - if you have test Eclipse instance running from within the host Eclipse (developer) instance, and then make a change to the Bndtools code in the host Eclipse instance, then Bndtools will diligently rebuild all of the relevant bundles and attempt to update these bundles in your running test Eclipse instance. Shortly thereafter, you’ll start seeing “Invalid registry object” errors in your test Eclipse instance that soon make it impossible to do anything:

Invalid registry object

There’s no way (that I know) to fix this - you have to stop the instance and restart it. This makes TDD-style development impractically slow. Furthermore, possibly because the extension registry was not designed to be dynamic, there are parts of the core Eclipse code that statically obtain and cache references to the installed plugins that they are interested in, which means even if you could restart the registered extension, the clients of that extension at best may not notice, and at worst would start to fail at runtime. I faced both of these issues while trying to develop the quick fix processor, and they got so frustrating I tried to find a way around them.

Enter the “facade” pattern (I’m open to better names…). In this pattern:

  • In your core bundle (which cannot be restarted), you only have a lightweight “facade” implementation of your extension point. For the Bndtools quick fix processor, it is here.
    • The facade directly implements the extension point interface that you’re interested in (in this case, IQuickFixProcessor).
    • It uses a ServiceTracker to track implementations of your extension interface.
    • It implements the methods of the extension interface by delegating to the instances being tracked by the ServiceTracker. If there are no instances active, then a sensible “null” default action is taken (in this case, to provide no completion proposals).
  • The core bundle registers the plugin using the facade class as the class type.
  • A secondary bundle (which does not have a plugin.xml) then contains the actual implementation and registers it in the OSGi registry. In the case of the quick fix processor, we do this simply using Declarative Services and adding the @Component annotation to the class definition (see here).
  • Now you can start/stop the secondary bundle without upsetting the extension registry. The fact that JDT statically caches the list of quick fix processors will not be a problem, because it will cache the facade - which in turn hides the dynamism of our real implementation.

With this approach, I was once again able to develop BuildpathQuickFixProcessor using TDD - Bndtools would re-compile it, then re-bundle & re-deploy bndtools.core.services into my running framework. Because all of the extension point implementations are contained in bndtools.core (which doesn’t restart in this workflow), I no longer run into those annoying registry errors that plagued my earlier attempts at doing TDD. The result was to be able to rapidly add many new features to the quick fix processor with the confidence that they were also well tested.

As yet, I have not directly tried this technique with any other extension points. It may not be feasible for extension points that are based on classes that contain state (eg, IncrementalProjectBuilder). But I see no reason it couldn’t be extended to other extension points too, especially the stateless ones.

Happy TDD!

1 Like

Thanks father Krieg, these blogs are extremely helpful and informative.

I’ve been pondering this facade pattern a bit recently, and it occurred to me that there are a few similarities between it and the proxy reference injection that Blueprint uses. The difference is the behaviour when the backing service is not present - in the facade implementation I put in place for the quick fix processor, it would return sensible defaults if the backing service was not present; in Blueprint it will block until the backing service is present (or optionally throw ServiceUnavailableException if a timeout is configured and it expires).

(Disclaimer: I’ve never used Blueprint; the above is based on my reading of the spec, but I’m not an expert by any stretch of the imagination…)