Expand my Community achievements bar.

Join us for an upcoming in-person Adobe Target Skill Builders event ~~> We're hosting these live learning opportunities to equip you with the knowledge and skills to leverage Target successfully. Learn more to see if we'll be coming to a city near you!

Part 1b: Run Adobe Target NodeJS SDK for Experimentation and Personalization on Edge Platforms (Akamai Edge Workers)

Avatar

Administrator

8/17/22

Authors: Artur Ciocanu, Jaemi Bremner

EdgeWorker Script

Akamai EdgeWorker environment is based on v8 engine, so we can use most of the modern JavaScript constructs like async/awaitPromise, etc. However there are some limitations, all these are covered here.

When starting to develop using Akamai EdgeWorkers it is important to decide which event handler we want to implement. More details around event handlers can be found here.

For the sample code, I have decided to use responseProvider, since I want the EdgeWorker code to react to incoming HTTP GET requests and build an HTTP response. We will be using the Adobe Target NodeJS SDK, so we'll have to get the dependency via NPM using:

 

 

 

$ npm i /target-nodejs-sdk -P

 

 

 

The sample code looks like this:

 

 

 

import { httpRequest } from "http-request";
import { createResponse } from "create-response";
import { logger } from "log";
import TargetClient from "@adobe/target-nodejs-sdk";
import RULES from "./rules";
const STATUS = 200;
const HEADERS = {
  "Content-Type": ["application/json"]
};
const createTargetClient = () => {
  return new Promise(resolve => {
    const result = TargetClient.create({
      client: "<client code>",
      organizationId: "<organization ID>",
      decisioningMethod: "on-device",
      artifactPayload: RULES,
      pollingInterval: 0, // "0" prevents polling, if artifactPayload is provided
      targetLocationHint: "<location hint>", // prevent cluster discovery
      logger: logger, // use Akamai EdgeWorker provided logger
      fetchApi: httpRequest,
      events: {
        clientReady: () => resolve(result)
      }
    });
  });
};
export async function responseProvider(request) {
  const deliveryRequest = {      
    execute: {
      mboxes: [{
        index: 0,
        name: "mbox-params",
        parameters: {
          foo: "bar"
        }
      }]
    }
  };
  logger.log("Received request", JSON.stringify(request));
  const client = await createTargetClient();
  const { response } = await client.getOffers({ request: deliveryRequest });
  logger.log("Sending response", JSON.stringify(response));
  return createResponse(STATUS, HEADERS, JSON.stringify(response));
}

 

 

 

Note: The RULES constant references the On-Device Decisioning artifact rules.json file. This file can be downloaded from https://assets.adobetarget.com/<client code>/production/v1/rules.json. This file will be available only after you have enabled On-Device Decisioning for your Adobe Target account.

It is important to highlight that Akamai EdgeWorkers environment is a little bit different from NodeJS or browser, hence when using Rollup we have to opt-in to bundle all the code for the browser environment and make sure that all the global objects like window, global or anything like that are declared and properly initialized to avoid runtime errors.

The sample Akamai EdgeWorker leverages the Rollup banner configuration to prepend to the final JavaScript file all the necessary declarations like window, etc. Here is the sample Rollup banner text:

 

 

 

// All these are required to ensure everything runs smoothly in an Akamai EdgeWorker
var window = {};
var TextDecoder = function() {};
var setTimeout = function(callback) { callback(); };

 

 

 

Testing it out

If everything was set up properly, then you should have an Akamai property configured with Akamai EdgeWorker behaviour that can be accessed at a specific domain name. Using the domain name you could run a simple cURL command to check that everything is looking good. Here is a sample:

 

 

 

curl --location --request GET 'https://target-odd-dev.test.edgekey.net/v1/personalization' \
--header 'Pragma: akamai-x-ew-debug' \
--header 'Pragma: akamai-x-ew-debug-rp' \
--header 'Akamai-EW-Trace: st=1618421957~exp=1618425557~acl=/*~hmac=6b8f31571c646d01ad5155407775f5b5b07ef237848164f745ca86c3e938dad5'

 

 

 

This will execute an Akamai EdgeWorker that will run Adobe Targte NodeJS SDK On-Device Decisioning. The output would look something like this:

 

 

 

{
  "status": 200,
  "requestId": "2e412eb3dc594a198030097772fd1a8c",
  "id": {
      "tntId": "2b6b95529c8f418f877504cca96710dc.34_0"
  },
  "client": "targettesting",
  "execute": {
    "mboxes": [
      {
        "name": "mbox-params",
        "options": [
          {
            "type": "json",
            "content": {
              "foo": "bar",
              "isFooBar": true,
              "experience": "A"
            },
            "responseTokens": {
              "activity.id": 125874,
              "activity.name": "[unit-test] mbox-params",
              "experience.id": 0,
              "experience.name": "Experience A",
              "offer.id": 246852,
              "offer.name": "/_unit-test_mbox-params/experiences/0/pages/0/zones/0/1612386851217",
              "option.id": 2,
              "option.name": "Offer2",
              "activity.decisioningMethod": "on-device"
            }
          }
        ],
        "index": 0
      }
    ]
  }
}

 

 

 

Normally we would stop here, but we all know that nothing works the way we want the first time. So it is crucial to have proper troubleshooting tools at our disposal. Thankfully Akamai EdgeWorker allows you to get the logs that we write in the JavaScript code via HTTP headers. In order to enable this capability we have to add a few debug HTTP headers to the outgoing request, these are:

  • Pragma: akamai-x-ew-debug
  • Pragma: akamai-x-ew-debug-rp - used for responseProvider
  • Aakamai-EW-Trace: st=...... - contains the HMAC used for handshaking to ensure request is authorized to get the logs.

Akamai CLI for EdgeWorkers has a convenient command named auth that allows us to generate the value required for Akamai-EW-Trace. The auth command needs the Akamai EdgeWorker debug secret one that we have set up earlier. To create the HMAC for Akamai-EW-Trace we can use this command:

 

 

 

$ akamai ew auth <debug secret>

 

 

 

The output would look something like this:

 

 

 

Akamai-EW-Trace: st=1619377928~exp=1619378828~acl=/*~hmac=<generated HMAC>

 

 

 

When enabling debug headers, our sample response will look like this:

 

 

 

--yguZ36SBeirJVeeQGLblT7
content-type: application/json
content-disposition: form-data; name="response-provider-body"
{"status":200,"requestId":"20605b03b80d47c9be5351b650d2630b","id":{"tntId":"80d1734703214647967607a938f8e1fe.34_0"},"client":"targettesting","execute":{"mboxes":[{"name":"mbox-params","options":[{"type":"json","content":{"foo":"bar","isFooBar":true,"experience":"B"},"responseTokens":{"activity.id":125874,"activity.name":"[unit-test] mbox-params","experience.id":1,"experience.name":"Experience B","offer.id":246851,"offer.name":"/_unit-test_mbox-params/experiences/1/pages/0/zones/0/1612386851213","option.id":3,"option.name":"Offer3","activity.decisioningMethod":"on-device"}}],"index":0}]}}
--yguZ36SBeirJVeeQGLblT7
content-type: text/plain;charset=UTF-8
content-disposition: form-data; name="stream-trace"
X-Akamai-EdgeWorker-ResponseProvider-Info: ew=5536 v0.24:target-odd-edgeworker; status=Success; status_msg=-; wall_time=35.778; cpu_time=28.696
X-Akamai-EdgeWorker-ResponseProvider-Log: D:main.js:1635 Received request {"sandboxId":null,"cpCode":1171899,"url":"/v1/personalization","query":"","scheme":"https","path":"/v1/personalization","method":"GET","host":"target-odd-dev.test.edgekey.net","userLocation":{"continent":"EU","country":"MD","region":"","zipCode":"","city":"CHISINAU"},"device":{"isMobile":false,"isWireless":false,"isTablet":false}}|D::1642 Sending response {"status":200,"requestId":"20605b03b80d47c9be5351b650d2630b","id":{"tntId":"80d1734703214647967607a938f8e1fe.34_0"},"client":"targettesting","execute":{"mboxes":[{"name":"mbox-params","options":[{"type":"json","content":{"foo":"bar","isFooBar":true,"experience":"B"},"responseTokens":{"activity.id":125874,"activity.name":"[unit-test] mbox-params","experience.id":1,"experience.name":"Experience B","offer.id":246851,"offer.name":"/_unit-test_mbox-params/experiences/1/pages/0/zones/0/1612386851213","option.id":3,"option.name":"Offer3","activity.decisioningMethod":"on-device"}}],"index":0}]}}
--yguZ36SBeirJVeeQGLblT7--

 

 

 

Note: In this response we see JSON response, along with all the logs we have added to our Akamai EdgeWorker script. This approach can be invaluable when trying to debug Akamai EdgeWorkers.

Closing thoughts

In this article, we have proved that Adobe Target NodeJS SDK can be used successfully from an Akamai EdgeWorker. We have seen how Terraform and Akamai CLI can be used to create the necessary Akamai resources to be able to invoke the Akamai EdgeWorker using a simple HTTP GET.

While I am very pleased with the result, there are a few roadblocks and things I wish we could improve in the future:

  • Terraform Akamai provider recently released v1. Most of my previous knowledge about Akamai provider wasn't really applicable and I had to redo most of the property configuration from scratch. Thankfully the provider documentation is really good, but it still required some trial and error.
  • Terraform Akamai provider doesn't know about EdgeWorkers. My guess is because this is a recent product and the provider hasn't been updated. It's not that bad, since we can use Akamai CLI, but ideally, we should keep everything under one single tool.
  • Akamai EdgeWorker debugging/troubleshooting could be better. At this point in time, the only way to debug anything in Akamai EdgeWorkers is to use log statements. It works, but it is slow, since every code change requires uploading the bundle and activating it. We can and should use staging network for development, but still we are talking about minutes here. An alternative would be to use an Akamai sandbox, but there are some limitations related to sandbox and EdgeWorkers like inability to fire HTTP requests from within the EdgeWorker.

The biggest issue I have faced while working with Akamai EdgeWorker is the caching of Akamai EdgeWorker response. I haven't found anything in the documentation related to this behaviour. During development, I have created more than 20+ versions of an Akamai EdgeWorker and I was testing everything using the staging network. After awhile I would get a cached JSON response and it didn't matter if I activated a new version or not. After some head-scratching, I decided to purge the cache for Akamai EdgeWorker and after that everything got back to normal and I was able to see my code changes again. I was lucky enough that I had access to purge cache functionality, in other setups developers might be restricted from purging the cache.

After all this, should anyone try to use Akamai EdgeWorker, my answer would be YES, as long as you can work around the limitations imposed by Akamai EdgeWorker. The ability to run logic as closely as possible to your users can not be underestimated. Akamai has the biggest network of points of presence, so using Akamai you can deliver outstanding performance. While Akamai EdgeWorkers event handlers might be confusing at first, they provide a lot of flexibility and you hav more control around how a particular request should be processed.

Follow the Adobe Tech Blog for more developer stories and resources, and check out Adobe Developers on Twitter for the latest news and developer products. Sign up here for future Adobe Experience Platform Meetup.

Resources

  1. Source code — https://github.com/artur-ciocanu/odd-akamai-edge-workers
  2. Adobe Target — https://business.adobe.com/products/target/adobe-target.html
  3. Adobe Experience Platform — https://business.adobe.com/products/experience-platform/adobe-experience-platform.html
  4. Adobe Target NodeJS SDK — https://adobetarget-sdks.gitbook.io/docs/sdk-reference-guides/nodejs-sdk
  5. Terraform — https://www.terraform.io/
  6. Akamai Terraform provider — https://registry.terraform.io/providers/akamai/akamai/latest/docs
  7. Akamai EdgeWorkers — https://developer.akamai.com/akamai-edgeworkers-overview#resources
  8. Akamai CLI — https://developer.akamai.com/getting-started/cli
  9. Akamai CLI EdgeWorkers package — https://developer.akamai.com/cli/packages/edgeworkers.html

Originally published: May 6, 2021

1 Comment