User Journey Analytics — A Micro frontend approach

MAYANK MITTAL
Deutsche Telekom Digital Labs
6 min readMar 18, 2024

Understanding User Journey Analytics (UJA)?

User Journey Analytics (UJA), is the process of tracking and analyzing the interactions and experiences of users as they engage with a product, service, or digital platform. It involves monitoring and mapping the various touchpoints and actions users take throughout their entire journey.

Why is it important for businesses to track them?

It empowers businesses to understand and optimize the user experience, increase conversions, enhance customer satisfaction, and make data-driven decisions. UJA also provides insights into user behavior, preferences, pain points, and opportunities for improvement.

How to use UJA in the world of micro frontends?

To implement analytics in micro frontends, you need to capture user journey events coming in from various micro-frontends and use a connector to link and transmit this data to the central data lake. This UJA micro frontend serves as the central broker that collects the UJA events from various other microfrontends and passes to the central data lake.

In this blog, we will discuss how to connect your app to the connector using the micro frontend approach.

You might know about the micro frontends, but let me give you a short introduction nonetheless.

Micro frontend is an approach to divide the whole FE application into smaller parts (MFs) so that all the smaller parts can work independently inside one container.

To understand more details about Micro frontend see: Breaking Monoliths: Exploring Micro-Frontends with Single-Spa

We will create a micro frontend to implement the UJA code and then use it in different MFs. For that, we will use the single-spa architecture. To communicate between the MFs, we will use an EventEmitter object called eventBus.

What is an event bus?

An event bus is a pattern used to communicate through events between different components or micro frontends without them knowing about each other.

It has two parts: emit and on. Emit is used to send the event name and payload, and “on” is used to receive the payload in the callback based on the event name. We define this event bus in the container application and pass this object to every MF, including the User Journey Analytics MF, as a prop.

· This eventBus object will also be used to emit events and listen to the emitted analytics data from other micro frontends.

· When an event is listened to in UJA MF, it will check if the connection to RabbitMQ is established or not.

· If the connection is already established, it will generate the common properties that need to be appended to the event payload.

· The enabler will hash the required properties using the HMAC SHA512 cryptographic mechanism.

· Then final event payload will be sent to the MQTT Server (RabbitMQ).

· If the connection is not there, it will keep adding analytics data to the message queue and send the data once the connection is reestablished.

In the Magenta View (MaVi) application, which is developed using single-spa, we use this concept to send events through the MF only. We call this the UJA Enabler. This MF loads initially before all the other MFs and makes a connection to the server. All other MFs are responsible for sending the events. Here is the architecture.

UJA in Magenta View container

Let Us start implementing the journey of analytics

UJA will be a “single-spa HTML” micro frontend that will listen to the events from the other MFs present in the container.

The connector that we are using here is RabbitMQTT. RabbitMQ is an open-source message broker that allows clients to connect over different protocols. MQTT (Message Queuing Telemetry Transport) is a publish-subscribe pattern-based “lightweight” messaging protocol.

JavaScript libraries that will be used

1. Single Spa HTML (npm i single-spa-html): A library for single-spa and vanilla HTML / web components.

2. Eclipse Paho JavaScript Client (npm i paho-mqtt): MQTT browser-based client library. It uses Web Sockets to connect to an MQTT broker

You can easily find the documentation of all these and how to import them.

The Flow

  1. This MF is loaded in the container initially.
  2. Then it loads MQTT configurations.
  3. After the configurations are loaded, it initializes the MQTT connection.
  4. It will reinitialize itself if, at any point in time, the connection is lost.
  5. After initialization, it will subscribe to the Eventbus object.
  6. Through the event bus, it will start listening to the events and then it will start publishing to the RabitMQ cluster from which the Rabit MQ will be connected to different tools like open search or kibana to show the analytics.

Too Much Theory?? Just check the Flow chart below

UJA Flowchart
UJA Flowchart

Let Us Talk about the code.

You can configure the basic things like username, password, session, and intervals in one file which you can call as config.json and then use it. In further code, I will use the options object to extract the values.

const options = {
invocationContext: {
host: "127.0.0.0.1", // replace it with your actual host. (don’t use any protocol)
port: "8080",
path: "/ ws",
},
reconnect: true, // set it to false if you don’t want to reconnect automatically
cleanSession: true,
keepAliveInterval: 300,
userName: "username",
password: "password",
useSSL: true,
reconnectAttempts: 10,
reconnectInterval: 180
};

Initiating the MQTT connection

function initiateMqttConnection() {
let host = options.invocationContext.host;
let port = options.invocationContext.port;
let path = options.invocationContext.path;
let eventTopicName = "rabitMQ/queue/name"; // this name is the rabitMQ queue name, you only must replace _ with /.
if (!host || !port || !eventTopicName) {
console.log("Configurations for MQTT client are not defined.");
return;
}
mqttClient = new Paho.Client(host, Number(port), path);
if (!mqttClient) {
return true;
}
mqttClient.onConnectionLost = onConnectionLost;
mqttClient.onMessageDelivered = onMessageDelivered;
MqttConnect();
}

Connect MQTT

function MqttConnect() {
let cleanSession = true;
let username = options.username;
let password = options.password;
const options = {
invocationContext: {
host: options.invocationContext.host,
port: options.invocationContext.port,
path: options.invocationContext.path,
},
reconnect: options.reconnect,
cleanSession: cleanSession,
keepAliveInterval: options.keepAliveInterval,
userName: options.username,
password: options.password,
onSuccess: onConnect,
onFailure: onFailure
};
if (location.protocol == "https:") {
options.useSSL = options.useSSL; // if your RabitMQ server is running on https, then ssl should be true
}
if (mqttClient && mqttClient.connect) {
mqttClient.connect(options);
}
}

What will you do when the MQTT is connected?

function onConnect() {
reconnectInterval ? clearReconnectInterval() : null;
if (messageQueue && messageQueue.length) {
(messageQueue || []).map(item => sendMessage(item));
messageQueue = [];
}
}

How will you send the message to MQTT?


function sendMessage(data) {
if (mqttClient && mqttClient.isConnected && mqttClient.isConnected()) {
const message = new Paho.Message(JSON.stringify(data));
message.destinationName = “rabitMQ / queue / name”;
if (mqttClient.send) {
mqttClient.send(message);
}
} else {
if (messageQueue && Array.isArray(messageQueue)) {
messageQueue.push(data);
}
}
}

What will you do if the Connection has been lost or failed?

function onFailure(response) {
console.log("User journey analytics enabler's connection has failed ", response);
!reconnectInterval ? ptions.reconnect ? setReconnectInterval() : null : null;
(reconnectAttempts >= options.reconnectAttempts) ? clearReconnectInterval() : null;
}


function onConnectionLost(response) {
console.log("UJA connection has lost ", response);
}

Return the message that has been sent.

function onMessageDelivered(msg) {
console.log("Message delivered ", msg.payloadString);
}

Reconnecting and clearing the interval

function setReconnectInterval() {
reconnectInterval = setInterval(initiateMqttConnection, config.reconnectInterval * 1000);
}


function clearReconnectInterval() {
clearInterval(reconnectInterval);
}

How event bus listen and send the data?

let eventBus = props.eventBus;
if (eventBus) {
eventBus.on(eventName, (data) => {
sendMessage(data)
});
}

How do different MFs send the data through the event bus?

// eventBus object's emit() function will be used to emit an event from any MF 
let data = {
appName: "Product",
appVersion: "1.0",
EVENT_ACTION: "event-action",
type: "event-type",
timestamp: 1660415894,
LOG_TIME: 1660415894,
extras: {
location: "/page-url",
natcoCode: "en",
environment: "development"
}
};
this.eventBus.emit(eventName, data);
// eventName should be a generic event name that will be used to send events from tile and listen events in User Journey Analytics Enabler

After RabitMQTT installation and the setup, you must publish the events to the queue of RabitMQ

const message = new Paho.Message(JSON.stringify(data));
message.destinationName = "product/analytics"; // Note: If we use "product.analytics" as routing key name then for using it in paho mqtt library we will have to replace "." with "/" eq: "product/analytics"
if (mqttClient.send) {
mqttClient.send(message);
}

Once we start emitting events from the Client we can view all published messages (event payload) in the RabbitMQ admin panel.

Yeah, you have set up the MF of user journey and you can name it as the enabler — User Journey Analytics Enabler.

--

--