RBM 캠페인에서 오프라인 사용자를 처리하는 방법

에이전트 메시지 전송 시간의 주요 요소는 에이전트가 메시지를 보낼 때 도달하려는 사용자에게 데이터 연결이 있는지 여부입니다. 사용자가 오프라인 상태이면 RBM 플랫폼은 메시지를 저장하고 최대 30일 동안 전송을 시도합니다. 그때까지 메시지를 전송할 수 없으면 시스템에서 삭제됩니다.

에이전트가 사용자에게 연락하려고 할 때 사용자가 연결되지 않을 수 있는 이유와 상황에는 여러 가지가 있습니다. 모바일 요금제 비용을 절감하기 위해 데이터를 사용 중지했거나, Wi-Fi에 연결되지 않은 비행기를 타거나, 터널에서 지하철에 탑승하고 있을 수도 있습니다. 메시지가 도착해야 하는 긴급성에 따라 에이전트는 전달되지 않은 RBM 메시지를 취소하고 해당 메시지를 다른 채널을 통해 다시 라우팅하여 오프라인 사용자를 적절하게 처리해야 합니다.

이 문서에서는 Google Cloud Datastore를 사용하여 전송 및 전송한 메시지를 추적하는 방법, 크론을 사용하여 전송되지 않은 메시지를 revoke하는 방법, SMS를 통해 해당 메시지를 다시 라우팅하는 방법을 설명합니다.

보낸 메일 추적하기

RBM 에이전트가 전송하는 모든 메시지에는 고유한 메시지 ID가 포함되어야 합니다. 에이전트가 전송하는 메시지를 추적하려면 각 메시지의 메시지 ID, 전화번호, 타임스탬프를 저장해야 합니다.

이 정보를 저장하는 데는 다양한 기술을 사용할 수 있지만 이 문서에서는 Google Cloud Datastore를 사용합니다. Cloud Datastore는 샤딩 및 복제를 자동으로 처리하는 확장성이 뛰어난 NoSQL 데이터베이스입니다. 에이전트가 보낸 메시지와 같은 비관계형 데이터를 저장하는 데 유용한 솔루션입니다. Cloud Datastore는 활성 Google App Engine 인스턴스가 있어야 하므로 App Engine을 사용하여 RBM 에이전트를 호스팅하고 크론 작업을 구성합니다.

Cloud Datastore용 클라이언트 라이브러리는 다양한 언어로 사용할 수 있습니다. 이 예에서는 Node.js를 사용하고 RBM 개발자 웹사이트에 있는 첫 번째 에이전트 Node.js 샘플을 기반으로 RBM 에이전트 코드를 만듭니다. 먼저 다음 명령어를 실행하여 Node.js 프로젝트용 Cloud Datastore를 설치합니다.

npm install --save @google-cloud/datastore

에이전트 소스 코드에서 Cloud Datastore 클라이언트 라이브러리에 대한 전역 참조를 추가합니다.

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

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

Datastore 객체가 만들어지면 각 메시지의 msisdn, message id, sent time, delivery 상태를 저장하는 함수를 소개합니다.

/**
 *   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);
        });
}

이 함수를 사용하면 에이전트가 사용자에게 메시지를 보낼 때마다 이 메서드를 호출해야 합니다. RBM Node.js 클라이언트 라이브러리가 RBM 메시지를 전송하면 라이브러리는 사용자에게 전송된 메시지의 messageId가 포함된 응답 객체를 콜백 메서드에 제공합니다.

다음은 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);
});

위 코드를 실행한 후에는 Datastore 항목 뷰 내 Google Cloud Console에서 Datastore를 검사할 수 있습니다.

Datastore

메시지 전송 상태 업데이트

이제 에이전트가 보낸 메시지 요청을 Datastore에 저장하므로 메시지가 사용자의 기기에 성공적으로 전송되면 전송 상태를 업데이트해야 합니다.

사용자 기기는 Cloud Pub/Sub를 통해 DELIVERED, READ, IS_TYPING 이벤트를 RBM 에이전트로 전송합니다. Pub/Sub의 핸들러에서 전송된 이벤트를 확인하고 전송된 플래그의 Datastore 설정을 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
    }
}

에이전트는 발신 메시지를 Datastore에 저장하고 전송 알림을 받으면 데이터를 업데이트합니다. 다음 섹션에서는 Google의 App Engine에 크론 작업을 설정하여 10분마다 실행되어 전송되지 않은 메시지를 모니터링합니다.

Google App Engine의 크론

크론 작업을 사용하여 다양한 간격으로 작업을 예약할 수 있습니다. Google의 App Engine에서 크론 작업은 설명, URL, 간격으로 구성됩니다.

Node.js 앱에서는 cron.yaml 파일에서 이를 구성합니다. 이 파일은 Google Cloud SDK를 사용하여 App Engine에 배포할 수 있습니다. cron.yaml로 작업 예약에서 다른 언어 구성 설정에 대해 알아보세요.

크론 작업에는 URL이 필요하므로 크론이 호출할 익스프레스 앱 라우터에 URL 엔드포인트를 추가해야 합니다. 이 웹훅은 Datastore에 오래된 메시지를 쿼리하고 RBM 플랫폼에서 삭제한 후 SMS를 통해 사용자에게 전송합니다.

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();
});

다음은 이 엔드포인트를 10분마다 실행하는 cron.yaml 파일 구성입니다.

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

크론 태스크를 App Engine에 배포하려면 다음 명령어를 실행합니다.

gcloud app deploy cron.yaml

배포 후에는 App Engine이 자동으로 크론 태스크를 구성하며 App Engine > 크론 작업에서 태스크를 볼 수 있습니다.

크론 작업

Datastore에서 전송되지 않은 메시지 쿼리

이전 섹션에서 설정한 크론 작업의 웹훅에서 delivered 상태가 false이고 이 사용 사례에 적합한 사전 정의된 제한 시간보다 lastUpdated 시간이 더 오래된, 전송된 모든 메시지를 조회하도록 로직을 추가해야 합니다. 이 예에서는 1시간이 지난 메시지는 만료됩니다.

이와 같은 복합 쿼리를 지원하려면 Datastore에 delivered 속성과 lastUpdated 속성을 모두 포함하는 복합 색인이 있어야 합니다. 이를 위해 다음 정보가 포함된 index.yaml이라는 프로젝트에 index.yaml이라는 파일을 만들 수 있습니다.

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

앞에서 정의한 크론 작업을 배포할 때와 마찬가지로 Google Cloud SDK를 사용하여 다음 명령어로 정의한 복합 색인을 배포합니다.

gcloud datastore create-indexes index.yaml

배포 후에는 App Engine이 자동으로 색인을 구성하며 Datastore > 색인에서 색인을 볼 수 있습니다.

데이터 저장소 색인

색인이 정의되었으면 크론 작업용으로 만든 웹훅으로 돌아가서 메시지 만료 로직을 완료할 수 있습니다.

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은 기본적으로 SMS 대체를 지원하지 않으므로 전송되지 않은 메시지를 SMS를 통해 전송하는 로직을 구현해야 합니다.

요약 및 요약

오프라인 사용자를 처리하기 위해, 전달되지 않은 RBM 메시지의 취소 로직을 빌드할 수 있습니다. 메시지 만료 전 사용 시간은 전송하는 정보의 시간에 따라 다릅니다. 시간에 민감한 정보의 경우 제한 시간을 2시간 미만으로 설정하는 것이 좋습니다.

이 예에서는 Cloud Datastore와 Google App Engine을 사용하여 저장소, 쿼리, 취소 프로세스를 관리하지만 주고받은 메시지를 추적하는 저장 메커니즘도 작동해야 합니다.

좋은 결과가 있길 바랍니다.