Digital Developer Conference: Hybrid Cloud 2021. On Sep 21, gain free hybrid cloud skills from experts and partners. Register now

OSGi demystified, Part 5: Using declarative services

The OSGi demystified article series addresses common OSGi issues in CICS — offering insight into OSGi, discussing best practices, and providing setup and configuration advice. This final article in the series explores declarative services in detail to demonstrate how to manage dependencies between different components of an application, and how the services approach can provide dynamically upgradable applications with zero downtime.

At its core, OSGi is a framework for managing components and their dependencies. In previous articles, we looked at how the OSGi framework manages dependencies — mainly from the bundle-wiring perspective. You saw that creating well-defined dependencies between components can provide a robust and predictable environment and vastly reduce run-time failures. However, as the dependencies between your components grow more complex, updates to any one component have a progressively larger impact. This complexity is most noticeable when you attempt to dynamically update parts of your application, and it becomes extremely complex to declare your bundle-wiring such that updates are realized. The OSGi framework wires one OSGi bundle to another at resolution time, and for the lifetime of that component it is averse to re-wiring (re-wiring can lead to breakage if the system has changed).

For this reason, OSGi services, not bundle-wiring, are the preferred mechanism for providing dynamic update capability. As a general rule, you should wire to the stable API while using OSGi services to bind dynamically to implementations of the API.

Service Component Runtime

The Service Component Runtime (SCR) is an implementation of the OSGi declarative services specification that offers a service-oriented component model to simplify OSGi-based development. Using this approach, OSGi bundles can be declared to provide service components, and those service components are instantiated by the runtime based on a piece of declarative XML (the component XML). The component XML also declares dependencies on other service components along with policy to control aspects of their behaviour.

Putting it more simplistically, it is a way to declaratively create components and services from your OSGi bundles and have dependencies on other services dynamically managed for you. It is this capability that you were most likely searching for when you updated the version and version range of your Import and Export Package statements in the MANIFEST. If you found it increasingly tedious to manage all the updates required to pick up newer versions of bundles, read on.

Of course, declarative services isn’t complete magic — you have to describe your components/dependencies and add some code hooks to get driven. But compared to rolling your own services, you won’t look back.

Component lifecycle

Let’s dive straight in. To create a component, you need to reference some component XML from your OSGi bundle. You can do this with the following bundle directive in your MANIFEST.MF:

Service-Component: path/to/component.xml

Each OSGi bundle can contain one or more of these components, and each component is described by some XML similar to that shown below. At this point, you are just declaring a basic component with no services; you’ll add a service shortly.

<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
   name="example.ds.impl.MyComponent"
   activate="activate"
   deactivate="deactivate"
   modified="modified">

   <implementation class="com.ibm.cics.java.example.ds.impl.MyComponent"/>
</scr:component>

When your OSGi bundle is installed into the OSGi framework, the MANIFEST.MF is read, the Service-Component directive is parsed, and the component.xml is processed. That processing results in the SCR instantiating an instance of your component class.

Each component has three lifecycle-related callback methods. You can optionally add these into your implementing Java class to be notified of the lifecycle events. The events are:

  • Activate, which is called after all services are bound
  • Modified, which is called when the component configuration is changed
  • Deactivate, which is called before your component is deactivated

The actual method names can be specified in the component XML file (as seen above), but if omitted they will default to activate, modified, and deactivate. These methods can have zero or more arguments, and each argument must be either the ComponentContext, BundleContext, or a Map of component properties. So let’s see what that looks like if you choose to implement them in your code:


package com.ibm.cics.java.example.ds.impl;

import org.osgi.service.component.ComponentContext;

public class MyComponent
{
    protected void activate(BundleContext context)
    {
        // Use bundle context
    }

    protected void deactivate(ComponentContext context)
    {
        // Use component context
    }

    protected void modified(Map<String, Object> props)
    {
        // Use properties
    }
}

No rocket science there! If you want those methods to be driven, add them to the class that’s specified as the implementation of your component.

Now that you’ve created a simple component from your OSGi bundle, it’s time to provide a service.

OSGi services

OSGi services are simply services provided by OSGi bundles. An OSGi bundle registers one or more services with the OSGi framework, which operates as an OSGi service repository. OSGi bundles can be providers of a service, or consumers, or both, or indeed neither.

In a basic implementation that doesn’t use declarative services, the activator of the OSGi bundle will call into the OSGi framework and register the services provided by that bundle or look-up/bind to any services it requires.

One of the key concepts to appreciate with OSGi services is that the lifecycle is dynamic. If an OSGi bundle is stopped or uninstalled, any services it provides are also stopped. There is no caching of services, and consumers of a service are expected to react to these conditions dynamically. Yes, that means you must code it all yourself. Groan!

However, as the use of OSGi services has evolved, patterns of registering, de-registering, binding, and unbinding to services have became apparent. Old services can go and new services can appear. Frameworks provide classes such as the ServiceTracker to reduce the amount of boilerplate code needed to react to these events. Eventually, we arrived at the declarative ways to configure services. These declarative approaches take care of all the service tracking, unbinding, and rebinding automatically. Known as declarative services and implemented by the SCR, this is a very powerful mechanism at your disposal.

Implementing an OSGi service

OSGi services are defined in two parts: the service interface and implementations of this interface. Take note that Java interfaces and OSGi service interfaces are not the same. OSGi service interfaces can be defined as either Java classes or Java interfaces.

An implementation of an OSGi service is a Java class that extends the service class or implements the service interface. A Java class may provide implementations of many service interfaces. An OSGi service may have multiple implementations.

So let’s first look at how a service would be registered programmatically. The OSGi APIs provide a method for registering services through the BundleContext object:

public void registerService(BundleContext context)
{
    MyServiceImpl implementation = new MyServiceImpl();
    Dictionary<String, ?> properties = new Hashtable<>();

    ServiceRegistration<MyService> registration =
        context.registerService(MyService.class, implementation, properties);
}

A ServiceRegistration reference is returned by the framework when the service implementation is registered. The OSGi bundle can use that same reference to later de-register (unregister) the service.

Declarative services

Since OSGi version 4, a service can also be registered declaratively in an XML file known as the component.xml. Taking this approach reduces the amount of boilerplate code required within the application, and allows developers to focus on business logic. Here’s an example that uses declarative services to register a service:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
   name="example.ds.impl.MyServiceImpl">

   <implementation class="com.ibm.cics.java.example.ds.impl.MyServiceImpl"/>
   <service>
      <provide interface="com.ibm.cics.java.example.ds.MyService"/>
   </service>
</scr:component>

In the component.xml, services implemented by this bundle are declared with provide elements and placed within the service element. The implementation class is indicated by the implementation element.

Note: When using OSGi declarative services, it’s good practice to use the lazy bundle activation policy (Bundle-ActivationPolicy: lazy), which means instantiation of declarative services takes place just before the service is called.

By itself, this declarative approach to defining services doesn’t add much value beyond removing the registration code. But service registration is only one aspect of declarative services. Key to declarative services is the ability to reference other services, to react to the loss of services, and to rebind to new implementations of services. Declarative services handle all that flux automatically and seamlessly.

Now let’s look at how service references can be defined in the component XML and called using Java APIs.

Service references

You’ve already seen how a bundle can register itself as an implementation of a service. So let’s take a look at how a bundle can consume other services.

<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
   name="example.ds.impl.MyServiceImpl">

   <implementation class="com.ibm.cics.java.example.ds.impl.MyServiceImpl"/>
   <service>
      <provide interface="com.ibm.cics.java.example.ds.MyService"/>
      <reference cardinality="0..1"
         interface="com.ibm.cics.example.java.ds.MyReferenceServiceService"
         name="ReferenceService"
         policy="dynamic"
         bind="setReferenceService"
         unbind="unsetReferenceService"/>
   </service>
</scr:component>

The component XML is used to express dependencies on other services. A reference element is used for each service you wish to consume. This element can be further refined with attributes such as the cardinality of the service. Will the component bind to one or many implementations of that service? Is the referenced service optional or compulsory? Can you bind dynamically to the service? Will you actively bind to newer versions if they become available? We will look at all these options in more detail shortly, but first let’s see what these references actually translate to in the code of your class:

package com.ibm.cics.java.example.ds.impl;

import com.ibm.cics.java.example.ds.MyService;
import com.ibm.cics.java.example.ds.MyReferenceService;

public class MyServiceImpl implements MyService
{
    private MyReferenceService referenceService;

    public void setReferenceService(MyReferenceService referenceService)
    {
        this.referenceService = referenceService;
    }

    public void unsetReferenceService(MyReferenceService referenceService)
    {
        if(this.referenceService == referenceService)
        {
            this.referenceService = null;
        }
    }
}

The SCR performs the mapping from your component.xml to your Java class. It acts upon the declarations, looks up services, generates instances, and drives the set and unset methods to inject appropriate service instances into your class. The SCR guarantees that service changes will be seamless. If a service you were relying upon is taken down, as long as there remains an implementation to bind to, there are no transient failures nor loss of service. This is a very powerful and extremely useful benefit of declarative services.

Reference attributes

As promised, let’s look at how reference element attributes determine the behavior of your component. Here’s a recap of the attributes you’ve used:

cardinality="0..1" interface="com.ibm.cics.example.java.ds.MyReferenceServiceService" name="ReferenceService" policy="dynamic" bind="setReferenceService" unbind="unsetReferenceService"

The cardinality attribute is probably the most important of these attributes. It determines whether you bind to one or more services and whether it is optional or mandatory to bind to that service. If the cardinality is optional and the service exists, you will bind to it. If the service does not exist, you won’t bind. In either event, your component will still activate.

The value used here — 0..1 — indicates that the service reference is optional and bound to a maximum of 1 service. If the referenced service is not active when your component is installed, you’ll activate anyway. If at a later time a matching service becomes active, you will be bound to it at that time. If an implementation of the referenced service is already bound and another one is activated, by default nothing happens (see policy attributes later to change that). That’s because your component already has its single reference. If the referenced service is deactivated, then the reference will be unbound and another service seamlessly bound in its place.

If you had chosen a cardinality of 1..1 and there were no referenced service available, your component would remain inactive until such time as a referenced service became available.

The following table should help you understand these options in more detail:

Cardinality Required Number of services Started
0..1 No Zero or one Can be started without any services bound
1..1 Yes Exactly one A service must be bound for the declared service to start
0..* No Zero or more Can be started without any services bound
1..* Yes At least one At least one service must be bound for the declared service to start

The interface and name attributes are self-explanatory, while the bind and unbind attributes correspond directly to the names of the methods in your Java class that the SCR will call to set/unset service references. If not specified, these default to the pattern setName and unsetName.

Dynamically binding services

The policy attribute is another key attribute. It controls how your component reacts when it rebinds to a service. Policy can be set to static or dynamic, and defaults to static. A static policy means that the service is restarted when a reference is bound or unbound from an active service. With the dynamic policy, binds and unbinds can occur on an active component. By default, both static and dynamic policy are reluctant — that is to say, once bound to a service they prefer not to bind to another. This behavior can be controlled by setting the policyOption attribute to GREEDY. When set to GREEDY, if a newer version of the referenced service is activated, your component will bind to that newer version and the existing service is unbound (in that order to prevent any outage).

Now let’s go on to implementation …

Dynamically updating applications without outage using OSGi’s declarative services

Let’s use the simple project that’s readily available on the CICSDev GitHub to explore how you can use OSGi’s declarative services to build applications that handle updates to services without outage.

This sample features a hypothetical requirement to create a local storage service. The storage service operates by providing some string data and getting an ID number in response. The ID number can be used to look up the data from the service at a later time. The sample is fairly simple and contrived, but serves our purpose to showcase declarative services.

To import the projects, simply clone or download the Git repository onto your local file system. Then, in Eclipse, FileImport…Existing Projects into Workspace. The root directory is the directory where the Git repository has been downloaded. Ensure that all projects are selected then click Finish. You may also need to set your target platform to “CICS TS 5.4 with Java EE and Liberty” (replacing 5.4 with the relevant version of CICS) to compile the projects.

Interface definition

As well-behaved programmers, we’re going to make use of interfaces — so your first task is to determine the OSGi interfaces needed to define your services. A well-designed interface needs less change and is easily extensible. Typically, it’s a minor change to add to an interface, and a major change to remove from or modify existing methods of an interface. The latter almost certainly requires dependent code to be modified, and could result in more disruption.

Good practice dictates that you should define the API and implementation in separate bundles. If you don’t, and modify an implementation stored with an API, the API dependencies alone will likely cause a cascade of bundle refreshes. By separating API and implementation(s), you can add and remove implementations without affecting the API. You should also package each OSGi bundle in its own CICS bundle for similar reasons.

In the API bundle com.ibm.cicsdev.osgi.ds.storage, you define the StorageService interface and export it using the Export-Package header in the OSGi manifest. That gives you a contract to work to. At this point, you’re free to write either the application that will use this code, or the implementation. Both could even be done at the same time by different teams — neither team needing to know what the other will do, as long as they keep to the interface.

The first implementation

You’ll start by writing a very simple implementation — something that can be used to verify the behaviour without doing anything complex. On a larger scale project, the team writing the application might even want to create a mock or stub service to verify the application without needing a real implementation.

In a new implementation bundle com.ibm.cicsdev.osgi.ds.storage.impl version 1.0.0, you create the Java class InMemoryStorage, implementing the StorageService interface. In this service, you just add and read a Java list. You then register your implementation using the declarative services component definition XML. In the component definition XML, you define that the InMemoryStorage class is an implementation of the StorageService. This allows the OSGi framework to handle the process of registering it as an OSGi service.

In Eclipse, these component definition XML files can be created and easily edited. Create a NewOther…Component Definition. The parent folder, by convention, is the OSGI-INF directory under the root of the project. Choose a file name, a name for the service, and the class the service uses.

Component definition image1

This will create the component XML file, and open a visual editor, which allows the above detail to be defined easily:

Component definition image2

This OSGi bundle is also packaged into its own CICS bundle. For a lot of projects, it makes sense to keep them separate which allows them to be swapped in and out easily.

Writing the CICS application

The next task is to write the CICS application that will use the service. In our system, the application is a CICS program that’s invoked via a CICS transaction. It could just as easily be linked to from another CICS program or invoked through a web service of some form.

You create the OSGi bundle com.ibm.cicsdev.osgi.ds.cicsapp to package the application code, and the class CICSApp processes parameters from the terminal and invokes the service. In this class, you’ll notice that there are methods for binding and unbinding the storage service, too.

Using declarative services, you create a component XML descriptor which specifies that the CICSApp class is an OSGi service that references the storage service, with a cardinality of 1..1 (so exactly one service must be bound at a time). As you haven’t specified otherwise, services will be bound reluctantly — so even if a service with a higher ranking is available, the current service will remain bound. The resulting XML looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
               activate="activate"
               deactivate="deactivate"
               modified="modified"
               name="com.ibm.cicsdev.osgi.ds.app.CICSApp">
   <implementation class="com.ibm.cicsdev.osgi.ds.cicsapp.CICSApp"/>
   <reference name="StorageService"
              interface="com.ibm.cicsdev.osgi.ds.storage.StorageService"
              bind="bindStorageService"
              unbind="unbindStorageService"
              cardinality="1..1"
              policy="dynamic" />
</scr:component>

We also provide a CICS-MainClass header in the OSGi manifest to ensure that CICS can resolve this class for the program link.

In addition to packaging this application in its own CICS bundle, you need to provide the accompanying PROGRAM and TRANSACTION resource definitions in the CICS bundle. Although it isn’t strictly necessary, keeping the application’s dependencies together within the same package is very convenient. Now you’re ready to deploy the first version of this application.

Deploy version 1.0.0 of the application

Now that you’ve deployed the CICS bundles to USS and defined CICS resources, here’s an overview of the naming scheme we’ve employed:

CICS Bundle Name OSGi Bundle Version Description
DS com.ibm.cicsdev.osgi.ds.storage 1.0.0 The OSGi interface bundle
DS-APP com.ibm.cicsdev.osgi.ds.cicsapp 1.0.0 The CICS application OSGi bundle
DS-IMP10 com.ibm.cicsdev.osgi.ds.storage.impl 1.0.0 The in-memory implementation of the storage service

Install all three CICS bundles, then run the transaction DSTS. To put data into the storage service, use the syntax DSTS PUT data. This will return the string “Created entry item” where item is the item ID of the stored data.

To retrieve data, use the syntax DSTS GET item, where item is the integer item ID to retrieve. Note that for both of these, you start your indexes at 1 not 0.

This should verify that the code is all working with this first simple implementation of your storage service.

CICS TSQ storage service

We think we can do better and provide a more capable version of our storage service. So let’s look at using a CICS TSQ to store data instead. TSQs don’t need to be defined before use, which keeps the set-up minimal for this sample. However, in a real-world example, you’d probably use something that’s more persistent like VSAM, Db2, or the coupling facility.

Later, we’ll show you how you can lean on declarative services and the OSGi framework to upgrade the services with no downtime for the application. But first, let’s create another OSGi bundle, com.ibm.cicsdev.osgi.ds.storage.impl, with version 1.1.0. In this implementation, the class TSQStorage contains the logic for reading and writing to a CICS TSQ. As with the in-memory service, you define the declarative services component XML descriptor and mark this as an implementation of the storage service. Package up the new implementation in a CICS bundle and export it to zFS. To recap, here’s a list of the OSGi and CICS bundles you should have:

CICS Bundle Name OSGi Bundle Version Description
DS com.ibm.cicsdev.osgi.ds.storage 1.0.0 The OSGi interface bundle
DS-APP com.ibm.cicsdev.osgi.ds.cicsapp 1.0.0 The CICS application OSGi bundle
DS-IMP10 com.ibm.cicsdev.osgi.ds.storage.impl 1.0.0 The in-memory implementation of the storage service
DS-IMP11 com.ibm.cicsdev.osgi.ds.storage.impl 1.1.0 The TSQ implementation of the storage service

Upgrading with zero downtime

Although the TSQ implementation of your storage service is installed and available, the application will continue to use the in-memory service until that service is disabled. That’s because you bound the storage service reluctantly, the default.

Even though the TSQ service is not bound to your application, it may be bound to other services if they used a 1..n cardinality, or had the greedy policy option. In your service, you could use this to migrate data from the old service to the new service to avoid data loss during the upgrade. For simplicity, we won’t cover this in our sample.

If you disable the DS-IMP10 bundle, you should notice some interesting events. Before the in-memory service is disabled, all services that reference it must be processed. The CICSApp service references this service, so CICSApp must either bind to another storage service or it will be disabled. Fortunately, the TSQ service is available so CICSApp is rebound to that service. Once all the references to your in-memory service have been processed, the in-memory service itself can then be unbound and ultimately disabled. A keen observer will note that there’s never a time at which the CICSApp is without a storage service — in fact, there’s a moment where it has two. The net result is that during the swap-over of service implementations, there is zero downtime … all by virtue of using OSGi declarative services.

It is easy to see the benefit of these types of seamless updates being made in a large running system with chains of referenced services. The ability to upgrade individual services without affecting the larger system is incredibly powerful.

Final thoughts

In this fifth and final article of the OSGi demystified series, you have seen an example of how to build applications that use OSGi declarative services. You’ve seen how to design services so they can be upgraded with zero downtime, and we touched on some important architectural designs like separating API from implementation and separating business logic from user interface. We hope this has inspired you and opened up a world of possibilities for your applications and your business.

For further reading, check out the links in the Resources section in the right nav.