Desarrollar la función Edge de AEM

IMPORTANT
Funciones de AEM Edge se encuentra en la versión beta. Las funciones y la documentación pueden cambiar. Para obtener comentarios, comuníquese con aemcs-edgecompute-feedback@adobe.com.

Nuestro objetivo es crear un bloque de Edge Delivery Services dinámico que llame a una función de Edge de AEM para recuperar datos dinámicos de una API de terceros.

El primer paso es desarrollar la función Edge de AEM, que expone un extremo que combina dos llamadas de API ascendentes en una respuesta y permite el desarrollo local de CORS. Para conocer los conceptos básicos del contrato de solicitud y respuesta, la coincidencia de extremos y fetch() de salida, consulte Generar un extremo de API con funciones de Edge. Aquí solo se tratan las partes específicas de este tutorial.

Empiece desde la plantilla

Su proyecto de funciones Edge de AEM (clonado en Configurar funciones Edge de AEM en Edge Delivery Services) se enviará con dos rutas de ejemplo, /hello-world y /weather, por lo que tendrá algo que probar el primer día. Ninguna de las dos pertenece a un proyecto real, así que revise primero la plantilla y elimine lo que no necesite.

Definición del contrato de API

Antes de escribir el controlador, defina la forma de solicitud y respuesta, de modo que el bloque de Edge Delivery Services, o cualquier otro llamador, se pueda crear con respecto al contrato sin leer la implementación.

Punto final: GET /api/frescopa/estimated-delivery

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

Se requiere postcode. sku es opcional y el valor predeterminado es house-blend-medium-roast.

Cuerpo de respuesta:

Una respuesta correcta devuelve esta 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"
}

Las respuestas de error comparten esta forma, con un code diferente por cada motivo de error:

// 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"
}

Cada cuerpo de error incluye un campo code, de modo que el llamador puede bifurcar el motivo del error en lugar de analizar message. OPTIONS solicitudes obtienen una respuesta por separado para la comprobación preliminar CORS, cubierta en Habilitar CORS para el desarrollo local a continuación.

Implementación de la lógica empresarial

Mantener index.js limitado al enrutamiento. La lógica empresarial se encuentra en src/handlers/, una carpeta por extremo.

Archivos que agregará

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

Llamadas del controlador y del flujo ascendente

El controlador de handler.js toma un sku y un postcode, llama a los dos servicios de flujo ascendente y combina sus resultados en una respuesta:

// 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 las formas JSON de error y de éxito fuera del controlador, y constants.js guarda la ruta de acceso y el SKU predeterminado, de modo que handler.js permanece centrado en la secuencia de dos llamadas. Cada simulación de src/mocks/ carga su propio token antes de devolver los datos, con la misma forma que usaría un cliente de API real:

// 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() y authorizedFetch() viven en lib/api-client.js. Tanto product-api.js como delivery-api.js comparten este asistente, por lo que cada API de flujo ascendente mantiene su propia clave de almacén secreto (CATALOG_API_TOKEN, DELIVERY_API_TOKEN) sin duplicar la lógica de autenticación. Cuando esté listo para reemplazar una llamada simulada con una API real, siga Use configuraciones y secretos con funciones de Edge para la configuración secreta en un sitio implementado y luego quite los comentarios del bloque anterior.

Cada plataforma limita una sola invocación a 32 llamadas de captura salientes, por lo que dos llamadas aquí dejan mucho margen de ampliación.

Habilitar CORS para desarrollo local

El servidor de desarrollo local del bloque de Edge Delivery Services se ejecuta en http://localhost:3000. El servidor de desarrollo local de la función AEM Edge se ejecuta en http://127.0.0.1:7676. Son dos orígenes diferentes, por lo que sin encabezados CORS, el explorador bloquea la solicitud antes de que el código la vea.

Compruebe el origen de la solicitud en una lista de permitidos de cors.js y aplique la misma comprobación a la solicitud de comprobación preliminar de OPTIONS que envía primero el explorador:

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

La implementación de referencia amplía ALLOWED_ORIGIN_EXACT con una regex para permitir también los dominios de desarrollo, fase y producción implementados, ya que una solicitud a esos dominios es del mismo origen a través de la CDN y técnicamente no necesita un encabezado CORS, pero al hacerlos coincidir explícitamente la lista de permitidos se documenta automáticamente. Una vez implementado, el explorador nunca envía una comprobación CORS para una solicitud del mismo origen, por lo que el encabezado adicional es inofensivo. Puede dejar el asistente de CORS en su lugar; solo agrega un encabezado cuando el origen de la solicitud coincide con la lista de permitidos.

Configurar el servidor de desarrollo local en fastly.toml

fastly.toml configura el servidor de desarrollo local iniciado por aio aem edge-functions serve. Para este tutorial, su bloque [local_server.secret_stores] proporciona a cada API de flujo ascendente un token local, de modo que el controlador puede llamar a SecretStoreManager.getSecret() sin credenciales reales:

# 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"

La plantilla también declara una entrada [local_server.backends] para la API Open-Meteo de la muestra del tiempo. Dado que este tutorial simula ambas llamadas de subida en lugar de llamar a un host real, elimine esa entrada en lugar de volver a señalarla.

Para ver el conjunto completo de [local_server] opciones, consulte la referencia de Fastly.toml.

Probar el extremo localmente

En este punto solo está probando el contrato de API: la forma de solicitud y respuesta del extremo, independiente del bloque de Edge Delivery Services que lo llamará más adelante.

Inicie el servidor de desarrollo local desde Configurar funciones de AEM Edge en Edge Delivery Services:

$ aio aem edge-functions serve

Llame a la ruta de acceso correcta con curl o abra la dirección URL en un explorador:

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

Confirme que la respuesta combina los datos de ambas llamadas de subida, por ejemplo, un nombre de producto de la búsqueda en el catálogo y una estimación de entrega de la búsqueda de satisfacción de pedidos en el mismo cuerpo JSON.

Servidor de desarrollo local que responde al extremo de envío estimado

A continuación, confirme las rutas de error de las compilaciones de responses.js:

# 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"

Cada uno debe devolver un cuerpo de error JSON con un campo code (MISSING_POSTCODE, UNKNOWN_SKU) en lugar de un 500 genérico, lo que confirma que el controlador valida la entrada antes de que llegue a las llamadas de flujo ascendente.

Añadir el punto final

Dos archivos de configuración declaran la ruta, siguiendo el patrón de Generar un extremo de API.

config/edgeFunctions.yaml declara la función Edge de AEM en sí, sin cambios con respecto a la plantilla:

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

config/cdn.yaml agrega una regla de selector de origen que reenvía la ruta de este tutorial a esa función:

# 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

Ver los archivos completos en la implementación de referencia: config/edgeFunctions.yaml y config/cdn.yaml.

Próximos pasos

En Desarrollo del bloque de Edge Delivery Services, se crea un andamio del bloque que llama a este punto de conexión y, a continuación, se crea en el editor universal.

Recursos adicionales

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