AEM Edge 기능 개발

IMPORTANT
AEM Edge 함수는 현재 베타 버전입니다. 기능 및 설명서는 변경될 수 있습니다. 피드백을 받으려면 aemcs-edgecompute-feedback@adobe.com에 문의하십시오.

목표는 서드파티 API에서 동적 데이터를 가져오기 위해 AEM Edge 함수를 호출하는 동적 Edge Delivery Services 블록을 빌드하는 것입니다.

첫 번째 단계는 AEM Edge 함수를 개발하는 것입니다. 이 함수는 두 개의 업스트림 API 호출을 하나의 응답으로 결합하는 끝점을 노출하고 로컬 개발을 위해 CORS를 활성화합니다. 요청 및 응답 계약, 끝점 일치 및 아웃바운드 fetch() 기본 사항에 대해서는 Edge 함수를 사용하여 API 끝점 만들기를 참조하십시오. 이 자습서와 관련된 부분만 여기에서 다룹니다.

보일러판에서 시작

AEM Edge 함수 프로젝트(Edge Delivery Services에서 AEM Edge 함수 설정에서 복제됨)는 두 개의 샘플 경로 /hello-world/weather과(와) 함께 제공되므로 첫 날에 테스트할 수 있는 작업이 있습니다. 둘 다 실제 프로젝트에 속하지 않으므로 먼저 보일러판을 검토하고 필요하지 않은 항목을 제거하십시오.

API 계약 정의

핸들러를 작성하기 전에 요청 및 응답 셰이프를 정의하면 구현을 읽지 않고도 Edge Delivery Services 블록 또는 다른 호출자를 계약에 따라 작성할 수 있습니다.

끝점: GET /api/frescopa/estimated-delivery

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

postcode은(는) 필수입니다. sku은(는) 선택 사항이며 기본값은 house-blend-medium-roast입니다.

응답 본문:

성공적인 응답은 다음 형태를 반환합니다.

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

오류 응답은 각 실패 이유에 대해 다른 code을(를) 사용하여 이 셰이프를 공유합니다.

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

모든 오류 본문에는 code 필드가 포함되어 있으므로 호출자는 message을(를) 구문 분석하는 대신 실패 이유를 분기할 수 있습니다. OPTIONS 요청은 아래 로컬 개발용 CORS 활성화에서 다루는 CORS Preflight에 대한 별도의 응답을 받습니다.

비즈니스 로직 구현

index.js을(를) 라우팅만 제한합니다. 비즈니스 논리는 src/handlers/ 아래에 있으며 끝점당 하나의 폴더입니다.

추가할 파일

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

핸들러 및 업스트림 호출

handler.js의 처리기가 skupostcode을(를) 가져와서 두 업스트림 서비스를 호출하고 결과를 하나의 응답으로 병합합니다.

// 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은(는) 오류 및 성공 JSON 셰이프를 핸들러에서 유지하고 constants.js은(는) 경로 경로 경로 및 기본 SKU를 유지하므로 handler.js은(는) 두 호출 시퀀스에 초점을 맞추고 있습니다. src/mocks/의 각 모형은 데이터를 반환하기 전에 자체 토큰을 로드하며, 실제 API 클라이언트가 사용하는 것과 동일한 형태입니다.

// 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()authorizedFetch()이(가) lib/api-client.js에 있습니다. product-api.jsdelivery-api.js은(는) 모두 이 도우미를 공유하므로 각 업스트림 API는 인증 논리를 복제하지 않고 자체 비밀 저장소 키(CATALOG_API_TOKEN, DELIVERY_API_TOKEN)를 유지합니다. 시뮬레이션된 호출을 실제 API로 바꿀 준비가 되면 배포된 사이트에서 암호 설정을 위해 구성 및 암호를 Edge 함수와 함께 사용을 수행한 다음 위의 블록의 주석을 제거하십시오.

각 플랫폼은 단일 호출을 32개의 아웃바운드 가져오기 호출​로 제한하므로 여기에 두 개의 호출이 있으면 여유 공간이 충분합니다.

로컬 개발용 CORS 활성화

Edge Delivery Services 블록의 로컬 개발 서버가 http://localhost:3000에서 실행됩니다. AEM Edge 함수의 로컬 개발 서버가 http://127.0.0.1:7676에서 실행됩니다. 이러한 요청은 두 개의 서로 다른 출처이므로 CORS 헤더가 없으면 브라우저에서 코드가 요청을 보기 전에 차단합니다.

cors.js의 허용 목록에 대해 요청 원본을 확인하고 브라우저에서 먼저 보내는 OPTIONS Preflight 요청에 동일한 검사를 적용합니다.

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

참조 구현은 CDN을 통해 배포된 개발, 스테이지 및 프로덕션 도메인에 대한 요청이 동일한 원본이고 기술적으로 CORS 헤더가 필요하지 않으므로 해당 도메인에 대한 정규식을 사용하여 ALLOWED_ORIGIN_EXACT을(를) 확장합니다. 하지만 CORS 헤더가 명시적으로 일치하면 허용 목록이 자동으로 문서화됩니다. 배포되면 브라우저는 동일 원본 요청에 대한 CORS 검사를 보내지 않으므로 추가 헤더는 문제가 없습니다. CORS 도우미를 그대로 둘 수 있습니다. 요청의 원본이 허용 목록과 일치하는 경우에만 헤더를 추가합니다.

fastly.toml에서 로컬 개발 서버 구성

fastly.toml은(는) aio aem edge-functions serve에 의해 시작된 로컬 개발 서버를 구성합니다. 이 자습서의 경우 해당 [local_server.secret_stores] 블록은 각 업스트림 API에 로컬 토큰을 제공하므로 핸들러가 실제 자격 증명 없이 SecretStoreManager.getSecret()을(를) 호출할 수 있습니다.

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

또한 보일러플레이트는 날씨 샘플의 Open-Meteo API에 대한 [local_server.backends] 항목도 선언합니다. 이 자습서에서는 실제 호스트를 호출하는 대신 업스트림 호출을 모두 시뮬레이트하므로 위치를 다시 지정하지 말고 해당 항목을 제거하십시오.

[local_server] 옵션의 전체 집합을 보려면 Fastly.toml 참조를 참조하십시오.

로컬에서 끝점 테스트

이 시점에서는 API 계약만 테스트합니다. 나중에 호출되는 Edge Delivery Services 블록과 관계없이 끝점의 요청 및 응답 셰이프입니다.

Edge Delivery Services에서 AEM Edge 기능 설정에서 로컬 개발 서버를 시작합니다.

$ aio aem edge-functions serve

curl(으)로 행복한 경로를 호출하거나 브라우저에서 URL을 엽니다.

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

응답이 두 업스트림 호출의 데이터를 병합하는지 확인합니다(예: 동일한 JSON 본문에 있는 카탈로그 조회의 제품 이름과 이행 조회의 게재 예상).

예상 배달 끝점에 응답하는 로컬 개발 서버

그런 다음 오류 경로 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"

각 JSON은 제네릭 500이 아닌 code 필드(MISSING_POSTCODE, UNKNOWN_SKU)를 사용하여 JSON 오류 본문을 반환해야 하며, 처리기가 업스트림 호출에 도달하기 전에 입력의 유효성을 검사하는지 확인합니다.

끝점 추가

두 구성 파일이 API 끝점 빌드의 패턴을 따라 경로를 선언합니다.

config/edgeFunctions.yaml은(는) AEM Edge 함수 자체를 선언합니다. 표준 접시에서 변경되지 않습니다.

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

config/cdn.yaml은(는) 이 자습서의 경로를 해당 함수에 전달하는 원본 선택기 규칙을 추가합니다.

# 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

참조 구현에서 전체 파일을 확인합니다. config/edgeFunctions.yamlconfig/cdn.yaml.

다음 단계

Edge Delivery Services 블록 개발에서 이 끝점을 호출하는 블록을 스캐폴드한 다음 유니버설 편집기에서 작성합니다.

추가 리소스

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