Print
Container Hierarchy

Container Hierarchy

Containers in Yan can be organized in a hierarchy to allow modularization.

Scoped container

In a web application, we could manage some global components in the application scope, while some other components can be managed in a session scope.

Suppose we have two classes: A and B, where B depends on A.

public class A{
...
}
public class B{
  public B(A a){...}
  ...
}

For the application scope components, a container object can be created as:

Container app_container = new DefaultContainer();
registerGlobalComponents(app_container);

The registerGlobalComponents() function is responsible for registering the global application scope components:

void registerGlobalComponents(Container container){
  container.registerComponent(Components.ctor(A.class).singleton());
  ...
}

For the session scope components, a similar registerSessionComponents() function can be written as:

class SessionScope implements Pool{
  public synchronized Object getPooledInstance(){
    return session.get(key);
  }
  public synchronized Object getInstance(ObjectReference ref){
    Object r = getPooledInstance();
    if(r==null){
      r = ref.get();
      session.put(key, r);
    }
    return r;
  }
  private final HttpSession session;
  private final Object key;
  ...
}
...
void registerSessionComponents(Container container, HttpSession session){
  container.registerComponent(Components.ctor(B.class).singleton(new SessionScope(session, "B")));
  ...
}
  • The SessionScope class is an implementation of interface Pool. It is used to tell the singleton() combinator that the scope of the singleton is a http session.
  • The registerSessionComponents() function is responsible for registering session scope components.

We will then create a session scope container and register components to it:

Container session_container = new DefaultContainer();
registerSessionComponents(session_container, session);

Getting organized

It is obvious that the component B in the session scope container needs to depend on the component A which is in the application scope container.

The inherit() combinator can be used to construct the hierarchy as:

Container virtual_container = session_container.inherit(app_container);
  • The session_container.inherit(app_container) creates a virtual container where components in session_container can depend on those from app_container, but components in app_container are independent of the session container.
  • inherit() is an immutable operation where neither session_container nor app_container is changed. The newly created virtual_container is backed by app_container and session_container.
  • Component instances can then be instantiated from virtual_container, which allows constructor of B to use the component A from app_container.

Finally the containers need to be stored:

application.put("container", app_container);
session.put("container", virtual_container);
  • virtual_container instead of session_container is stored because we want the global components to be visible to each session.

Multi-inheritance

Depending on requirement, we may change our mind to have the application scope container inherit from the session scope one.

For example, there's a UserInfo object that we need to store in each session. And in the application scope we may register a component SingleSignon that depends on the UserInfo object:

public class SingleSignon{
  public SingleSignon(UserInfo user, ...){...}
  ...
}

The SingleSignon component is registered into the app_container:

void registerAppContainer(Container container)
  container.registerComponent(Components.ctor(SingleSignon.class));
}
  • The singleton pattern should not be used here.

The container hierarchy is organized as such:

Container virtual_container = app_container.inherit(session_container);
  • Note, the app_container is the one who's inheriting now.

Thus, for each different session, we get a different UserInfo object, and hence a different SingleSignon object.

You may start wondering that

How can you have the same application container inherit from different containers of each session at the same time? Won't you have concurrency problem?

Rest assured, the immutability nature of the inherit() combinator is to rescue. When "container1.inherit(container2)" is called, neither container1 nor container2 is modified. Instead, a new container object maintaining the inheritance relationship is created.

This means, you can inherit from as many containers as needed. One could even have app_container.inherit(session_container) and session_container.inherit(app_container) both exist at the same time if he's crazy enough to find a use for it. (If you find a senario to do so, please kindly let me know so I can update this article with a practical example.)

What about life cycle?

Life cycle in Yan is decoupled from containers. A separate LifecycleManager class is used to manage lifecycle for components.

According to the actual requirement, we could either associate the LifecycleManager with containers or keep them independent.

To support life cycle, the registerGlobalComponents() and registerSessionComponents() functions need to be slightly changed to include a LifecycleManager parameter:

void registerGlobalComponents(Container container, DefaultLifecycleManager man){
  Component a = Components.ctor(A.class).singleton();
  Lifecycle lifecycle = man.newLifecycle()
    .initializer("init")
    .disposer("destroy");
  container.registerComponent(man.withLifecycle(a, lifecycle));
  ...
}
void registerSessionComponents(Container container, HttpSession session, DefaultLifecycleManager man){
  Component b = Components.ctor(B.class).singleton(new SessionScope(session, "B"));
  Lifecycle lifecycle = man.newLifecycle()
    .starter("start")
    .stopper("stop");
  container.registerComponent(man.withLifecycle(b, lifecycle));
  ...
}

Now, if we want a central life cycle management regardless of the container hierarchy, we could use a single DefaultLifecycleManager object as:

DefaultLifecycleManager man = new DefaultLifecycleManager();
...
registerGlobalComponents(app_container, man);
...
registerSessionComponents(session_container, session, man);
...

This way, life-cycle for instances from both scope are managed at the same place in respect with the order of creation.

Or, we could create one DefaultLifecycleManager object for each scope:

DefaultLifecycleManager app_man = new DefaultLifecycleManager();
...
registerGlobalComponents(app_container, app_man);
...
DefaultLifecycleManager session_man = new DefaultLifecycleManager();
registerSessionComponents(session_container, session, session_man);
...

This way, we can manage component life-cycle separatly for different scope.

Powered by Atlassian Confluence