Services and Service Registry

Targets usually offer micro-services.

For this purpose, they register so-called Services in a ServiceRegistry.

Upon registration, services receive an ID, the service ID (short: SID), of the type: ref: IId <ID>.

In contrast to the: ref: TID <TID> the SIDs are constant, documented and well known. In this way, services offered in external NODES can also be used.

You can also register Observer, which will receive a copy of each message sent to a service. For this reason, services are also used as event sources where messages are not addressed to a specific target. Notifications are simply sent to a dedicated service, and all interested parties receive a copy of the notification.

How do I use a service?

Messages have an envelope that contains the recipient’s address, among other things. This is usually the address of a target, like TID. NID. NODEID. Such an address is directed and the message is delivered directly to the target.

However, if you omit the target ID (TID), only the address of a namespace remains. This has the form NID.NODEID. If the kernel encounters such an incomplete address, it checks whether a service is registered in exactly this namespace, which has the same ID as the record ID. The record ID of the message must therefore be the service ID.

If the kernel has found such a service, a COPY of the message is sent to all observers attached to this service.

This can now be used as

  • Redirection: The provider of the service attaches his address as an observer to the service. He receives the message addressed to the service and answers as usual. The sender of the message does not know the address of the target. It could even be a target registered in another NODE.

  • Notification: The operator (or any other target) triggers the service by sending a message to the service. The trigger also works synchronously for local services. All observers of this service get the COPY of the message. This makes it easy to report changes in state to all interested targets.

  • Data Object: By the way, the last message is saved at the service. If a target now attaches its address as an observer to the service, it gets a COPY of the last message. Thus it is possible to transfer a state (in contrast to the state change) to the Observer.

    This is often used to indicate whether a subsystem has already been started. The SubSystem sets up a “SubSystemAvailable” service. Before a customer target uses the subsystem, it attaches an observer to this service and waits for the message “SubSystemAvailable”. When the message arrives, the customer can interact with the subsystem. If the target would send a message to the subsystem immediately after the startup, it would run the risk that the subsystem has not yet been loaded.

Example of a message to a service:

protected void sendCreateNamespace(IId aNid) throws CException
{
    final IId nid = CRecordCreateNamespace.getNID();
    final CEnvelope env = new CEnvelope(nid);

    final CRecord rec = new CRecord(CRecordCreateNamespace.ID);
    CRecordCreateNamespace.setParamWantedNid(rec, aNid);

    sendRequest(env, rec);
}

A new namespace is set up here. First we get the namespace where this service was registered. The envelope receives a recipient address consisting only of this namespace. This is not a complete address, of course. We do not include the TID because we want to address a service. And we can omit the HID, because we want to address a namespace of the same NODE. If we wanted to create a namespace in a remote NODE, this line would look like this:

final IId nid = CRecordCreateNamespace.getNID();
final INodeID remoteHid = CNodeIDFactory.create("a78d5028-79dd-4315-b47b-4266c35e7ab7");
final CEnvelope env = new CEnvelope(nid, remoteHid);

The record contains the user data, so we store the ID of the namespace we want to create. Afterwards we send the envelope and record, which together form the message.

The CRecordCreateNamespace service is operated by a system target that receives the message, executes the request and writes the result into the message as a response. The kernel will then transport the message back to the sender so that the result can be checked.

We also see from the example that it makes no difference to address a service in a local or remote system. This makes devel. one unique.

How Do I Set Up a Service As Target?

There are many possibilities here. If you describe the services and other messages with XML (very recommendable), the information is evaluated at the start of the namespace and the services are automatically registered in the system.

The article about the record generator can be found here: Überblick.

However, you can also do everything yourself with the ServiceRegistry:

final IId serviceId = CIdFactory.create("2d6cb8e5-80a5-4676-b88a-a5baaf7a995d");
final IServiceRegistry sr = getNamespace().getServiceRegistry();
sr.registerService(serviceId, "AddMonitorPanel");

Here, a service ID is generated from a UUID. The service registry of the local namespace is then fetched and the service is registered. The second argument only serves to facilitate debugging by assigning a descriptive name to the SID.

For remote NODES, the service must be registered with a message.

But now something is missing: The target does not receive any messages, as it has not yet attached an observer to the service. What we’re about to do:

sr.addObserver(serviceId, getAddress());

And something more is missing: In order to catch the message, we need a message handler.:

addMessageHandler(CRecordMonitorAddPanel.ID, new IMessageHandler()
{
    @Override
    public boolean handleMessage(final CEnvelope aEnvelope,
                                 final CRecord aRecord) throws Exception
    {
        final Object paramPanelRef = CRecordMonitorAddPanel.getParamPanelref(aRecord, null);
        final String paramTabName = CRecordMonitorAddPanel.getParamTabname(aRecord, null);
        final int paramTabOrder = CRecordMonitorAddPanel.getParamTaborder(aRecord, 0);

        if (paramPanelRef == null)
        {
            final String err = "PanelRef is null.";
            LOG.error(err);
            aEnvelope.setResult(CResultCode.INVALID_ARGUMENT, err);
        }
        else if (paramPanelRef instanceof JPanel == false)
        {
            final String err = "PanelRef is no JPanel.";
            LOG.error(err);
            aEnvelope.setResult(CResultCode.INVALID_ARGUMENT, err);
        }
        else if (CUtilString.isEmpty(paramTabName))
        {
            final String err = "PanelName is empty.";
            LOG.error(err);
            aEnvelope.setResult(CResultCode.INVALID_ARGUMENT, err);
        }
        else
        {
            final JPanel panel = (JPanel) paramPanelRef;

            ...
        }

        aEnvelope.setResult(null);
        return true;
    }
});

Here it is, a slimmed-down original message handler from the monitor. It ensures that plug-ins can attach additional tabs to the monitor’s TAB control, such as the sequence panel.

A service is removed using the following method:

public void deregisterService(final IId aSID) throws CException;

How to find out if a service exists?

In an asynchronous system, it is not always guaranteed that all services are registered right from the start. Some plug-ins are loaded after your own code, or many more services are added later. To do this, we need methods to identify the existence of services.

One possibility is of course to simply send a message to the service. If it cannot be found, the message returns with an error code.

For local services, it is easier to check the existence by synchronous method call.:

boolean exist = getNamespace().getServiceRegistry().existService(sid);
And what to do if the service was not found?
So it’s better to use a monitor right away::
boolean exist = existService(sid, getAddress());

If the service is NOT found here, a Monitor is registered. You get messages when the state of this service changes:

  • CRecordNotifyServiceRegistered.ID, if the service has been registered
  • CRecordNotifyServiceDeregistered.ID, if the service has been deregistered
  • CRecordNotifyServiceObserverRegistered.ID, if an observer has been registered
  • CRecordNotifyServiceObserverDeregistered.ID, if an Observer has been deregistered

There are also direct methods for registering and deregistering monitors:

public void registerMonitor(final IId aSID,
                            final ITargetAddress aObserver);
public void deregisterMonitor(final IId aSID,
                              final ITargetAddress aObserver);

Observer Methods

The following observer methods are supported by the service registry:

// Attaching an Observer
public void addObserver(final IId aSID,
                        final ITargetAddress aAddress) throws CException;

// Attach an observer to all these services
public void addObservers(final IId[] aSID,
                         final ITargetAddress aAddress) throws CException;

// Determine the number of observers on a particular service
public int getObserverCount(final IId aSID);

// Remove all observers of a service (Caution!)
public void removeAllObserver(final IId aSID) throws CException;

// Remove an own observer from a service
public void removeObserver(final IId aSID,
                           final ITargetAddress aObserver) throws CException;

// Remove all own observers from a service.
public void removeObservers(final ITargetAddress aObserver) throws CException;

The methods are probably self-explanatory.

Trigger

Usually you send messages to a service, which are then sent to the Observer. This process can also be triggered synchronously:

// Trigger the service synchronously (messages are sent asynchronously)
public void triggerObserver(final IId aSID,
                            final CMessage aTemplate) throws CException;

Here the observer receives a copy of the message aTemplate.

If you want to repeat the last trigger, you can use the following two methods:

public void recallTrigger(final IId aSID) throws CException;

public void recallTrigger(final IId aSID,
                          final ITargetAddress aAddress) throws CException;

While the first method takes into account all available observers of this service, the latter sends the message of the last trigger to the specified address.

Further Methods

The following methods are also supported:

// Returns the name of a service
public String getName(final IId aSID);

// Gives information about the service
public void getServiceInfo(final IId aSID,
                           final CRecord aRecord) throws CException;

// How many services are registered in this namespace?
public int size();

Services of the Service Registry

The following services provide asynchronous access to the service registry of each namespace. They can be used by message. A more detailed description can be found in the JavaDoc.

  • CRecordRegisterService.ID
  • CRecordDeregisterService.ID
  • CRecordGetServiceInfo.ID
  • CRecordNotifyServiceDeregistered.ID
  • CRecordNotifyServiceObserverDeregistered.ID
  • CRecordNotifyServiceObserverRegistered.ID
  • CRecordNotifyServiceRegistered.ID
  • CRecordTriggerRecall.ID

Services of the D1-NODE

Services are registered in the namespaces to which they are to apply. However, if you want to offer micro services that are relevant for the entire NODE and not just for a namespace, you should consider using the SYSTEM namespace.