Obsługa użytkowników offline w kampaniach RBM

Ważnym czynnikiem czasu dostarczania wiadomości agenta jest to, czy użytkownik, z którym próbujesz się skontaktować, ma połączenie do transmisji danych w momencie wysyłania wiadomości przez agenta. Jeśli użytkownik jest offline, platforma RBM przechowuje wiadomość i próbuje dostarczyć ją przez maksymalnie 30 dni. Jeśli nie uda się dostarczyć wiadomości w tym terminie, jest ona usuwana z systemu.

Istnieje wiele powodów, dla których użytkownik może nie mieć połączenia, gdy agent próbuje się z nim skontaktować. Mogli wyłączyć transmisję danych, aby zaoszczędzić pieniądze na abonamencie, być w samolocie bez połączenia z Wi-Fi lub być w metrze w tunelu. W zależności od tego, jak pilna jest potrzeba dostarczenia wiadomości, agent musi płynnie obsługiwać użytkowników offline, unieważniając niedostarczone wiadomości RBM i przekierowując je innymi kanałami.

W tym artykule pokażę, jak używać Google Cloud Datastore do śledzenia wysyłanych i dostarczanych wiadomości, jak używać narzędzia cron do revoke niedostarczonych wiadomości, a także jak przekierowywać te wiadomości przy użyciu SMS-ów.

Monitorowanie wysłanych wiadomości

Każda wiadomość wysyłana przez agenta RBM musi zawierać unikalny identyfikator wiadomości. Aby śledzić wiadomości wysyłane przez agenta, musisz zapisać identyfikator, numer telefonu i sygnaturę czasową każdej wiadomości.

Możesz przechowywać te informacje przy użyciu różnych technologii, ale w tym artykule używam Google Cloud Datastore. Cloud Datastore to wysoce skalowalna baza danych NoSQL, która automatycznie obsługuje fragmentację i replikację. To świetne rozwiązanie do przechowywania niepowiązanych danych, takich jak wiadomości wysłane przez agenta. Cloud Datastore wymaga aktywnej instancji Google App Engine, dlatego do hostowania agenta RBM i konfigurowania zadania cron używam App Engine.

Istnieją biblioteki klientów Cloud Datastore dostępne w wielu językach. W tym przykładzie używam Node.js i kodu agenta RBM na przykładzie pierwszego agenta Node.js dostępnego w witrynie dla programistów RBM. Najpierw zainstaluję Cloud Datastore dla mojego projektu Node.js. Aby to zrobić, uruchom to polecenie:

npm install --save @google-cloud/datastore

W kodzie źródłowym agenta dodaję globalne odwołanie do biblioteki klienta Cloud Datastore.

// Imports the Google Cloud client library
const Datastore = require('@google-cloud/datastore');

// Creates a client
const datastore = new Datastore({
    projectId: PROJECT_ID,
});

Po utworzeniu obiektu datastore wprowadzam funkcję przechowywania stanu msisdn, message id, sent time i delivery dla każdej wiadomości.

/**
 *   Records an entry in the Cloud Datastore to keep track of the
 *   messageIds sent to users and the delivery state.
 *
 *   @property {string} msisdn The user's phone number in E.164 format.
 *   @property {string} messageId The unique message identifier.
 *   @property {boolean} delivered True if message has been delivered.
 */
function saveMessage(msisdn, messageId, delivered) {
    const messageKey = datastore.key(['Message', messageId]);

    const dataForMessage = {
        key: messageKey,
        data: {
            id: messageId,
            msisdn: msisdn,
            lastUpdated: new Date().getTime(),
            delivered: delivered
        },
    };

    // Record that the message was sent.
    datastore
        .save(dataForMessage)
        .then(function() {
            console.log('saved message successfully');
        })
        .catch((err) => {
            console.error('ERROR:', err);
        });
}

Gdy to zrobisz, musisz wywoływać ją za każdym razem, gdy agent wysyła wiadomość do użytkownika. Gdy biblioteka klienta RBM Node.js wysyła komunikaty RBM, biblioteka dostarcza obiekt odpowiedzi w metodzie wywołania zwrotnego, który zawiera messageId w przypadku wiadomości wysłanej do użytkownika.

Poniżej znajdziesz przykład wysyłania zwykłego tekstu do użytkownika i rejestrowania informacji o wiadomości po udanej komunikacji z interfejsem RBM API.

let params = {
    messageText: 'Hello, World!',
    msisdn:'+12223334444',
};

// Send "Hello, World!" to the user.
rbmApiHelper.sendMessage(params,
function(response) {
    // Extract the message id from the response
    let messageId = response.config.params.messageId;

    // Store the sent state in the Datastore
    saveMessage(phoneNumber, messageId, false);
});

Po uruchomieniu powyższego kodu możesz sprawdzić Datastore w konsoli Google Cloud w widoku Encje w Datastore.

Datastore

Aktualizowanie stanu dostarczenia wiadomości

Gdy agent przechowuje w Datastore żądania wiadomości, musimy zaktualizować stan dostarczenia po dostarczeniu wiadomości na urządzenie użytkownika.

Urządzenia użytkowników wysyłają zdarzenia DELIVERED, READ i IS_TYPING do agentów RBM przez Cloud Pub/Sub. W module obsługi Pub/Sub sprawdź dostarczone zdarzenia i zmień ustawienie Datastore dla dostarczonej flagi na „true”.

/**
 *   Uses the event received by the Pub/Sub subscription to send a
 *   response to the client's device.
 *   @param {object} userEvent The JSON object of a message
 *   received by the subscription.
 */
function handleMessage(userEvent) {
    if (userEvent.senderPhoneNumber != undefined) {
        let msisdn = userEvent.senderPhoneNumber;
        let messageId = userEvent.messageId;
        let eventType = userEvent.eventType;

        if(eventType === 'DELIVERED') {
            saveMessage(msisdn, messageId, true);
        }

        // TODO: Process message and create RBM response
    }
}

Agent zapisuje wiadomości wychodzące w Datastore i aktualizuje dane po otrzymaniu powiadomienia o dostarczeniu. W następnej sekcji skonfigurujemy zadanie cron w Google App Engine, które będzie uruchamiać się co 10 minut i monitorować niedostarczone wiadomości.

Cron w Google App Engine

Za pomocą zadań cron możesz planować zadania w różnych odstępach czasu. W Google App Engine zadanie cron jest skonfigurowane z opisem, adresem URL i interwałem.

W aplikacjach Node.js konfigurujesz je w pliku cron.yaml, który możesz wdrożyć w App Engine za pomocą pakietu SDK Google Cloud. Informacje o innych konfiguracjach konfiguracji językowych znajdziesz w artykule o planowaniu zadań z użyciem pliku cron.yaml.

Zadanie cron wymaga adresu URL, więc do routera Express aplikacji musisz dodać punkt końcowy URL, który będzie wywoływany przez program cron. Ten webhook odpowiada za wysyłanie zapytań do magazynu danych o starych wiadomościach, usuwanie ich z platformy RBM i wysyłanie ich do użytkownika SMS-em.

router.get('/expireMessages', function(req, res, next) {
    // TOOD: Query the Datastore for undelivered messages,
    // remove them from the RBM platform, and send them over SMS

    res.status(200).send();
});

Poniżej znajdziesz konfigurację pliku cron.yaml do wykonywania tego punktu końcowego co 10 minut.

cron:
-   description: "Processing expired RBM messages"
  url: /expireMessages
  schedule: every 10 mins

Aby wdrożyć w App Engine zadania cron, uruchom to polecenie:

gcloud app deploy cron.yaml

Po wdrożeniu App Engine automatycznie konfiguruje zadanie cron. Możesz je wyświetlić w sekcji App Engine > Zadania cron.

zadania cron

Wysyłanie do Datastore zapytań o niedostarczone wiadomości

W webhooku zadania cron skonfigurowanego w poprzedniej sekcji musisz dodać funkcje logiczne umożliwiające wyszukiwanie wszystkich wysłanych wiadomości, w których stan delivered ma wartość false (fałsz) i które są o lastUpdated razy starsze niż wstępnie zdefiniowany limit czasu, który odpowiada naszym przypadku użycia. W tym przykładzie wygasają wiadomości starsze niż godzinę.

Aby móc obsługiwać takie zapytanie złożone, Datastore musi mieć indeks złożony zawierający zarówno właściwości delivered, jak i lastUpdated. Aby to zrobić, możesz utworzyć w projekcie plik o nazwie index.yaml z następującymi informacjami:

indexes:
-   kind: Message
  properties:
  -   name: delivered
    direction: asc
  -   name: lastUpdated
    direction: desc

Podobnie jak w przypadku wdrażania zdefiniowanego wcześniej zadania cron, użyj pakietu SDK Google Cloud do wdrożenia zdefiniowanego przez Ciebie indeksu złożonego za pomocą tego polecenia:

gcloud datastore create-indexes index.yaml

Po wdrożeniu App Engine automatycznie konfiguruje indeks, a indeks jest widoczny w sekcji Datastore > Indeksy.

Indeksy Datastore

Po zdefiniowaniu indeksu możemy wrócić do webhooka utworzonego dla zadania cron i dokończyć proces wygasania wiadomości:

router.get('/expireMessages', function(req, res, next) {
    // Milliseconds in an hour
    const TIMEOUT = 3600000;

    // Threshold is current time minus one hour
    const OLD_MESSAGE_THRESHOLD = new Date().getTime() - TIMEOUT;

    // Create a query to find old undelivered messages
    const query = datastore
        .createQuery('Message')
        .filter('delivered', '=', false)
        .filter('lastUpdated', '<', OLD_MESSAGE_THRESHOLD);

    // Execute the query
    datastore.runQuery(query).then((results) => {
        for(var i = 0; i < results[0].length; i++) {
            let msisdn = results[0][i].msisdn;
            let messageId = results[0][i].id;

            // Stop the message from being sent
            rbmApiHelper.revokeMessage(msisdn, messageId);

            // Remove the message from the Datastore
            datastore.delete(results[0][i][datastore.KEY]);

            // TODO: Send the user the message as SMS
        }
    });

    res.status(200).send();
});

RBM nie obsługuje natywnie zastępczych SMS-ów, więc trzeba wdrożyć mechanizm wysyłania niedostarczonych wiadomości SMS-em.

Podsumowanie i TL;DR

Aby obsługiwać użytkowników offline, możesz utworzyć logikę unieważniania dla niedostarczonych wiadomości RBM. Czas potrzebny do wygaśnięcia wiadomości zależy od tego, jak bardzo pilne są przesyłane informacje. W przypadku informacji, które pilnie wymagają czasu, zalecany jest czas oczekiwania krótszy niż dwie godziny.

W tym przykładzie do zarządzania procesem przechowywania, zapytań i unieważniania używane są Cloud Datastore i Google App Engine, ale każdy mechanizm przechowywania danych do śledzenia wysyłanych i dostarczonych wiadomości powinien działać.

Powodzenia i życzę udanego programowania!