Calling 3rd Party APIs


Any use of third-party APIs MUST be implemented in tasks. It's very convenient, as the following functionalities are included 'out of the box' with Zenaton regarding task processings:

  • monitoring
  • alerting in case of errors
  • automatic retries with exponential backoff if there is an error

TIP

To avoid creating a duplicate in case of retry after an error, we recommend having only ONE non-idempotent call. For example, if you are using a REST API, your task can implement many GET requests but should only include one POST request.

There are no limitation about the code within a task so you can use your preferred method to call a 3rd party service:

  • either require the service SDK (which must be added to your Zenaton project)
  • or use a http client (such as axios) to call API endpoints

Here is an example using Sendgrid and Star Wars APIs to retrieve more important characters in Star Wars saga, then send result by email:

// Workflow function
module.exports.handle = function*(email) {
  // Get the list of Star wars characters and their occurence.
  const nbMoviesPerCharacter = yield this.run.task("getCharactersOccurence");

  // create an array of tasks
  getDetailTasks = nbMoviesPerCharacter.map(c => ["getCharacterDetail", c.uri, c.nb]);

  // Run parallel tasks to get details on most famous characters.
  const famousCharacters = yield this.run.task(...getDetailTasks);

  // send results by email using sendgrid
  yield this.run.task("sendByEmail", email, famousCharacters);
};

getCharactersOccurence task

const axios = require("axios");

module.exports.handle = async function(threshold = 4) {
  const movies = (await axios.get("https://swapi.co/api/films/")).data.results;

  // Extract characters ids of all films, some can appear multiple times.
  const characters =  movies.reduce((acc, x) => [...acc, ...x.characters], []);

  // Count the number of films per characters ids.
  let nbMoviesPerCharacter =  characters.reduce((acc, c) => ({ ...acc, [c]: 1 + (acc[c] || 0) }), {});

  // Filter the characters ids to keep only those that appears in at least that 4 movies.
  nbMoviesPerCharacter = Object.keys(nbMoviesPerCharacter)
    .map(uri => ({ uri, nb: nbMoviesPerCharacter[uri] }))
    .filter(x => x.nb >= threshold);

  return nbMoviesPerCharacter;
};

getCharacterDetail task

const axios = require("axios");

// "getCharacterDetail" task
module.exports.handle = async function(uri, nb) {
  const characterData = (await axios.get(uri)).data;
  return {
    birth_year: characterData.birth_year,
    name: characterData.name,
    height: characterData.height,
    movies: nb
  };
};

sendByEmail task

const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

const emailFrom = "zenaton-tutorial@zenaton.com";

module.exports.handle = async function(to, famousCharacters) {
  const headers = ["Birth year", "Name", "Height", "Movies"];
  const cells = famousCharacters.map(x => Object.values(x));

  const msg = {
    to,
    from: emailFrom,
    subject: "SW results",
    text: "Here is your result: \n" + JSON.stringify([headers].concat(cells), null ,2)
  };

  sgMail.send(msg);
};

As in the example above, authorization tokens are usually provided through environment variables.