Service references

A very powerful feature of the component system is the declarative expression of references to other services registered in the App Runtime. The declarative component model significantly simplifies the handling of the dynamics of service dependencies.

A reference expression is a declarative wrapper of the method BundleContext.getServiceReferences (described in Programmatic service access), which requires an interface as selector together with an optional filter expression. Services which are selected by these conditions are called target services. The properties providing, filter and cardinality of a reference specify rules which are checked by the App Runtime. If all rules of a reference are fulfilled, the reference is said to be satisfied. A component configuration becomes satisfied when all references are satisfied. If a reference becomes unsatisfied, the component configuration also becomes unsatisfied and is deactivated. If a component configuration is activated by the App Runtime, some of the target services are bound to the configuration. These services are called bound services.

References are expressed as part of the component description.

manifest.json file: references of components
{
  "name" : "mybundle",
  ...
  "components" : [
  {
    "name" : "MyComponent",
    ...
    "references" : [
      {
        "name" : "logService",
        "providing" : "ct.framework.api.LogService"
      },
      ...
    ]
  },
  ...
  ]
}

A reference needs at least a name and a providing property. The name property must have a unique value inside the references of the component description and identifies the reference. The providing property defines the interface which the target service must provide.

Filtering of References

The property filter at a reference is used to further reduce the target services selected by the providing property. The value of filter is a filter expression described in the section Searching for bundles.

References filter property
{
  "name" : "myFactory",
  "providing" : "ct.framework.api.ComponentFactory",
  "filter" : "(Component-Factory=MyComponentFactory)"
}

It is allowed to use property placeholders in filter expressions, like in the following code sample.

References filter property
properties: {
   "storeId" : "sample-store"
},
references:[{
    "name": "store",
    "providing": "ct.api.Store",


    // the string '{storeId}' is replaced by the value of the property 'storeId' of the component configuration
  "filter": "(&(useIn=selection)(id={storeId}))"
}]

Placeholders in filter expressions allow the change of the target set of a reference based on configuration properties.

Reference Cardinality

By specifying the property "cardinality" on a reference you can declare target set conditions.

References cardinality property
{
  "name" : "myFactory",
  "providing" : "myservice.Interface",
  // optional reference, only one target service needed
  "cardinality" : "0..1"
}

Following cardinality values are supported:

Expression Description

1..1

Selects exactly one target service from the set of target services and a target service must be available to become satisfied. This is the default if no property is defined.

0..1

The reference is satisfied if none or more target services are available. Selects one service from the set of target services, if available.

1..n

Selects all target services from the set of target services and a target service must be available to become satisfied.

0..n

The reference is satisfied if none or more target services are available. Selects all services from the set of target services, if available.

If only one target service is selected then the first service returned by BundleContext.getServiceReferences is bound, which is the one with the highest ranking or the one first registered.

Reference Policy

A special lifecycle property is the "policy" property on a reference. It can have the value "dynamic" or "static" and declares the capability of a component instance to react on runtime changes of the matching target services of a reference. The policy is only important if changes in the target services still keep the reference satisfied. For example, this is the case if a reference has a 1..n cardinality and a new target service is registered at the system.

Value Description

dynamic

Defines that the component instance is able to react dynamically on changes in the bound service set itself. Therefore the App Runtime is not restarting the component configuration.

This is the default value.

static

Defines that the component instance is not able to react on dynamic changes in the bound service set. This means that the App Runtime restarts the component configuration on such changes.

References policy property
{
    "name" : "myFactory",
    "providing" : "myservice.Interface",
    "cardinality" : "0..n",
    // defines that the component cannot handle dynamic changes of that reference
    "policy" : "static"
}

Accessing References

The bound service set of declared references are provided by the App Runtime to a component instance in different ways. The App Runtime distinguishes between following main strategies:

  • Injection Strategy: References are transparently injected into the component instance and access is made via member variables in the component instance.

  • Event Strategy: References are injected into the component instance by method calls. The App Runtime calls specific methods provided by the component instance to inform about new services and service removal.

  • Lookup Strategy: References are manually looked up by the component instance in the component context.

All strategies can be mixed inside a component instance, very common is a mix of Injection Strategy and Event Strategy. The Lookup Strategy is rarely used.

All these strategies are discussed in the following sections on this sample component description:

Component description sample
{
    "name" : "mybundle",
    ...
    "components" : [
    {
        "name" : "MyComponent",
        "references" : [
            {
                "name" : "logService",
                "providing" : "ct.framework.api.LogService"
            },
            {
                "name" : "stores",
                "providing" : "ct.api.Store",
                "cardinality" : "0..n"
            }
        ]
    }
    ]
}

Accessing References via Injection Strategy

The injection strategy means that references are provided as member properties inside the component instance without any further effort on the side of the component instance. The component instance can directly access the injected properties. The injected properties are named exactly like the name property of the reference. To access the service properties of an injected reference, the special property <name>_info is injected.

Reference injection
export default class {
        doSomething(){

            // simply access the logService service instance,
            // because of the default 1..1 cardinality it is guaranteed to be available
            this.logService.info("DoSomething started!");

            // read service properties
            let logserviceProperties = this.logService_info;

            // this.stores contains all stores in the system (0..n cardinality)
            // if no stores are in the system, the array is still present but empty
            let stores = this.stores;

            // read service properties of stores, is an array, too
            let storeProperties = this.stores_info;

            stores.forEach((store,index)=>{
                // get service properties of current store
                let storeProps = storeProperties[index];
                let ranking = storeProps["Service-Ranking"];
                ...
            });
        }
}
Injection/ejection happens before activate and after deactivate and of course in between if the policy is dynamic and the target service set changes. For details, see component lifecycle methods.

Accessing References via Event Strategy

The event strategy is used by the App Runtime to inject services into the component instance if the instance provides event methods. The names of the event methods are calculated based on the multiplicity of the cardinality and the name of the reference.

Cardinality Name Calculation Pattern Sample

0..1 or 1..1

set<ReferenceName>

unset<ReferenceName>

setLogService : function(logservice, serviceproperties){ …​ }

unsetLogService : function(logservice, serviceproperties){ …​ }

0..n or 1..n

add<ReferenceName>

remove<ReferenceName>

addStores : function(store, serviceproperties){ …​ }

removeStores : function(store, serviceproperties){ …​ }

The name of the reference is capitalized to create the method name, so logService is converted into LogService.
Reference Event Strategy
export default class {
        constructor(){
            this.logger = null;
            this.stores = [];
        }

        // called by App Runtime to bind the log service
        setLogService(logservice, serviceproperties){
            this.logger = logservice;
        }

        // called by App Runtime to inform about unregistration of the log service
        unsetLogService(logservice, serviceproperties){
            this.logger = null;
        }

        // called by App Runtime to inform about a new store
        addStores(store, serviceproperties){
            this.stores.push(store);
        }

        // called by App Runtime to inform about unregistration of a store
        removeStores(store, serviceproperties){
            var index = this.stores.indexOf(store);
            this.stores.splice(index,1);
        }

        doSomething(){
            this.logger.info("DoSomething started!");
            this.stores.forEach((store)=>{
                ...
            });
        }
}

These default method names can be overwritten by defining the "bind" and "unbind" properties at the reference description.

Reference with custom bind
{
    "name" : "mybundle",
    ...,
    "components" : [
    {
        "name" : "MyComponent",
        "references" : [
            {
                "name" : "logService",
                "providing" : "ct.framework.api.LogService",

                // declare custom event methods (setLogging is used for bind and unbind)
                "bind" : "setLogging",
                "unbind" : "setLogging"
            }
        ]
    }
    ]
}

This results in an implementation like this:

Reference with custom bind method
export default class {

        constructor(){
            this.logger = null;
        }

        // called by App Runtime to bind and unbind the log service
        setLogging(logservice, serviceproperties){
            if (this.logger){
                // unset
                this.logger = null;
            }else{
                this.logger = logservice;
            }
        }

        doSomething(){
            this.logger.info("DoSomething started!");
        }
}
The event methods are called (like injection) before activate and after deactivate and of course also between if the policy is dynamic and the target service set changes. For details, see component lifecycle methods.

Accessing References via Lookup Strategy

A third variant for retrieving the services is to use the ComponentContext. The ComponentContext is provided by the activate method (see component lifecycle methods).

Reference-relevant methods of the ComponentContext are:

Method Description

locateServiceReferences : function(referenceName)

Returns an array of service references. The matching target services.

locateServices : function(referenceName)

Returns an array of service instances. The bound service set.

locateService : function(referenceName, serviceReference)

Returns service instance selected by the service reference.

The usage style is similar to working with the BundleContext. Services, which are located by using locateService or locateServices, should not be stored in member variables. The 'locate*' methods should only be called between activate and deactivate. For details, see component lifecycle methods.

References via lookup
export default class {

        activate(componentContext){
            // store component context in member variable
            this.componentContext = componentContext;
        }

        deactivate(componentContext){
            // clear member variable
            this.componentContext = null;
        }

        doSomething(){
            let componentContext = this.componentContext;
            // cardinality is 1..1 so the result array has exactly 1 item, the log service
            let logger = componentContext.locateServices("LogService")[0];
            logger.info("DoSomething started!");

            // get all stores
            let stores = componentContext.locateServices("Stores");

            stores.forEach((store)=>{
                ...
            });
        }
}

If the lookup style is used in a component instance, the special property "noInjection" should be set on the reference description to ensure that the App Runtime does not inject the references into the component instance.

References: noInjection property
{
    "name" : "mybundle",
    ...,
    "components" : [
    {
        "name" : "MyComponent",
        "references" : [
            {
                "name" : "logService",
                "providing" : "ct.framework.api.LogService",
                // disable injection behavior
                "noInjection" : true
            },
            {
                "name" : "stores",
                "providing" : "ct.api.Store",
                "cardinality" : "0..n",
                // disable injection behavior
                "noInjection" : true
            }
        ]
    }
    ]
}

Declarative Event Binding to References

A further feature of the component system is the declarative event binding. This is of interest if a component instance likes to listen on events on a reference and to execute a special method if that event is fired.

It can be distinguished between three kinds of event binding styles: "connect", "on", and "watch".

Style: on

This style uses the dojo/on module to connect to events provided by services. The "on" property on a reference declares the binding.

References policy property
{
    "name" : "aTool",
    "providing" : "ct.tools.Tool",
    "filter" : "(id=atoolname)",

    // on is an object with event names to event handle methods
    "on" : {
        // Click = event name fired by tool
        // _handleOnClick = the method which should be called if the event fires
        "Click" : "_handleOnClick"
    }
}

The code of the component instance might look like this:

MyComponent implementation using injected properties
export default class {

    _handleOnClick(event){
        // do something...
        console.debug("clicked..", event);
    }
}

Style: connect

This style should be replaced by using the on style, when possible.

This style uses the dojo/_base/connect module to connect to events provided by services. The "connect" property on a reference declares the binding.

References policy property
{
    "name" : "aTool",
    "providing" : "ct.tools.Tool",
    "filter" : "(id=atoolname)",

    // connect is an object with event names to event handle methods
    "connect" : {
        // onClick = event fired by tool
        // _handleOnClick = the method which should be called if the event fires
        "onClick" : "_handleOnClick"
    }
}

The code of the component instance might look like this:

MyComponent implementation using injected properties
export default class {

    _handleOnClick(event){
        // do something...
        console.debug("clicked..", event);
    }
}

Style: watch

This style declares a property change listener. It can be used at services which provides watch methods, like defined by dojo/Stateful. All ct/Stateful and dijit/_Widget instances provide such a method.

The "watch" property on a reference declares the binding.

References policy property
{
    "name" : "aStateful",
    "providing" : "a.datamodel.Instance",

    // watch is an object with property names to property change listener methods
    "watch" : {
        // selectedItem = name of the property about whose value changes should be informed
        // _handleSelectedItemChange = the property change listener method
        "selectedItem" : "_handleSelectedItemChange"
    }
}

The code of the component instance might look like this:

MyComponent implementation using injected properties
export default class {

    _handleSelectedItemChange(propertyname, oldvalue, newvalue){
        // do something...
    }
}