One to contain them all: Unifying the Mule Registry in 3.7

April 21 2015

4 comments 0
motif

In this post we’re going to continue the discussion started our last post “A sneak peek into Mule 3.7’s deepest internals” about how ’s , lifecycle and dependency injection mechanism are being overhauled in Mule 3.7. In this case, we’re going to take a deep dive into how we managed to unify the registries while keeping backwards compatibility and all the implications of such maneuvers.

IMPORTANT: If you haven’t read the first post in this series go ahead and do it now. This will be very difficult to follow otherwise. This post carries the same warning as the first one: it’s mainly intended for developers coding their own custom Mule components. If you’re the kind of person who codes their own components or finds it tasteful to leverage the low level Mule APIs and internals, then you should definitively read this series of posts. On the other hand, if you’re the kind of user which just simply uses Mule out-of-the-box and the Anypoint Studio palette gives you all you need, then you probably won’t be as excited about this long read, but it’s recommended anyway; it’s always good to know your tools.

Replacing the TransientRegistry

175

By converging on JUST one Spring registry:

  • We have all objects in a single registry. No interoperability problems.
  • Spring already knows how to create all objects in the correct order and inject dependencies into them, even in cases of circular dependencies.
  • Spring provides OOTB support for JSR-330 ( for the win!)
  • Spring is a proven tool that we already rely on. It just doesn’t make sense to try to build our own

This unification pretty much solves all the problems in the “registry split” and “interoperability and dependency injection” sections on the first post (seriously, read it before continuing). This is however, easier said than done…

TransientRegistry will be deprecated as of Mule 3.7.0. For backwards compatibility reasons, it will still be part of the distribution but it will not be used anymore. Along with it, the InjectProcessor and PreInitProcessor interfaces (and all their implementations) are also deprecated and will also not be used anymore. All these deprecated components are replaced by a matching BeanPostProcessor in the SpringRegistry.

SimpleRegistry

As stated before, one of the main purposes of the TransientRegistry is to keep the mule core and Spring decoupled – especially handy when running tests. All the tests that directly extend the AbstractMuleContextTestCase need the MuleContext to have a registry to function. Because of the decoupling requirements already stated, we don’t want that registry to be a Spring one. However, we need a registry that behaves “similarly enough” to it. So we created a new registry called SimpleRegistry. This registry has the following properties:

  • It is lightweight and simple enough for testing implementation of the Registry interface. NOT RECOMMENDED FOR PRODUCTION USE. The actual runtime will never instantiate it.
  • It will not apply any lifecycle operation when registering/unregistering any objects (unlike the TransientRegistry).
  • It has really basic support for JSR-330. It will perform dependency injection only on fields annotated with @Inject. Notice that this is not even close to fully supporting that JSR and that the way it’s implemented is really not performant.
  • It extends backwards compatibility with any custom tests referencing the TransientRegistry (notice however, that this is not something you should be doing).

This registry will be automatically added to the MuleContext on all subclasses of AbstractMuleContextTestCase which don’t extend FunctionalTestCase.

The FunctionalTestCase base class will continue to behave as usual, but it will only use the SpringRegistry. No SimpleRegistry or TransientRegistry will be used in that case (which mimics how the actual runtime will behave).

Bootstrap

If the TransientRegistry is being replaced by the SpringRegistry, that means that the latter (or some other component) needs to assume responsibility for all the tasks that it was performing. One of those tasks is processing the registry-.properties files.

Wow..wow…! registry-bootstrap.properties? What are those? When Mule starts, it searches the classpath for files called registry-bootstrap.properties. Those simple property files contain keys and class names for objects to be automatically added to the registry. For example, the new HTTP Connector uses it to register a connection manager, the Batch module uses it to register the batch engine, etc. A component called SimpleRegistryBootstrap was responsible for creating Instances of those classes and registering them in the TransientRegistry. This was problematic because:

  • As previously stated, the TransientRegistry wrongfully applied the initialise() phase when those objects were registered
  • The SimpleRegistryBootstrap object was fired as a spring bean, which created a very odd functional dependency between the TransientRegistry and the SpringRegistry.

SimpleRegistryBootstrap will also be deprecated as of 3.7.0, and is replaced with a new one called SpringRegistryBootstrap which is really similar in terms of functionality but with one key difference: it doesn’t instantiate and registers objects, instead it creates and registers BeanDefinition objects which are added to the Spring BeanFactory before it is initialized. In that way, these bootstrap objects go through the same creation-injection-initialisation cycle as the components defined in XML. This fixes a key issue: it will no longer be possible for an object to fetch a dependency which doesn’t yet exists. Actually, because now you can use dependency injection, you don’t need to fetch it at all!

Override mechanisms

Manual_Override_in_actionAnother mechanism that we need to keep backwards compatibility with is Agents and CoreExtensions. Just to stay in the scope, I won’t go into detail of what agents and core extensions are, but let’s just say that those are Mule extension points useful for adding functionality which is cross to any application deployed in a Mule container. A great example of that is the ClusterCoreExtension, which replaces the default QueueManager and ObjectStore instances for cluster versions of them, while also adding some objects of its own, like the HazelcastManager.

Before this refactor, that mechanism worked because the TransientRegistry already existed to receive those objects, and because it had precedence over the SpringRegistry, a call to muleContext.getRegistry().lookupObject(QueueManager.class) will return the version in the TransientRegistry instead of the one in Spring.

However, this is not a consistent behavior, because the user can declare this:

In this case, the myComponent bean would get the local QueueManager instead of the cluster one. Why? Because it is being created and injected by Spring, which knows nothing about the cluster aware QueueManager that the ClusterCoreExtension gave to the TransientRegistry. Yikes!

This issue is solved by the unification of the registries, but it introduces another problem: by the time the CoreExtension is executed, the SpringRegistry doesn’t exist yet.

The solution to that problem goes as follows:

  • When the MuleContext is created, by default it will still have one registry, but it will no longer be a TransientRegistry, but a SimpleRegistry, which means no eager initialization
  • The SimpleRegistry will catch any object registered before Spring kicks in
  • Upon creation the SpringRegistry will iterate through all other registries (if any) and will take over its registered objects. Objects and registries that have been taken over, are said to have been absorbed.
  • Each absorbed registry is removed from the MuleContext
  • When the SpringRegistry is asked for an absorbed object, it guarantees that the returned instance is the exact same that used to live in the absorbed registry

In this way we achieve:

  • Backwards compatibility with agents and core extensions
  • A consistent mechanism for overriding definitions
  • The overridden objects get a fair treatment regarding initialisation and dependency injection

Adding custom registries

We will continue to support adding user registries, but since we don’t know of anyone doing that, and because we’ve already identified it as a source of problems, that functionality will be deprecated.

Also, dependency injection will not be supported on custom registries.

In the next episode…

This is quite a lot to process and digest, so we’ll take a break and ask you to please join us next week on the next and final piece of this series. We’ll go through how the concepts of lifecycle and registries were separated, how the lifecycle works now, improvements in the initialization order, and a cool new Mule API for injecting dependencies into arbitrary objects, even if they’re not registered.

Thanks!

PS: Extra points to whomever can point out from which TV series was the “manual override” image taken from!


We'd love to hear your opinion on this post

4 Responses to “One to contain them all: Unifying the Mule Registry in 3.7”

  1. Nicely written articles Mariano! Looking forward to the last installment to better understand the impacts to the Lifecycle interface. We depend on that interface for our custom message processor which manages our local repository of outbound endpoints for our Service Registry implementation. We tried to migrate that custom message processor to use DevKit, but ran into problems with how DevKit facilitated the Lifecycle interface. I’m hoping that problem was fixed with these underlying changes.

    BTW…That picture is from the coolest car on TV in the 80’s, Knight Industries Two Thousand other wise known as K.I.T.T. The car was definitely more interesting than its driver. 🙂

    Agree(2)Disagree(0)Comment
    • Hello Drew, glad you found these useful.

      If you recall from the first post, this all starts with Mule having trouble honouring its end of the lifecycle bargain. If your components are implementing lifecycle correctly and if you’re not doing anything illegal, then you should be fine. So far we have only spotted a few cases in which this fixes were problematic and they were cases in which the bug was being used as a feature. An example of that would be a component A in which initialise phase a component B was required to already be started. If you’re not doing crazy stuff like that, then you should not only be fine but you should now have tools like dependency injection at your disposal, but we’ll talk about that on the next post.

      PS: KITT FTW!

      Agree(0)Disagree(0)Comment
  2. Good

    Agree(0)Disagree(0)Comment
  3. It’s an awesome article regarding the internal architecture of Mule. Please post more. I am excited to know how Mule works internally.

    Agree(0)Disagree(0)Comment