mcpField
C
commerce-api-gateway

commerce-api-gateway

github.com/finsilabs开发6072 次安装5 天前更新
代码仓库

关于 commerce-api-gateway

面向定制化无头电商前端的技能,介绍如何构建 GraphQL Federation 网关或 REST BFF,并在网关统一处理认证、限流、缓存和可观测性。

主要功能

  1. 01配置 Apollo Router 联邦网关
  2. 02使用 Fastify 构建 REST BFF
  3. 03在网关集中处理认证
  4. 04设置限流与响应缓存
  5. 05添加 OpenTelemetry 分布式追踪

适用场景

  1. 01聚合商品、库存与 CMS API
  2. 02统一验证电商 API 的身份
  3. 03减少页面加载时的多次后端请求

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill commerce-api-gateway

安装后在 Claude Code 中输入/commerce-api-gateway确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/apollo-federation-schema-and-router-conf/
criteria.json
task.md
evals/observability-dataloader-and-api-version/
criteria.json
task.md
evals/rest-bff-with-rate-limiting-and-caching/
criteria.json
task.md

详细介绍

Commerce API 网关

概述

API 网关位于店面客户端与一组后端 Commerce 服务之间,为身份验证、速率限制、缓存和请求路由提供统一入口。在可组合 Commerce 架构中,网关会聚合来自不同服务(目录、购物车、搜索、CMS)的 API,让前端只需发起一次或少数几次调用,而不必发起数十次调用。本技能涵盖使用 Apollo Router 构建 GraphQL Federation 网关、REST 聚合 BFF,以及在网关层处理横切关注点(身份验证、速率限制、可观测性)。

何时使用此技能

  • 当你的店面在每次页面加载时需要向不同服务发起 10 次以上 API 调用
  • 当你需要在所有 Commerce API 中统一实施身份验证和授权
  • 当不同团队分别负责不同服务,而你需要在前端和后端之间建立契约
  • 当你希望实施速率限制、熔断或缓存,而不修改每个服务
  • 当你需要一个涵盖目录、库存、CMS 和个性化数据的统一 GraphQL 架构

前置条件和平台说明

此技能适用于自定义/无头店面(Node.js、Python 或类似后端)。代码示例使用 TypeScript/Node.js,可适配任何技术栈。

Shopify:Shopify Hydrogen 是 Shopify 的无头框架。使用 Shopify 作为 Commerce 后端并搭配自定义前端,或将 Shopify 与其他同类最佳服务混合使用时,可采用 MACH/可组合模式。 WooCommerce:WooCommerce 可通过其 REST API 和 WPGraphQL 作为无头后端。当你将前端与 WordPress 解耦时,可采用这些模式。 Magento:Magento 的 GraphQL API 和 PWA Studio 支持无头架构。在 MACH 技术栈中将 Magento 用作后端服务时,可采用这些可组合模式。

你需要:

  • Node.js 18+(或改用你的后端语言)
  • 用于缓存/队列的 Redis

核心说明

  1. 设置用于 GraphQL Federation 的 Apollo Router

    Apollo Federation 将多个 GraphQL 子图组合成一个统一的超图。每项服务负责架构中的一个部分。

    # Install Apollo Router (the high-performance Rust gateway)
    curl -sSL https://router.apollo.dev/download/nix/latest | sh
    
    # Install Rover CLI for schema management
    npm install -g @apollo/rover
    

    定义超图配置:

    # supergraph.yaml
    federation_version: =2.5.0
    subgraphs:
      catalog:
        routing_url: http://catalog-service:4001/graphql
        schema:
          subgraph_url: http://catalog-service:4001/graphql
      inventory:
        routing_url: http://inventory-service:4002/graphql
        schema:
          subgraph_url: http://inventory-service:4002/graphql
      cart:
        routing_url: http://cart-service:4003/graphql
        schema:
          subgraph_url: http://cart-service:4003/graphql
    

    组合并运行:

    rover supergraph compose --config supergraph.yaml > supergraph.graphql
    ./router --supergraph supergraph.graphql --config router.yaml
    
  2. 定义 Federation 子图架构

    每项服务定义自己负责的架构部分,并可扩展其他服务拥有的类型:

    # catalog-service/schema.graphql
    type Query {
      product(id: ID!): Product
      products(first: Int, after: String): ProductConnection
    }
    
    type Product @key(fields: "id") {
      id: ID!
      name: String!
      slug: String!
      description: String
      price: Money!
      images: [Image!]!
    }
    
    # inventory-service/schema.graphql — extends Product from catalog
    type Product @key(fields: "id") @extends {
      id: ID! @external
      inventory: InventoryStatus!
    }
    
    type InventoryStatus {
      available: Boolean!
      quantity: Int
      warehouseLocations: [String!]!
    }
    

    网关会通过调用库存服务,并传入从目录响应中收集的产品 ID,来解析 Product.inventory 字段;这一过程会自动完成,无需客户端编排。

  3. 使用 Fastify 构建 REST BFF(面向前端的后端)

    对于倾向使用 REST 而非 GraphQL 的店面:

    import Fastify from 'fastify';
    import {catalogClient} from './services/catalog';
    import {inventoryClient} from './services/inventory';
    import {cmsClient} from './services/cms';
    
    const app = Fastify({logger: true});
    
    // Composite endpoint for Product Detail Page
    app.get<{Params: {id: string}}>('/api/pdp/:id', async (request, reply) => {
      const {id} = request.params;
      const customerId = request.headers['x-customer-id'] as string | undefined;
    
      const [product, inventory, content] = await Promise.allSettled([
        catalogClient.getProduct(id),
        inventoryClient.getStock(id),
        cmsClient.getProductContent(id),
      ]);
    
      if (product.status === 'rejected') {
        return reply.status(404).send({error: 'Product not found'});
      }
    
      return {
        product: product.value,
        inventory: inventory.status === 'fulfilled' ? inventory.value : {available: true, quantity: null},
        content: content.status === 'fulfilled' ? content.value : null,
      };
    });
    
    // Composite endpoint for Cart Page
    app.get<{Params: {cartId: string}}>('/api/cart/:cartId', {
      preHandler: [requireAuth],
    }, async (request, reply) => {
      const cart = await cartClient.getCart(request.params.cartId);
      const productIds = cart.lines.map((l: any) => l.productId);
      const inventoryMap = await inventoryClient.getBulkStock(productIds);
    
      return {
        ...cart,
        lines: cart.lines.map((line: any) => ({
          ...line,
          inventory: inventoryMap[line.productId] ?? {available: true},
        })),
      };
    });
    
    await app.listen({port: 3000, host: '0.0.0.0'});
    
  4. 在网关层实施身份验证

    网关会验证 JWT,并将解码后的身份转发给子图:

    // middleware/auth.ts
    import {FastifyRequest, FastifyReply} from 'fastify';
    import {verify, JwtPayload} from 'jsonwebtoken';
    
    export async function requireAuth(request: FastifyRequest, reply: FastifyReply) {
      const token = request.headers.authorization?.replace('Bearer ', '');
      if (!token) return reply.status(401).send({error: 'Authentication required'});
    
      try {
        const payload = verify(token, process.env.JWT_PUBLIC_KEY!, {algorithms: ['RS256']}) as JwtPayload;
        request.user = {id: payload.sub!, email: payload.email, roles: payload.roles ?? []};
      } catch {
        return reply.status(401).send({error: 'Invalid token'});
      }
    }
    
    // Apollo Router auth via coprocessor (Rust plugin alternative)
    // router.yaml
    
    # router.yaml
    authentication:
      router:
        jwt:
          jwks:
            - url: https://your-auth-provider/.well-known/jwks.json
    
    authorization:
      require_authentication: false  # Allow public queries; subgraphs enforce per-field auth
    
  5. 实施速率限制和响应缓存

    // Rate limiting with Redis token bucket
    import {RateLimiterRedis} from 'rate-limiter-flexible';
    import Redis from 'ioredis';
    
    const redis = new Redis(process.env.REDIS_URL!);
    
    const rateLimiter = new RateLimiterRedis({
      storeClient: redis,
      keyPrefix: 'rl_gateway',
      points: 100,       // 100 requests
      duration: 60,      // per 60 seconds
      blockDuration: 60, // block for 60s when exceeded
    });
    
    app.addHook('preHandler', async (request, reply) => {
      const key = request.user?.id ?? request.ip;
      try {
        await rateLimiter.consume(key);
      } catch {
        reply.header('Retry-After', '60');
        return reply.status(429).send({error: 'Too many requests'});
      }
    });
    
    // Response caching with stale-while-revalidate
    import {fastifyCaching} from '@fastify/caching';
    app.register(fastifyCaching, {privacy: fastifyCaching.privacy.PUBLIC, expiresIn: 60});
    
  6. 使用 OpenTelemetry 添加分布式追踪

    // tracing.ts — initialize before importing app modules
    import {NodeSDK} from '@opentelemetry/sdk-node';
    import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-http';
    import {HttpInstrumentation} from '@opentelemetry/instrumentation-http';
    
    const sdk = new NodeSDK({
      serviceName: 'commerce-gateway',
      traceExporter: new OTLPTraceExporter({url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT}),
      instrumentations: [new HttpInstrumentation()],
    });
    sdk.start();
    
    // Propagate trace context to downstream services
    // OpenTelemetry auto-injects W3C traceparent headers into all outgoing HTTP requests
    

示例

配置带缓存和 CORS 的 Apollo Router
# router.yaml
server:
  listen: 0.0.0.0:4000
  cors:
    origins:
      - https://www.mystore.com
      - https://staging.mystore.com

supergraph:
  listen: 0.0.0.0:4000

traffic_shaping:
  router:
    timeout: 30s
  all:
    timeout: 10s
    retry:
      enabled: true
      min_per_sec: 10
      ttl: 10s
      retry_on_http_statuses: [500, 502, 503]

telemetry:
  tracing:
    otlp:
      endpoint: http://otel-collector:4317
      protocol: grpc
为旧版 REST 服务拼接架构
import {wrapSchema, RenameTypes, FilterTypes} from '@graphql-tools/wrap';
import {fetch} from 'undici';

// Wrap a legacy REST API as a virtual GraphQL subgraph
const legacyProductSchema = buildSchema(`
  type Query {
    legacyProduct(sku: String!): LegacyProduct
  }
  type LegacyProduct {
    sku: String!
    name: String!
    wholesalePrice: Float!
  }
`);

const legacyProductSubschema = {
  schema: legacyProductSchema,
  executor: async ({document, variables}: any) => {
    const sku = variables.sku;
    const res = await fetch(`${process.env.LEGACY_API_URL}/products/${sku}`);
    const product = await res.json();
    return {data: {legacyProduct: product}};
  },
};

最佳实践

  • 保持网关精简 —— 网关处理横切关注点(身份验证、速率限制、缓存、追踪);业务逻辑应放在服务中
  • GraphQL 使用 Apollo Federation —— 架构拼接虽然可行,但 Federation 才是标准做法;它能明确所有权,并支持在 CI 中进行架构注册表检查
  • 为每项服务设置独立超时 —— 响应缓慢的库存服务不应拖慢商品页面;为每个子图设置独立超时,并对非关键数据使用 @defer
  • 在网关而非客户端缓存 —— 使用 Cache-Control 和 Fastify/Apollo 缓存插件,将重复查询的结果从内存中返回;这可以降低所有下游服务的负载
  • 为网关 API 而非子图设置版本 —— 在网关层公开 /api/v1 和 /api/v2 前缀;各个子图的架构可在 Federation 下独立演进
  • 为每个子图使用健康检查端点 —— 配置路由器轮询每项服务的 /health,并自动将不健康的子图从路由中移除
  • 将网关延迟作为系统级 SLO 进行监控 —— 网关的 p99 延迟决定了每次用户交互的延迟上限;当延迟超过页面性能预算时发出告警

常见问题

问题解决方案
解析实体引用时出现 N+1 查询使用 Apollo Federation 的 @key 和批量解析(DataLoader 模式),每项服务通过一次调用获取所有被引用的实体
身份验证令牌未转发到子图配置 Apollo Router 的 headers.all 传播,将 Authorization 和 x-customer-id 标头转发到所有子图
网关在高负载下成为瓶颈将 Apollo Router 部署为负载均衡器后方、可水平扩展的无状态服务;它采用 Rust 核心,每个实例可处理数千 RPS
子图架构冲突阻碍部署在 CI 中使用 rover subgraph check,在合并前根据注册表验证架构变更
来自 SPA 的跨源请求被阻止将 CORS 来源明确设置为你的店面域名;经过身份验证的网关绝不要使用通配符 *

相关技能

  • @composable-commerce
  • @webhook-architecture
  • @monitoring-alerting-commerce
  • @edge-commerce
  • @load-testing-commerce

评论

支持 Markdown