If anyone is subscribed to the JAMES list, please include them on the conversation. This is an important issue that requires the attention of everyone involved. The next generation of the ComponentManager will be simplified from the current implementation, which will force some projects to change the way they design their systems and interfaces.
Therefore, I suggest we come together on how we lookup and use our components. Currently there are three trains of thought: one inspired by the ECM (monolithic), one inspired by Phoenix(only lookup), and one inspired by Fortress(in the middle). The first order of business is that we need to be clear on our terminology. * Component -- a managed resource that is resolved at runtime (late binding) * Container -- a resource manager that resolves component instances * Lookup Mechanism -- the interface that the client uses to lookup components * Client -- the code that uses components I would love to give an illustration, but its copyrighted... Anyway, here we go: MONOLITHIC DESIGN ----------------- No other way to say it than its bad. By merging all the concerns into one entity, we are tempted to do to add new methods to the lookup mechanism. One example that was probably (in retrospect) ill advised was the "release()" method on the CM. At the time both Giacomo and I argued that the only way a container would have the knowledge that a client is done with a component is to explicitly say so. Having seen what has happened in Cocoon's explosion of component types and interfaces, I can see there are better ways of doing things. The section at the end will identify exactly what I mean. SEPARATED CONCERNS ------------------ We have all been indoctrinated at how good this is. We know because we use it. Avalon components are much less painful to develop than EJBs, and a large part of that is because EJB design uses a monolithic mindset and Avalon uses SOC. A container is a container. A lookup mechanism is a lookup mechanism. There is nothing more that needs to be said about that other than what is philisophically separate should be separate in code. SOMEWHERE IN THE MIDDLE ----------------------- I started on Fortress with the mindset of correcting the most heinous wrongs committed by the ECM. A Container is a Container, and a lookup mechanism is a lookup mechanism. However, in an attempt to maintain as much compatibility with the ECM as possible it inherited a couple of the shortcommings. However, correcting those wrongs would require changing the way we write components. The origional ComponentManager Interface ---------------------------------------- interface ComponentManager { Component lookup( String role ) throws ComponentException; } That was it. Nothing more. The whole problem started with the need for named components, or components resolved by a hint. As a ressult we invented the ComponentSelector (actually in Cocoon there was a "NamedCOmponentManager" that resolved "NamedComponents"). The Avalon team did correctly deduce that there was no real need for NamedComponents. The name, or hint is a function that the container assigns to a component--not something inherent in the component itself. Then cocoon needed a mechanism for pooled components. However there was no way to return a component instance to the container. Peter tried to tell us that it can be done, and that it should not be done at the CM level. In retrospect, I am sorry we prevailed. As a result, we have the following: interface ComponentManager { Component lookup( String role ) throws ComponentException; boolean hasComponent( String role ); void release( Component comp ); } and interface ComponentSelector { Component lookup( Object hint ) throws ComponentException; boolean hasComponent( Object hint ); void release(); } -o0o- In an attempt to remove the ComponentSelector abstraction, I came up with something that would be a high performance CM interface. It used a Request or Prototype object to resolve the request for a component ahead of time. As a result, we could safely remove the requirement for the ComponentException. At first I thought it would be a step forward, and I pitched it to the Avalon list. When I got some opposition from Peter and Leo, I pitched it to Stefano to see if he could recommend anything. Surprisingly enough, Stefano's reply was simply "Ok, just one (maybe stupid) question: what's wrong in something like this: interface ComponentManager { Component getComponent(String role); Component getComponent(String role, Object hint); ... } " with a further clarification of "I currently disklike the abuse of (non-runtime) exceptions that Avalon throw to indicate things that are not found. A try/catch requires the JVM to save the state of the execution (sort-of an internal continuation) and this is a very expensive process. "The code above is much more performant and friendly in that respect, even if I still don't understand the need for those 'request' stubs." Which raises two very good points. How should we handle the CM interface, and do we need exceptions? As a result, I would like to go back to grass roots, with a couple modifications. Here is the interface for the CM: interface ComponentManager { Object lookup(String role); Object lookup(String role, Object hint); boolean exists(String role); boolean exists(String role, Object hint); } If you notice, there are four distinct differences from the current ComponentManger: 1) We return Object instead of Component. This merges the lessons learned from the ServiceManager discussions about accessing legacy components that are not able to emplement the Component interface. 2) We remove all *declared* exceptions. If there is something wrong, it is more appropriate to throw a RuntimeException. 3) We add an additional lookup and hasComponent method (role, hint combination). 4) We remove the release() method altogether. Now, I forsee few raised flags with the first three changes. They simplify our lives, and help make truly performant code. The role/hint combo takes its cue from a common construct in Java, used in JSSE, i18n resolution, and many other factories. Basically there is a default implementation, and the hint is used to let the "factory" choose a different implementation than the default if need be. It also lets us get rid of the ComponentSelector. In this case, it is a hint to the Container that I not only want a ConnectionManger, I want one that handles TLS connections. That way, we can look it up in one call, with no string concatenations: ConnectionManager conn = (ConnectionManager) manager.lookup( ConnectionManager.ROLE, "TLS" ); There is little confustion, and if the hint is not supplied, the CM returns the default implemenatation. It also lets us resolve bundles using a Locale: XMLBundle bundle = (XMLBundle) manager.lookup( XMLBundle.ROLE, Locale.getInstance("US") ); Or something like that... What will require some convincing--esp. to the Cocoon crew is the removal of the release() method. To that end, read the last section. -o0 What needs to change 0o- In order for a container to manage component instances using pools or per/lookup policies, we need to change the way we think about our components. Consider the DataSourceComponent as a prototype. With the DataSourceComponent, the client code does not care how the Connection object came into existance. It does not care that it has been used 100 times before. All it cares about is that the Connection is ready to be used now. The DataSourceComponent does not have a release method, but yet it manages to pool the Connection instances. The secret is the proxy layer. The proxy layer is a wrapper around the Connection object, that intercepts calls to the Connection's "close" method. As a result instead of truly closing the connection, it merely returns the Connection to the pool. The proxy also adds a method to check if the connection is still alive. So the proxy is one way of ensuring we have a component. It is also a way of returning a component automatically after a certain timeout has elapsed. If the component is needed again, the proxy is able to pull a new instance from the pool to perform the task. However, such a use does have consequences to the design of your components (i.e. a component can't store state information if it would automatically be returned to a pool after a timeout). One of the nice things about a proxy style component is that we can also enable a type of garbage collection behind the scenes. Consider Cocoon. Cocoon has a hierarchy with the Cocoon object as the root container, and each sitemap is a container in and of itself. Each container in the hierarchy knows when a request has been fulfilled--one way or another. As a result, the container can clean up all the components automatically without consulting the child components if they are done with the handles yet or not. Cocoon's pipeline components like the generators, serializers, et. al. would either need to be designed so that they are automatically returned to the container when they are done being used, or they would need to be changed so that the Generator returns an XMLSource, the Serializer returns a ContentHandler, and the Transformer returns an object that mixes the two. Once the pipeline has hit the endDocument() SAX event, it can clean up after itself! In order to make it easier for the component developers, we need to have a dynamic proxy generator (that would also help with DataSourceComponet too...). It would wrap the interface to enable garbage collection of components. All a client is responsible for is to request the component. Anyway I am open to comments now. -- "They that give up essential liberty to obtain a little temporary safety deserve neither liberty nor safety." - Benjamin Franklin --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, email: [EMAIL PROTECTED]