正在加载,请稍候…

API Gateway Patterns: Rate Limiting, Auth at the Edge, and Request Aggregation

Design and implement API gateway patterns. Token bucket rate limiting, JWT validation, request aggregation, service discovery, and Kong configuration.

The API gateway handles auth, rate limiting, routing, and observability so services focus on business logic.

Token Bucket Rate Limiting (Redis + Lua)

func (l *TokenBucketLimiter) Allow(ctx context.Context, key string) (bool, error) {
    script := redis.NewScript(`
        local capacity = tonumber(ARGV[1])
        local refill_rate = tonumber(ARGV[2])
        local now = tonumber(ARGV[3])
        local bucket = redis.call('HMGET', KEYS[1], 'tokens', 'last_refill')
        local tokens = tonumber(bucket[1]) or capacity
        local last_refill = tonumber(bucket[2]) or now
        local new_tokens = math.min(capacity, tokens + (now - last_refill) * refill_rate)
        if new_tokens >= 1 then
            redis.call('HMSET', KEYS[1], 'tokens', new_tokens - 1, 'last_refill', now)
            redis.call('EXPIRE', KEYS[1], 3600)
            return 1
        end
        return 0
    `)
    result, err := script.Run(ctx, l.client, []string{key},
        l.capacity, l.refillRate, time.Now().Unix()).Int()
    return result == 1, err
}

JWT Validation at the Edge

func AuthMiddleware(validator *JWTValidator, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        auth := r.Header.Get("Authorization")
        if !strings.HasPrefix(auth, "Bearer ") {
            http.Error(w, "Unauthorized", 401)
            return
        }
        claims, err := validator.Validate(auth[7:])
        if err != nil {
            http.Error(w, "Unauthorized", 401)
            return
        }
        r.Header.Set("X-User-ID", claims.Subject)
        r.Header.Set("X-User-Roles", strings.Join(claims.Roles, ","))
        next.ServeHTTP(w, r)
    })
}

Request Aggregation (BFF Pattern)

func (gw *Gateway) GetDashboard(ctx context.Context, userID string) (*Dashboard, error) {
    type result[T any] struct{ data T; err error }

    userCh := make(chan result[*UserProfile], 1)
    ordersCh := make(chan result[[]Order], 1)
    metricsCh := make(chan result[*Metrics], 1)

    go func() { d, e := gw.userSvc.GetProfile(ctx, userID); userCh <- result[*UserProfile]{d, e} }()
    go func() { d, e := gw.orderSvc.GetRecent(ctx, userID, 10); ordersCh <- result[[]Order]{d, e} }()
    go func() { d, e := gw.metricsSvc.Get(ctx, userID); metricsCh <- result[*Metrics]{d, e} }()

    if u := <-userCh; u.err != nil {
        return nil, u.err
    } else {
        return &Dashboard{User: u.data, Orders: (<-ordersCh).data, Metrics: (<-metricsCh).data}, nil
    }
}

Kong Configuration

services:
  - name: order-service
    url: http://order-service.production.svc:8080
    plugins:
      - name: rate-limiting
        config:
          minute: 100
          policy: redis
          redis_host: redis.production.svc
      - name: jwt
        config:
          claims_to_verify: [exp]
    routes:
      - name: orders
        paths: [/api/v1/orders]
        methods: [GET, POST]

Getting auth and rate limiting right at the edge frees every service from reimplementing the same logic.

→ Validate API URLs with the URL Parser tool.