正在加载,请稍候…

API Gateway Patterns: BFF, Aggregation, and Cross-Cutting Concerns

Design and implement API gateways. Learn Backend for Frontend (BFF) pattern, request aggregation, authentication, rate limiting, circuit breaking, and protocol translation.

API Gateway Patterns

What an API Gateway Does

Client Request
      |
      v
  API Gateway
   |   |   |   |
   |   |   |   +-- Rate Limiting
   |   |   +------ Authentication/Authorization
   |   +---------- Request/Response Transform
   +-------------- Routing, Load Balancing, Circuit Breaking
   |
   v
Microservices (User Service, Order Service, Product Service...)

Backend for Frontend (BFF) Pattern

// Web BFF: Aggregates data optimized for desktop web
app.get('/dashboard', authenticate, async (req, res) => {
  const userId = req.user.id;

  // Aggregate from multiple services in parallel
  const [user, orders, recommendations, notifications] = await Promise.all([
    userService.getProfile(userId),
    orderService.getRecentOrders(userId, { limit: 5 }),
    recommendationService.getForUser(userId, { limit: 6 }),
    notificationService.getUnread(userId),
  ]);

  // Shape data for web UI needs
  res.json({
    user: { name: user.name, avatar: user.avatar, plan: user.plan },
    recentOrders: orders.map(o => ({ id: o.id, status: o.status, total: o.total, date: o.createdAt })),
    recommendations: recommendations.map(r => ({ id: r.id, name: r.name, price: r.price, image: r.imageUrl })),
    unreadCount: notifications.filter(n => !n.read).length,
  });
});

// Mobile BFF: Less data, mobile-optimized fields
mobileBFF.get('/dashboard', authenticate, async (req, res) => {
  const [user, notifications] = await Promise.all([
    userService.getProfile(req.user.id),
    notificationService.getUnread(req.user.id),
  ]);

  // Mobile needs less data (bandwidth conscious)
  res.json({
    name: user.name,
    avatar: user.avatarSmall, // Smaller image
    unreadCount: notifications.length,
  });
});

Request Aggregation

// Aggregate multiple service calls into one API call
app.get('/products/:id/full', async (req, res) => {
  const productId = req.params.id;

  const [product, inventory, reviews, seller] = await Promise.allSettled([
    productService.getProduct(productId),
    inventoryService.getStock(productId),
    reviewService.getSummary(productId),
    sellerService.getSeller(productId),
  ]);

  // Handle partial failures gracefully
  res.json({
    product: product.status === 'fulfilled' ? product.value : null,
    inStock: inventory.status === 'fulfilled' ? inventory.value.quantity > 0 : null,
    rating: reviews.status === 'fulfilled' ? reviews.value.average : null,
    seller: seller.status === 'fulfilled' ? seller.value : null,
  });
});

Cross-Cutting Concerns

// Authentication middleware
const authenticate = async (req: Request, res: Response, next: NextFunction) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'Authentication required' });

  try {
    req.user = await verifyJWT(token);
    next();
  } catch {
    res.status(401).json({ error: 'Invalid token' });
  }
};

// Request ID for tracing
app.use((req, res, next) => {
  req.id = req.headers['x-request-id'] as string || uuid();
  res.setHeader('x-request-id', req.id);
  next();
});

// Timeout middleware
app.use((req, res, next) => {
  const timeout = setTimeout(() => {
    res.status(504).json({ error: 'Gateway Timeout' });
  }, 30000);

  res.on('finish', () => clearTimeout(timeout));
  next();
});

// Circuit breaker per downstream service
const orderCircuit = new CircuitBreaker({ failureThreshold: 5, timeout: 60000 });

app.get('/orders', authenticate, async (req, res) => {
  try {
    const orders = await orderCircuit.execute(() => orderService.list(req.user.id));
    res.json(orders);
  } catch (err) {
    if (err.message === 'Circuit breaker is open') {
      return res.status(503).json({ error: 'Service temporarily unavailable' });
    }
    throw err;
  }
});

Protocol Translation

// gRPC-to-REST gateway
import grpc from '@grpc/grpc-js';

const userServiceClient = new UserServiceClient('user-service:50051', grpc.credentials.createInsecure());

app.get('/users/:id', async (req, res) => {
  // REST request -> gRPC call -> REST response
  userServiceClient.getUser({ id: req.params.id }, (err, user) => {
    if (err) return res.status(err.code === 5 ? 404 : 500).json({ error: err.message });
    res.json({ id: user.id, name: user.name, email: user.email });
  });
});

The API Gateway is the front door to your microservices - keep it thin and focused on routing concerns.