Document Properties
Kbid25G731
Last Modified04-Feb-2020
Added to KB17-Apr-2014
Public AccessEveryone
StatusOnline
Doc TypeGuidelines, Concepts & Cookbooks
Product
  • ICM 7.6
  • ICM 7.7
  • ICM 7.8
  • ICM 7.9
  • ICM 7.10

Concept - Component Framework

1 Introduction

The component framework is introduced to declare dependencies between code artifacts. The major idea behind "declaring dependencies" is that each piece of the application can define the corresponding requirements and functionality which can be used by other components. The result is that it is now possible to fulfill requirements of a component with different implementations.

The component framework can also be used to declare the instances and wiring. Different application types can use different instances and/or wirings, but it is also possible to use the same instances. Using the same instances is very helpful in case of UnitTests; required contracts can be implemented with some kind of test-aware implementations.

The intention of the component framework is to provide a (LEGO) brick system that can be composed as needed. The contracts are the glue between the bricks. So it is important to think about generalized contracts and reusable implementations.

The component framework allows developers to write and organize functional components. Splitting functionality into components has the following advantages:

  • encapsulation - component contracts define the functionality of a component
  • increasing environment-independence - a component instance does not know about which components are linked
  • better testability - the testCase can link the component with test-enabled mock components which reduces the test dependencies to other implementations
  • re-usability - introducing component contracts enables the developer to think about interaction between static components
DIA General Component Idea

Both implementation I and U do not know to which components the instances will be wired. The instance U does not need to resolve instance I. U gets I injected.

Benefits

It is possible to

  • separate interfaces for usage and configuration of a component
  • avoid implementation of factory classes
  • avoid code for retrieving dependent components (like another dependency injection framework)
  • have multiple instances for one interface with the same or different implementations
  • have explicit wiring

2 References

For common questions in this context, refer to the corresponding cookbook.
More information on XML definitions can be found in the Reference - Component Framework.

3 XML Definition

The component framework uses an XML declaration of contracts, implementations and instances. Initially, we started with two options: 1) annotations and-or 2) XML declaration. Annotations work well for contracts and implementation declaration, but not for instance declaration. Additionally, annotations modify the source code of the component classes, so the implementation depends on the framework. That was a major disadvantage, especially while other frameworks are on the market, like Spring or Guice. Now the component implementations can be instantiated via "new" and configured for test cases without the framework.

3.1 Defining Contracts

Defining contracts is as "simple" as writing Java interfaces. It is important that you can (and hopefully you will) split the interfaces to requirement interfaces and provided interfaces. For example, normally you put getter and setter together in one interface to configure the instance. You can remove these types of methods from the provided interface. The provided interface should declare what the instance should do and not what is required to fulfill this functionality. A good example is the CartridgeListProvider contract. It does not matter where the cartridges come from - the CartridgeListProvider provides a list of cartridge names.

CartridgeListProvider.java
public interface CartridgeListProvider
{
    public List<String> getSelectedCartridges();
}

Summary:

  • keep the contracts as small as possible
  • do not mix different aspects of the implementation into the contracts
  • keep in mind that the implementation can implement multiple contracts
  • find a second implementation which uses the same contract to think about the intention of the functionality
  • remove all setters (because the instance has no request state)
contracts.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
  <contract name="[name]" class="[classname]"/>
</components>

The XML description allows the following tags:

  • components - body tag for component definitions
  • contract - defines a component contract (currently only Java interfaces are supported), attributes:
    • name - name of contract
    • class - name of Java interface, abstract class, or primitive class

3.2 Defining Implementations

An implementation for a component instance cannot store request-, user-, session- or domain-specific information. The instance is running during the whole lifetime of an application server. This is currently a huge difference compared to business objects. Sometimes the performance requirements forces a caching of business objects, therefore you can use a cache, which is registered at the CacheAPI.

Defining a good structure for the implementations is a hard goal. This includes especially the partitioning of the functionality. If you separated the different aspects of the implementation, you will see that some implementation requires some other, and other implementations do not require anything.

implementations.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
  <implementation name="[name]" implements="[contract-name]" factory="[factory-name]" class="[class-name]" start="nameOfStartMethod" stop="nameOfStopMethod">
    <!-- multiple implements can be listed -->
    <implements contract="[contract-name]" />
    <requires name="[property-name]" contract="[contract-name]" cardinality="[1..1|0..1|1..n|0..n]" />
  </implementation>
</components>

The XML description has the following allowed tags:

  • implementation - defines an implementation of a contract, attributes:
    • name - name of the implementation
    • factory - name or class name of component factory (default: JavaBeanFactory)
    • implements - name of contract
    • class - name of the class that implements the contract (optional if the factory is not a generic factory)
    • start - name of method to start the component, start is called after all requirements are fulfilled (optional)
    • stop - name of method to stop the component, stop is called if the context is stopped (optional)
  • requires - define the requirements of the implementation, attributes:
    • name - name of property (used by JavaBeanFactory to define names of setter/adder (with capital first letter))
    • contract - name of contract
    • cardinality - cardinality of property (default: 1..1) (used by JavaBeanFactory to define if a setter or adder is used)
      • 1..1 required
      • 0..1 optional
      • 1..n required many
      • 0..n optional many
  • implements - defines names of additional implemented contracts (extension to attribute implements of tag implementation)
    • contract - name of contract

3.3 Defining Instances

3.3.1 In General

The ComponentFW will instantiate all defined instances on the first access. Therefore the instance is created with the defined factory (default JavaBeanFactory). After the instantiation of the required instances, the requirements will be fulfilled.

Summary:

  • all defined instances are available for all applications of the application type
  • do not use the instance identifier inside of your implementation (use only the wiring to get access on other instances)
  • use anonymous instances if you do not need the instance twice for wiring
instances.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
  <!-- an instance for an implementation without requirements -->
  <instance name="[name]" with="[implementation-name]" />

  <!-- an instance for an implementation with directly fulfilled requirements -->
  <instance name="[name]" with="[implementation-name]">
    <!-- fulfill the requirement with a constant -->
    <fulfill requirement="[property-name]" value="[constant]"/>
    <!-- fulfill the requirement with another instance -->
    <fulfill requirement="[property-name]" with="[instance-name]" />
  </instance>

  <!-- outside of instance tag -->
  <fulfill requirement="[property-name]" of="[instance-name]" with="[instance-name]" />
  <fulfill requirement="[property-name]" of="[instance-name]" value="[constant]" />

  <!-- instance inside of fulfill tag -->
  <instance name="[name]" with="[implementation-name]">
    <!-- with attribute of fulfill tag is implicit - filled with inner instance element(s) -->
    <fulfill requirement="[property-name]">
      <!-- name of instance is optional - anonymous instances are allowed here -->
      <instance with="[implementation-name]">
        <fulfill requirement="[property-name]" with="[instance-name]" />
      </instance>
      <!-- recursive declaration of instances and fulfillment -->
      <instance with="[implementation-name]">
        <fulfill requirement="[property-name]">
          <instance with="[implementation-name]">
        </fulfill>
      </instance>
    </fulfill>
  </instance>

  <!-- replace an instance with a new one, the old is available via the name - value of delegate attribute -->
  <replace name="[name]" with="[implementation-name]" delegate="[renamed-instance-name]">
    <fulfill requirement="[delegate-property-name]" with="[renamed-instance-name]" />
<!-- other fulfill tags ... -->
  </replace>
</components>

The XML description has the following allowed tags:

  • instance - defines a component configuration for a component instance, attributes:
    • name - name of component instance
    • with - name of implementation
  • fulfill - defines the wiring or simple configuration of the component instance. One attribute of "config", "with" or "value" is mandatory. List of attributes:
    • requirement - name of property
    • of - name of configuration key (the value is retrieved from the configuration framework)
    • with - name of instance (the value is fulfilled with an instance)
    • value - constant definition (constants can be of type String, Integer, Boolean, Long, BigDecimal, Double, Float, Enums and any class which has a constructor with a string parameter)
  • replace - defines a replacement of an existing instance
    • name - name of component instance (like instance tag)
    • with - name of implementation (like instance tag)
    • delegate - new name of existing instance

3.3.2 Scopes

The introduction of the application framework made it necessary to define scopes for instances, so that for different applications different instances were created. The scope defined in the XML file defines the context in which the instance is created.

<instance name="[aName]" with="[anImplementation]" scope="global|app" />

There are two values available:

  • global: with this scope defined, only one instance of the implementation is created. The instance will be put into the "global" context and is available from all applications.
  • app: this value tells the framework to create one instance of the implementation per application. The instance is created exclusively for the current application and cannot be accessed by any other application.

If no scope is defined within the instance definition, the scope will be global.

4 Example

The following example shows a contract (MailService), two implementations (one SMTP service and one dispatcher/director). The instance definition defines 2 SMTP services and one dispatcher which delegates the requests to one of the SMTP services. Both implementations use the JavaBeanFactory (which is the default factory). This factory injects the dependencies via simple set* and add methods. The set method is used if the requirement has the cardinality 0..1 or 1..1, a single object has to be assigned (like the algorithm, port or host requirement). The add method is used if multiple objects can be assigned (like the delegate requirement of MailDirector).

DIA Cookbook Componentization MailService

Defining the MailService contract:

MailService.java
public interface MailService
{
    public abstract void sendMail(Mail mail);
}
contract-mailservice.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
  <contract name="MailService" class="com.intershop.beehive.component.mail.MailService" />
  <contract name="String"  class="java.lang.String" />
  <contract name="Integer" class="java.lang.Integer" />
</components>

Followed by 2 implementations:

smtp-mail.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
  <implementation name="SMTPMail" implements="MailService" class="com.intershop.beehive.mail.SMTPMailService">
    <requires name="host" contract="String"  />
    <requires name="port" contract="Integer" />
  </implementation>
</components>
director-mail.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
  <implementation name="MailDirector" implements="MailService" class="com.intershop.beehive.mail.DirectoryMailService">
    <requires name="delegate" contract="MailService" cardinality="1..n" />
    <requires name="algorithm" contract="String" cardinality="0..1" />
  </implementation>
</components>
DirectoryMailService.java
public class DirectoryMailService implements MailService
{
  private List<MailService> delegates = new ArrayList<MailService>();
  private boolean isRoundRobin = false;

  public DirectoryMailService()
  {
  }

  public void addDelegate(MailService delegate)
  {
    delegates.add(delegate);
  }

  public void setAlgorithm(String algorithm)
  {
    isRoundRobin = algorithm.equals("round-robin");
  }

  public void sendMail(Mail mail)
  {
  }
}

Defining instances and wiring them together.

instances.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
  <instance name="mail" with="MailDirector">
    <fulfill requirement="delegates">
      <instance with="SMTPMail">
        <fulfill requirement="port" value="23" />
        <fulfill requirement="host" value="intershop.mail.smtp1.host" />
      </instance>
      <instance with="SMTPMail">
        <fulfill requirement="port" value="99"/>
        <fulfill requirement="host" value="host2"/>
      </instance>
    </fulfill>
  </instance>
</components>

5 Accessing Components

5.1 Accessing Components From a Component Class

All component implementations should use requirement declarations and responsible "setter" and "adder" methods to get other component instances wired. So the instance does not need to know which other instance (name or type) is used or how to get such an instance.

Note

Business objects or business object extensions will be instantiated by factories which are component instances. So other component instances can be transferred via these factories.

5.2 Accessing Components From a Non-Component Class

Some artifacts, e.g. pipelets, will be instantiated by other global components. So these need a possibility to access components without wiring.
It is possible to get the instance from the component manager or from the current application via the name of the instance.

WithoutAppContext.java
T instance = NamingMgr.getManager(ComponentMgr.class).getGlobalComponentInstance(instanceName);
PipeletExample.java
AppContext currentAppContext = AppContextUtil.getCurrentAppContext();
App app = currentAppContext.getApp();
T instance = app.getNamedObject(name);

Note

The AppContextUtil.getCurrentAppContext() does not guarantee to return an AppContext. Always make sure a current AppContext is actually available before calling currentAppContext.getApp().

The lookup for an instance provides 3 possibilities:

  • all application types using the same instance (use scope "global")
  • multiple application types using the same global instance, but some are different (use named assignment)
  • each application type uses another instance (use scope "app")

Example:

DIA Component Concept app wired component

The component framework instantiates the yellow instances. All instances are registered at the ComponentContext. The developer can wire instances to the app (e.g., instance A is wired to the currentApp);
this will override the instance lookup for this special app. In the example, instance "A" will be returned.

Note

Do not use getComponent(String) inside of a component implementation. The initialization could fail under some circumstances. Use an additional requirement for your implementation and wire the instance instead.

Disclaimer

The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.

Customer Support
Knowledge Base
Product Resources
Support Tickets