Sviluppare la funzione Edge di AEM

IMPORTANT
AEM Edge Functions è attualmente in versione beta. Le funzioni e la documentazione potrebbero cambiare. Per ricevere commenti, contattare aemcs-edgecompute-feedback@adobe.com.

Il nostro obiettivo è generare un blocco dinamico di Edge Delivery Services che chiama una funzione Edge di AEM per recuperare dati dinamici da un’API di terze parti.

Il primo passaggio consiste nello sviluppare la funzione Edge di AEM, che espone un endpoint che combina due chiamate API a monte in un’unica risposta e abilita CORS per lo sviluppo locale. Per informazioni di base sul contratto di richiesta e risposta, sulla corrispondenza dell’endpoint e su fetch() in uscita, vedere Creare un endpoint API con funzioni Edge. Qui vengono trattate solo le parti specifiche di questa esercitazione.

Inizia dalla piastra

Il tuo progetto AEM Edge Functions (clonato in Configurare AEM Edge Functions su Edge Delivery Services) viene fornito con due route di esempio, /hello-world e /weather, quindi hai qualcosa da testare il primo giorno. Nessuno dei due appartiene a un progetto reale, quindi controlla prima la piastra e rimuovi ciò che non ti serve.

Definire il contratto API

Prima di scrivere l’handler, definisci la forma di richiesta e risposta in modo che il blocco Edge Delivery Services o qualsiasi altro chiamante possa essere creato in base al contratto senza leggere l’implementazione.

Endpoint: GET /api/frescopa/estimated-delivery

GET /api/frescopa/estimated-delivery?sku=house-blend-medium-roast&postcode=90210

postcode è obbligatorio. sku è facoltativo. Impostazione predefinita: house-blend-medium-roast.

Corpo risposta:

In caso di esito positivo, la risposta restituisce questa forma:

// 200 OK
{
  "sku": "house-blend-medium-roast",
  "productName": "House Blend - Medium Roast",
  "inventoryStatus": "in-stock", // or "low-stock", "out-of-stock"
  "qtyLeft": 12,
  "fulfillmentRegion": "West Coast Fulfillment",
  "deliveryEta": "tomorrow",
  "cutoffMessage": "Order by 2:00 PM for same-day dispatch.",
  "message": "Arrives tomorrow in West Coast Fulfillment"
}

Le risposte di errore condividono questa forma, con un code diverso per ogni motivo di errore:

// 400 missing postcode, 404 unknown sku, 405 wrong method: same shape, different code
{
  "error": "Missing required parameter",
  "code": "MISSING_POSTCODE", // or "UNKNOWN_SKU", "METHOD_NOT_ALLOWED"
  "message": "The postcode query parameter is required"
}

Ogni corpo dell’errore include un campo code, pertanto un chiamante può diramarsi in base al motivo dell’errore invece di analizzare message. OPTIONS richieste ottengono una risposta separata per la verifica preliminare CORS, descritta in Abilitare CORS per l’ambiente di sviluppo locale di seguito.

Implementare la logica di business

Mantieni index.js limitato al routing. La logica di business si trova in src/handlers/, una cartella per endpoint.

File da aggiungere

src/
├── index.js                              # routes to a handler, nothing else
├── handlers/estimated-delivery/
│   ├── handler.js                        # orchestrates the two upstream calls
│   ├── responses.js                      # builds the success and error JSON payloads
│   └── constants.js                      # route path and query-param defaults
├── lib/
│   ├── api-client.js                     # shared per-API token loading
│   └── cors.js                           # origin allow-list, applied to every response
└── mocks/                                # tutorial stand-ins for the two upstream APIs
    ├── catalog/product-api.js
    └── delivery/delivery-api.js

Gestore e chiamate a monte

Il gestore in handler.js accetta un sku e un postcode, chiama i due servizi upstream e unisce i risultati in un’unica risposta:

// src/handlers/estimated-delivery/handler.js
async function estimatedDeliveryBySkuAndPostcodeHandler(req) {
  const url = new URL(req.url);
  const sku = (url.searchParams.get("sku") ?? DEFAULT_SKU).trim();
  const postcode = (url.searchParams.get("postcode") ?? "").trim();

  if (!postcode) {
    return missingPostcodeError(sku);
  }

  // API 1: catalog lookup
  const product = await getProductBySku(sku);
  if (!product) {
    return unknownSkuError(sku, postcode);
  }

  // API 2: fulfillment lookup
  const fulfillment = await getEstimatedDeliveryByPostcode(postcode);

  return json(buildSuccessResponse(product, fulfillment));
}

responses.js mantiene le forme JSON di errore e di successo fuori dal gestore e constants.js contiene il percorso di route e lo SKU predefinito, quindi handler.js rimane concentrato sulla sequenza di due chiamate. Ogni mock in src/mocks/ carica il proprio token prima di restituire i dati, la stessa forma utilizzata da un client API reale:

// src/mocks/catalog/product-api.js
export async function getProductBySku(sku) {
  await getApiToken(SECRETS.CATALOG);

  // Real API: uncomment and replace with your catalog endpoint.
  // const response = await authorizedFetch(
  //   `https://api.example.com/products?sku=${encodeURIComponent(sku)}`,
  //   SECRETS.CATALOG,
  // );
  // return response.ok ? response.json() : null;

  return PRODUCTS[sku] || null;
}

getApiToken() e authorizedFetch() vivono in lib/api-client.js. Sia product-api.js che delivery-api.js condividono questo helper, pertanto ogni API upstream mantiene la propria chiave dell’archivio segreto (CATALOG_API_TOKEN, DELIVERY_API_TOKEN) senza duplicare la logica di autenticazione. Quando sei pronto a sostituire una chiamata simulata con un’API reale, segui Utilizza configurazioni e segreti con funzioni Edge per l’installazione segreta su un sito distribuito, quindi rimuovi il commento dal blocco precedente.

Ogni piattaforma limita una singola chiamata a 32 chiamate di recupero in uscita, pertanto due chiamate lasciano molto spazio di crescita.

Abilita CORS per sviluppo locale

Il server di sviluppo locale del blocco Edge Delivery Services viene eseguito su http://localhost:3000. Il server di sviluppo locale della funzione Edge di AEM viene eseguito su http://127.0.0.1:7676. Queste sono due origini diverse, quindi senza intestazioni CORS, il browser blocca la richiesta prima che il codice la veda mai.

Verificare l’origine della richiesta rispetto a un elenco Consentiti in cors.js e applicare lo stesso controllo alla richiesta di verifica preliminare OPTIONS inviata dal browser:

// src/lib/cors.js
const ALLOWED_ORIGIN_EXACT = new Set([
  "http://localhost:3000",
  "http://127.0.0.1:3000",
]);

function isAllowedOrigin(origin) {
  return Boolean(origin) && ALLOWED_ORIGIN_EXACT.has(origin);
}

function applyCors(request, response) {
  const origin = request.headers.get("Origin");
  if (!isAllowedOrigin(origin)) {
    return response;
  }

  const headers = new Headers(response.headers);
  headers.set("access-control-allow-origin", origin);
  headers.set("vary", "Origin");
  return new Response(response.body, { status: response.status, headers });
}

function corsPreflightResponse(request) {
  const origin = request.headers.get("Origin");
  if (!isAllowedOrigin(origin)) {
    return new Response(null, { status: 403 });
  }

  return new Response(null, {
    status: 204,
    headers: {
      "access-control-allow-origin": origin,
      "access-control-allow-methods": "GET, OPTIONS",
      "access-control-allow-headers": "Content-Type",
      vary: "Origin",
    },
  });
}

export { applyCors, corsPreflightResponse };
// src/index.js
if (url.pathname === "/api/frescopa/estimated-delivery") {
  if (req.method === "OPTIONS") {
    finalResponse = corsPreflightResponse(req);
  } else if (req.method === "GET") {
    finalResponse = await estimatedDeliveryBySkuAndPostcodeHandler(req);
  }
}

finalResponse = applyCors(req, finalResponse);

L’implementazione di riferimento estende ALLOWED_ORIGIN_EXACT con un regex per consentire anche i domini di sviluppo, stage e produzione distribuiti, poiché una richiesta a tali domini ha la stessa origine tramite la rete CDN e tecnicamente non ha bisogno di un’intestazione CORS, ma la corrispondenza con essi rende esplicitamente l’elenco Consentiti autocertificante. Una volta implementato, il browser non invia mai un controllo CORS per una richiesta della stessa origine, pertanto l’intestazione aggiuntiva non è pericolosa. Puoi lasciare attivo l’helper CORS, che aggiunge un’intestazione solo quando l’origine della richiesta corrisponde all’elenco Consentiti.

Configurare il server di sviluppo locale in fastly.toml

fastly.toml configura il server di sviluppo locale avviato da aio aem edge-functions serve. Per questo tutorial, il blocco [local_server.secret_stores] assegna a ciascuna API upstream un token locale, in modo che il gestore possa chiamare SecretStoreManager.getSecret() senza credenziali reali:

# fastly.toml
[local_server.secret_stores]
  [[local_server.secret_stores.secret_default]]
    key = "CATALOG_API_TOKEN"
    data = "catalog-tutorial-token"
  [[local_server.secret_stores.secret_default]]
    key = "DELIVERY_API_TOKEN"
    data = "delivery-tutorial-token"

Il boilerplate dichiara anche una voce [local_server.backends] per l’API Open-Meteo del campione meteo. Poiché questa esercitazione simula entrambe le chiamate a monte invece di chiamare un host reale, rimuovi tale voce invece di rimandarla.

Per il set completo di [local_server] opzioni, vedi il riferimento a Fastly.toml.

Test locale dell’endpoint

A questo punto stai testando solo il contratto API: la forma di richiesta e risposta dell’endpoint, indipendente dal blocco Edge Delivery Services che lo chiamerà in seguito.

Avviare il server di sviluppo locale da Configurare le funzioni Edge di AEM in Edge Delivery Services:

$ aio aem edge-functions serve

Chiama il percorso felice con curl o apri l’URL in un browser:

$ curl "http://127.0.0.1:7676/api/frescopa/estimated-delivery?sku=house-blend-medium-roast&postcode=90210"

Conferma la risposta unisce i dati di entrambe le chiamate a monte, ad esempio un nome di prodotto dalla ricerca catalogo e una stima di consegna dalla ricerca di evasione nello stesso corpo JSON.

Server di sviluppo locale che risponde all'endpoint di consegna stimato

Quindi confermare i percorsi di errore responses.js build:

# missing postcode
$ curl "http://127.0.0.1:7676/api/frescopa/estimated-delivery?sku=house-blend-medium-roast"

# unknown SKU
$ curl "http://127.0.0.1:7676/api/frescopa/estimated-delivery?sku=not-a-real-product&postcode=90210"

Ogni deve restituire un corpo di errore JSON con un campo code (MISSING_POSTCODE, UNKNOWN_SKU) anziché un generico 500, confermando che il gestore convalida l’input prima che raggiunga le chiamate a monte.

Aggiungi l’endpoint

Due file di configurazione dichiarano la route, seguendo il pattern in Creare un endpoint API.

config/edgeFunctions.yaml dichiara la funzione Edge di AEM stessa, invariata rispetto alla boilerplate:

# config/edgeFunctions.yaml
kind: "EdgeFunctions"
version: "1"
data:
  functions:
    - name: my-edge-function

config/cdn.yaml aggiunge una regola del selettore di origine che inoltra il percorso di questa esercitazione a tale funzione:

# config/cdn.yaml
- name: route-estimated-delivery-to-edge-function
  when: { reqProperty: path, equals: "/api/frescopa/estimated-delivery" }
  action:
    type: selectAemOrigin
    originName: edgefunction-my-edge-function
    skipCache: true

Vedere i file completi nell’implementazione di riferimento: config/edgeFunctions.yaml e config/cdn.yaml.

Passaggi successivi

In Sviluppa il blocco Edge Delivery Services, scaffold il blocco che chiama questo endpoint, quindi crealo in Universal Editor.

Risorse aggiuntive

recommendation-more-help
experience-manager-learn-help-cloud-service