Sling Discovery API

As Adobe moved to cloud-native solution for AEM by introducing AEM as Cloud Service which can be scalable on both Author & Publish instances, there are scenarios for certain use cases which needs to be targeted to be run on one instance rather than all the current running instances. This article talks about how this problem can be solved by using Sling Discovery API.

In AEM as Cloud Service, Sling-based deployment consist of several author & publish instances which are scaled depending on the traffic & load. This number of instances would form a cluster that shares a common content repository.

 

The discovery-api bundle is introduced as an abstraction for such scenarios called topology. It provides access to the current topology, allows to be informed of any changes in the topology (Joining & Leaving Instances Nodes).

Discovery Entities

The following entities are defined by Discovery API.

Instance & Instance Description

Every AEM Instance node in Cloud Service is represented in the Discovery API by an InstanceDescription.

  • It represents one Sling instance.
  • It has a unique Sling ID
  • It has a flag that marks if it is leader in a cluster.
  • It has properties which are provided via PropertyProvider.

Cluster & ClusterView

Multiple Instances that are connected to the same underlying repository are called as a Cluster. In Discovery API cluster concept is represented via a ClusterView. Its features are as below.

  • Each cluster has a stable leader unless the leader crashes.
  • It has an ordered & stable list of instances which are currently live. The order of the list is stable & it only moves up a position if an instance listed crashes & replacing newly started instance will always be added at the end of the list.
  • It has unique id that is persistent across restarts.

Topology & TopologyView

The Topology or TopologyView represents a snapshot or view of several loosely coupled Sling instances (InstanceDescription) and Clusters (ClusterView) of a particular deployment. A cluster can consist of one or more instances. Each instance is always part of a cluster (Even if the cluster consists of only one cluster).

Cluster Leader

Discovery API introduces support for Cluster Leader.

  1. Within each cluster, the API guarantees that one & only one instance is leader at any time & it is guaranteed to be stable.
  2. As long it stays alive & is visible by other instances of the same cluster, it will stay as leader.
  3. As soon as it leaves the cluster another instance in the cluster is elected as leader.
  4. *The leader can be used to deal with work that must be guaranteed to only execute on one instance in the cluster.

Topology Changes

  1. The DiscoveryService provides access to the current valid TopologyView.
  2. Applications can register a TopologyEventListener & any change to the topology are informed. Whenever the discovery service detects that an instance is no longer responding or has newly joined, or a new leader has been elected it sends a TOPOLOGY_CHANGED event, starts settling the change within the topology.
  3. When only properties are changed a PROPERTIES_CHANGED event is sent.
  4. Following is an example of using listener with real time scenario.

Say, If a Service/Servlet/Scheduler implementations needs to be called only on Cluster Leader Instance in either author or publish tiers to execute any functionality/task that should not be executed by multiple running instances at the same time in AEM as Cloud Service, such use cases require using DiscoveryService by implementing TopologyEventListener.

Cluster Leader Use Case

  1. CREATE CLUSTER INTERFACE

package com.training.sling.service;

import org.apache.sling.discovery.DiscoveryService;

import org.apache.sling.discovery.InstanceDescription;

/**

 * Marker interface.

 *

 */

public interface ClusterLeader {

}

  1. CREATE CLUSTER EVENT LISTENER

package com.training.sling.service.impl;

import java.util.Dictionary;

import java.util.Hashtable;

import org.apache.felix.scr.annotations.Activate;

import org.apache.felix.scr.annotations.Component;

import org.apache.felix.scr.annotations.Deactivate;

import org.apache.felix.scr.annotations.Reference;

import org.apache.felix.scr.annotations.Service;

import org.apache.sling.discovery.DiscoveryService;

import org.apache.sling.discovery.TopologyEvent;

import org.apache.sling.discovery.TopologyEventListener;

import org.apache.sling.discovery.TopologyView;

import org.osgi.framework.BundleContext;

import org.osgi.framework.ServiceRegistration;

import com.training.sling.service.ClusterLeader;

/**

 * The listener interface for receiving clusterLeaderEvent events.

 *

 */

@Component

@Service(

        value = { TopologyEventListener.class })

public class ClusterLeaderEventListener implements TopologyEventListener {

    /** The discovery service. */

    @Reference

    private DiscoveryService discoveryService;

    /** The bundle context. */

    private BundleContext bundleContext;

    /** The cluster leader service registration. */

    ServiceRegistration clusterLeaderServiceRegistration = null;

    /**

     * Handle topology event.

     *

     * @param event

     *            the event

     */

    @Override

    public void handleTopologyEvent(TopologyEvent event) {

        final TopologyView newView = event.getNewView();

        if (newView != null) {

            if (newView.getLocalInstance().isLeader()) {

                registerClusterLeader();

            } else {

                unregisterClusterLeader();

            }

        }

    }

    @Activate

    public void activate(BundleContext bundleContext) {

        this.bundleContext = bundleContext;

        if (discoveryService.getTopology().getLocalInstance().isLeader()) {

            registerClusterLeader();

        }

    }

    @Deactivate

    public void deactivate() {

        unregisterClusterLeader();

    }

    /**

     * Register cluster leader.

     */

    private synchronized void registerClusterLeader() {

        final Dictionary<String, Object> props = new Hashtable<>();

        if (clusterLeaderServiceRegistration == null) {

            clusterLeaderServiceRegistration = bundleContext.registerService(ClusterLeader.class.getName(), new ClusterLeader() {

            }, props);

        }

    }

    /**

     * Unregister cluster leader.

     */

    private synchronized void unregisterClusterLeader() {

        if (clusterLeaderServiceRegistration != null) {

            clusterLeaderServiceRegistration.unregister();

            clusterLeaderServiceRegistration = null;

        }

    }

}

  1. INJECT CLUSTER LEADER

   /** The cluster leader. */

      @Reference

      private transient ClusterLeader clusterLeader;

Properties

The discovery API provides mechanism for announcing properties for each instance to the topology via PropertyProvider service interface.

Simple use case is the announcements of endpoint URLs or ports such that applications can communicate to other instances in the topology.

Example: –

import org.apache.felix.scr.annotations.Component;

import org.apache.felix.scr.annotations.Service;

import org.apache.sling.discovery.PropertyProvider;

@Component

@Service(value = { PropertyProvider.class })

@Property(name = PropertyProvider.PROPERTY_PROPERTIES,

          value = {“sample.value1”, “sample.value2” })

public class SamplePropertyProvider implements PropertyProvider {

      public String getProperty(final String name) {

            if (“sample.value1”.equals(name)) {

                  return “foo”;

            } else if (“sample.value2”.equals(name)) {

                  return “bar”;

            } else {

                  return null;

            }

      }

}