top of page

News

Creating a real-time telephony service with Velo

Mon Nov 22 2021

2.gif
Blank_Canvas_on_Transparent_Background.p

Share

  • Twitter
  • Black LinkedIn Icon
  • Facebook

Here’s how we added support for ultra-low latency applications like real-time telephony to create our call-forwarding app.

Updated: Nov 23, 2021

I’m CTO and co-founder of a startup called Ringaly, and a full stack developer. I chose Wix over frameworks like Angular or React because of the radically faster time-to-market offered by its drag-and-drop Editor, its library of pre-integrated components, and the Wix APIs I can access through Velo, which let me choose my own path by building on top of the basic Wix functionality. The best example of Velo’s ability to extend Wix was our requirement to speed up Wix database queries by a factor of 10x to support real-time routing of telephone calls. To see why that is so important, you need to understand the Ringaly application.


The business

Ringaly is not what you might expect from a Wix site. It’s a cloud telephony service that ensures families don’t miss phone calls about their loved ones - or a family phone number that works beyond the home. Families buy a phone number from Ringaly, add family cell phones to it as contacts and share it with school, after school, doctors, etc. When someone calls the Ringaly number it simultaneously rings all their family contacts and the first to answer gets the call. Other family contacts get a text saying who called and who answered. Simple!


The technical problem

So our Wix site is fundamentally capturing the routing information used by our cloud telephony provider (Twilio) to forward calls and texts to family members. It’s not complicated information - just a simple mapping between Ringaly phone numbers and associated family contacts (and their names so we can send texts saying “Call from school was answered by Mom”) so it’s easy to store in a custom Velo database collection.


The technical problem is that Twilio needs to access this routing information in real-time, as incoming phone calls are received. The Wix database is powerful, scalable and resilient but isn’t optimized for high-speed applications out of the box. A typical database query on a Wix collection can easily take 2 seconds or more. To be fair it doesn’t normally need to be super fast. It just needs to be as fast as typical page load times. But telephony applications are *very* sensitive to latency. Calls that ring interminably get dropped. So we needed to speed up Wix collection queries by 10 times, from >2 secs to a blistering sub-200ms.


The solution: integrating Wix with Redis


Fortunately, speeding up database queries is a common problem with a standard solution: placing a high-speed data cache in between the database (Wix) and the reader (Twilio) - see the graphic at the top of this article. The idea is that data from the slower database is automatically synchronised into the faster cache so the reader can get at it more quickly. The cache we used is Redis (via Twilio Sync), an amazingly quick millisecond-level state synchronization API which we use as our cache.


There are a few different caching patterns or strategies (see this neat summary) but we settled on a hybrid of two cooperating patterns:


  • Client-side inline read-through cache

  • Backend inline write-through cache

Client-side inline read-through cache

/**
 * Get Ring using Redis to implement inline
 * (read-through) cache
 */
async function getRing(label, context, twilioClient, 
                                    axios, inNumber) {

  const ringDocName = 'ring-' + inNumber;

  let ringDoc = await fetchSyncDoc(
    context, 
    twilioClient, 
    ringDocName
  );
                                   
  console.log(
    label, 
    ringDocName, 
    ringDoc ? 
      JSON.stringify(ringDoc.data) : 
      'cache miss'
  );

  if (!ringDoc) {
    // Twilio overrides default Authorization header 
    // (so create our own) 
    let config = {
      params: { inNumber: inNumber },
      headers: { 
        'Ringaly-Authorization': 
          'Bearer ' + context.WIX_KEY 
      } 
    };
    // Retrieve OUT numbers from Wix
    const response = await axios.get(
      context.WIX_RING_URL, config
    ); 
    
    const ringInfo = getRingInfo(response.data);

    ringDoc = await createSyncDocWithRetry(
      context, 
      twilioClient,
      ringDocName, 
      ringInfo, 
      RING_TTL
    );
  }

  if (ringDoc)
    return ringDoc.data;
}

Twilio code


When a phone call comes in to Twilio (the client) it first looks in Redis for the associated family contacts. If it can’t find them (known as a ‘cache miss’) it uses the Axios library to make a HTTPS request to Wix (handled by the Velo wix-http-functions API) to fetch the data from the Wix database (even though this is slow) and adds them to Redis for next time. Although slow for the initial query this is a useful safety net in case the other caching pattern hasn’t already populated the cache.


Backend inline write-through cache


/**
 * Update Ring in database and cache
 * @param {*} userInfo
 * @param {Ring} ring - assume ring.outNumbers is 
 *   in stringified format
 * @returns {Promise<Ring>} updated Ring (with 
 *   stringified outNumbers)
 */

export async function updateRing(userInfo, ring) {
  let updatedRing = null;
  try {
    // FIXME FIXME wixData.update mutates
    // its input item object, 
    // removing included multi-references and
    // reverting included single references to Ids
    // Workaround: use a shallow copy 
    // of the input object

    let updateRing = Object.assign({}, ring);
    
    updatedRing = await wixData.update(
      constants.COLLECTION_RINGS, 
      updateRing, 
      {'suppressAuth': true}
    );

    const twilioSync = await getTwilioSync();
    const ringDocName = prefix + ring.inNumber;
    const ringInfo = getRingInfo(ring);

    // Just in case, check whether 
    // ringDoc is already in cache
    let syncDoc = await fetchSyncDoc(
      userInfo, 
      twilioSync, 
      ringDocName
    );
    
    if (syncDoc)
      await updateSyncDoc(
        userInfo, 
        twilioSync, 
        ringDocName,                             
        ringInfo, 
        RING_TTL
      );
    else
      await createSyncDoc(
        userInfo, 
        twilioSync, 
        ringDocName, 
        ringInfo, 
        RING_TTL
      );
  }
  catch(error) {
    logError(userInfo, label, 'updateRing', error);
    throw error;
  }
 
  return updatedRing;
}

Twilio code


Whenever call routing information is created or updated in the Wix database our Velo code updates both the database collection AND the cache. This keeps the cache ‘fresh’ and reduces (and potentially even eliminates) the possibility of client-side ‘cache misses’. The updateSyncDoc() and createSyncDoc() functions shown above call Redis APIs via an NPM module installed in the Velo backend.


Bottom line


The Velo wix-http-functions API allows third party applications (like Twilio, our cloud telephony provider) direct and secure access to the Wix database. In the opposite direction the fact that Velo is running on the extensible Node.js platform allows installation of third party NPM modules to directly access third party applications like Twilio from Wix (so we can make calls or send texts and cache information from the Wix database).


Ringaly is using Velo’s remarkable bidirectional extensibility to power a real-time family call-forwarding service and to speed up Wix database queries by 10x times! Wix gave us fast time to market and, thanks to Velo, we don't have to compromise on integrations or customization.


Learn more about Ringaly here.

 
 
 

Comments


Blank_Canvas_on_Transparent_Background.p

0

get certified.png

Related Posts

View All

1.png

CORVID LIBARY

Unified Database Management

Unified Database Management

Jun 18, 2018   8min

1.png

CORVID LIBARY

Unified Database Management

Unified Database Management

Jun 18, 2018   8min

1.png

CORVID LIBARY

Unified Database Management

Unified Database Management

Jun 18, 2018   8min

bottom of page