Microservices Best Practices: Updating Device Connectivity Status with Java SDK

Microservices Best Practices: Updating Device Connectivity Status with Java SDK

Overview

As you might have read in the Microservice Best Practices: Getting Started article already, Microservices can be used to implement multiple patterns. One of the most used patterns for Cumulocity IoT is the server-side device agent which retrieves/fetches data from devices, maps it to the Cumulocity IoT Domain model, and implements the REST Open API to transport the data to Cumulocity.

When updating the device connectivity status with a microservice this often leads to the case that the status is not updated as expected.
In this article, I will describe the problem in more detail and will provide a solution.

Device Connectivity status in Cumulocity

One key feature of Cumulocity IoT is to track the connectivity status of your devices as part of the device management, also called Connection Monitoring.

image

The connection of each device is tracked in two directions:

  1. Send connection: Interval the device has to send data to the platform - so called upstream data. It is also related to the Required interval that can be defined to define when a device is considered as offline. For example, if the required interval is set to 60 minutes the device needs to send data upstream at least once within 60 minutes otherwise it will be considered as offline.
  2. Push connection: The push connection is considered as active if you either have a long polling HTTPS connection on /notification/operations (old real-time API) or a MQTT client is connected and subscribed on s/ds topic. Otherwise, the connection will be considered as inactive. No data must be sent to update this status. It is sufficient to have an established connection.

Let me summarize the status from a device agent perspective:

To update the Send connection status the agent has to send any data within the required interval which is considered as a device request. What this means in detail I will explain in the next chapter.

To update the Push connection status the agent should have an active connection to Cumulocity and should make sure that it is reconnected when closed for any reason.

Updating the device connectivity status within a microservice

If you implement a server-side agent using the Java Microservice SDK there are some assumptions made so that the connectivity status is not updated as expected. Let me explain why this is the case.

The microservice SDK implements the Open API. As it was developed for multiple patterns (not only server-side agents) there was the assumption made that all requests should be counted as application calls and not device calls. This differentiation can be made by setting a specific header, the X-Cumulocity-Application-Key that contains the application name. Per default in the microservice SDK, this header is set which leads to the result that the device connectivity status is not updated when sending any data, which is on purpose.

Let me give you an example: Think about a scheduled aggregation microservice that calculates everyday aggregated metrics. If you didn’t have the header set, all these requests would be considered as device requests and each time the data is sent to Cumulocity they will be updating the connectivity status which confuses the user in the end.

So it really depends on your pattern and purpose of your microservice when to have this header set and when not.

Remove the X-Cumulocity-Application-Key header from Microservice SDK requests

As explained, in the microservice SDK it is assumed that the header should always be set therefor it cannot be easily removed or set for every request implemented within the SDK. Now, if you still want to remove the header you have to change the context the microservice uses to call the REST API.

Using the microservice SDK you always have to be in a specific context which is normally defined when calling MicroserviceSubscriptionsService and the methods provided: callForTenant, runForTenant, callForEachTenant, runForEachTenant

In that tenant context, some basic information is provided like the tenant, credentials and also the appKey which is important for us. It can be autowired to your implementation like this and accessed if you are in any context:

@Autowired
private ContextService<MicroserviceCredentials> contextService;

To modify that appKey we can clone the existing context, remove the appKey and used the modified context to call any cumulocity endpoints which should be considered as device requests.

Here is an example method:

public MicroserviceCredentials removeAppKeyHeaderFromContext(MicroserviceCredentials context) {
         final MicroserviceCredentials clonedContext = new MicroserviceCredentials(
              context.getTenant(),
              context.getUsername(), context.getPassword(),
              context.getOAuthAccessToken(), context.getXsrfToken(),
              context.getTfaToken(), null);
         return clonedContext;
    }

The important thing is to set the appKey to null.

In our original API calls we now just add this method removeAppKeyHeaderFromContext:

public MeasurementRepresentation createMeasurement(String name, String type, ManagedObjectRepresentation mor,
                                                       DateTime dateTime, HashMap<String, MeasurementValue> mvMap, String tenant) {

        MeasurementRepresentation measurementRepresentation = subscriptionsService.callForTenant(tenant, () -> {
            MicroserviceCredentials context = removeAppKeyHeaderFromContext(contextService.getContext());
            return contextService.callWithinContext(context, () -> {
                try {
                    MeasurementRepresentation mr = new MeasurementRepresentation();
                    mr.set(mvMap, name);
                    mr.setType(type);
                    mr.setSource(mor);
                    mr.setDateTime(dateTime);
                    log.debug("Tenant {} - Creating Measurement {}", tenant, mr);
                    return measurementApi.create(mr);
                } catch (SDKException e) {
                    log.error("Tenant {} - Error creating Measurement", tenant, e);
                    return null;
                }
            });
        });
        return measurementRepresentation;
    }

Now the newly created Measurement should be considered as a device request and the device connectivity send status should be updated as expected.

Summary

In this article I demonstrated how requests from a microservice implemented with the Java SDK can be modified so that they are considered as device requests, thus, updating the device connectivity status in the device management.

This should be used with caution and only be used for requests that are actually device requests otherwise it will lead to confusion updating the connectivity status even for requests which are not originated by any device.

Read full topic