Reuse and external contracts. After a weeklong refactoring of my AOP framework I've had to think about an important issue with reuse: external contracts. I'm sure there is a better word for it, but here's what I mean by it. When reusing a component, or a class, what is important is not only that it has the methods that one needs, but that the component itself has access to the services that *it* needs. For example a mailing component might need access to a mail service and an LDAP directory, which together form the external contract of the mailing component. So, if I want to be able to reuse the mailing component I need to have these other two, and also have a way to "plug them in" so that the mailing component can use it.
When adding support for this kind of plugability to my own framework I looked at EJB to see how it was solved there. The EJB approach to this problem is the comp:java/env JNDI namespace, where one can bind services into through the XML descriptor. This "works", but there are (at least) two major issues with it:
The result of this complexity is that one isn't very keen on using it as a way to declaratively assign services to be used by each component. Instead one's code access the services it needs directly, which leads to higher coupling between the component and the runtime environment.
- The JNDI API is overkill for this purpose, and is more than a little cumbersome. If you want to use it "properly" there's a whole bunch of exceptions that you need to be able to catch in client code. One more or less have to make some kind of wrapper around it so as to make it possible to use it without bloating code with lots of JNDI-specific stuff.
- There's no way to specify sets of services that should be available to all components. This leads to a LOT of duplication in the XML descriptor and is just very tedious. For example, if all your components need access to a DataSource you'll have to define it for each of those components. Yuck.
Here's what I did for my AOP framework: instead of using JNDI I simply made a static class with a getService(Class) method. This is simple enough that I can access it directly in code without having to "go through hoops". Then, in my XML descriptor for the AOP objects I made it possible to declare sets of services that could be named. For each object I would then either reference a specific service that was to be bound to it, or a set of services. Typically I would have a set named "common" that contained a bunch of commonly used servies, such as logging, datasources, persistence manager, and so on. This makes it much much easier to update this service binding environment, as I can change it in one place but use that change in a lot of places. Next, allowing for sets to include other sets makes it even easier.
The end goal of this is to make it possible to write objects in a way that they are not tightly bound to the runtime environment, thus allowing them to be reused more easily. And, as far as I can tell, it worked out pretty well. [Random thoughts]
When pondernig about building architecturally neutral code this crops up as one of the main core APIs that services and components require - a way to lookup the services that they need. I totally agree that JNDI is just too bloated and cumbersome.
We need a simple and easy to use lookup API that allows components or beans to lookup other services in a simple way that could use a variety of different implementations under the covers; whether its JNDI, Avalon, JMX etc.
One example of an API thats very close to what we all need is the ServiceManager thats part of Avalon Framework. Though I'd prefer a slightly more flexible way of looking up services by the interface that they must implement as well as a name.
DataSource dataSource = (DataSource) LookupFacade.lookup( DataSource.class, "news" );
So that the API (DataSource) and the name (news) can be specified. Some applications may only have 1 data source so that the name is not required. However lots of applications have different services that implement the same API, so having some kind of naming mechanism as well would help.