Skip to main content

Request Flow

Dokumentasi lengkap alur request dari client hingga response, termasuk authentication, authorization, dan error handling.

🔄 Complete Request Flow


📊 Request Lifecycle Stages

1. Client Request Preparation

// Flutter Client
Future<Response> createOrder(CreateOrderRequest request) async {
  // Generate unique request ID
  final requestId = Uuid().v4();
  
  // Get JWT token from secure storage
  final token = await secureStorage.read(key: 'auth_token');
  
  // Prepare headers
  final headers = {
    'Authorization': 'Bearer $token',
    'X-Request-ID': requestId,
    'Content-Type': 'application/json',
  };
  
  // Make request
  try {
    final response = await dio.post(
      '/orders',
      data: request.toJson(),
      options: Options(headers: headers),
    );
    return response;
  } catch (e) {
    // Handle error
    throw handleError(e);
  }
}

2. API Gateway Processing

// API Gateway Middleware Chain
func (gw *Gateway) HandleRequest(c echo.Context) error {
    // 1. Request ID
    requestID := c.Request().Header.Get("X-Request-ID")
    if requestID == "" {
        requestID = uuid.New().String()
    }
    c.Set("request_id", requestID)
    
    // 2. Logging
    logger := gw.logger.With(
        zap.String("request_id", requestID),
        zap.String("method", c.Request().Method),
        zap.String("path", c.Request().URL.Path),
    )
    logger.Info("request started")
    
    // 3. Rate Limiting
    if err := gw.rateLimiter.Check(c); err != nil {
        return c.JSON(429, ErrorResponse{
            Error: Error{
                Code:    "RATE_LIMIT_EXCEEDED",
                Message: "Too many requests",
                TraceID: requestID,
            },
        })
    }
    
    // 4. Authentication
    token := extractToken(c)
    userCtx, err := gw.authService.ValidateToken(c.Request().Context(), token)
    if err != nil {
        return c.JSON(401, ErrorResponse{
            Error: Error{
                Code:    "UNAUTHORIZED",
                Message: "Invalid or expired token",
                TraceID: requestID,
            },
        })
    }
    c.Set("user", userCtx)
    
    // 5. Forward to service
    return gw.forwardToService(c)
}

3. Service Processing

// Business Service Handler
func (s *OrderService) CreateOrder(c echo.Context) error {
    ctx := c.Request().Context()
    requestID := c.Get("request_id").(string)
    user := c.Get("user").(*UserContext)
    
    // Start tracing
    ctx, span := s.tracer.Start(ctx, "CreateOrder")
    defer span.End()
    
    // Parse request
    var req CreateOrderRequest
    if err := c.Bind(&req); err != nil {
        return c.JSON(400, ErrorResponse{
            Error: Error{
                Code:    "INVALID_REQUEST",
                Message: "Invalid request body",
                TraceID: requestID,
            },
        })
    }
    
    // Validate
    if err := c.Validate(&req); err != nil {
        return c.JSON(400, ValidationErrorResponse(err, requestID))
    }
    
    // Check authorization
    if !s.authz.CanCreateOrder(user) {
        return c.JSON(403, ErrorResponse{
            Error: Error{
                Code:    "FORBIDDEN",
                Message: "Insufficient permissions",
                TraceID: requestID,
            },
        })
    }
    
    // Business logic
    order, err := s.orderUseCase.CreateOrder(ctx, user.ID, &req)
    if err != nil {
        s.logger.Error("failed to create order", 
            zap.Error(err),
            zap.String("request_id", requestID),
        )
        return handleServiceError(c, err, requestID)
    }
    
    // Publish event (async)
    go s.eventPublisher.Publish(OrderCreatedEvent{
        OrderID: order.ID,
        UserID:  user.ID,
    })
    
    // Metrics
    s.metrics.OrderCreated.Inc()
    
    return c.JSON(201, SuccessResponse{
        Data: order,
    })
}

🔐 Authentication Flow


⚡ Caching Strategy

Cache Flow

Cache Implementation

func (s *ProductService) GetProduct(ctx context.Context, id string) (*Product, error) {
    // Check cache
    cacheKey := fmt.Sprintf("product:%s", id)
    
    var product Product
    err := s.cache.Get(ctx, cacheKey, &product)
    if err == nil {
        s.metrics.CacheHit.Inc()
        return &product, nil
    }
    
    s.metrics.CacheMiss.Inc()
    
    // Query database
    product, err = s.repo.FindByID(ctx, id)
    if err != nil {
        return nil, err
    }
    
    // Store in cache (15 minutes TTL)
    _ = s.cache.Set(ctx, cacheKey, product, 15*time.Minute)
    
    return &product, nil
}

🚨 Error Handling Flow

Error Response Format

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human readable message",
    "details": [
      {
        "field": "field_name",
        "message": "Field specific error"
      }
    ],
    "traceId": "550e8400-e29b-41d4-a716-446655440000"
  }
}

📈 Performance Metrics

Key Metrics Tracked

# Request Metrics
http_requests_total: counter
  labels: [method, path, status]

http_request_duration_seconds: histogram
  labels: [method, path]
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5]

# Cache Metrics
cache_hits_total: counter
  labels: [cache_type]

cache_misses_total: counter
  labels: [cache_type]

# Database Metrics
db_query_duration_seconds: histogram
  labels: [operation, table]
  buckets: [0.001, 0.01, 0.1, 0.5, 1]

# Error Metrics
errors_total: counter
  labels: [type, service]

🔍 Observability

Distributed Tracing

// Trace propagation
func (s *Service) HandleRequest(c echo.Context) error {
    // Extract trace context from headers
    ctx := otel.GetTextMapPropagator().Extract(
        c.Request().Context(),
        propagation.HeaderCarrier(c.Request().Header),
    )
    
    // Start span
    ctx, span := s.tracer.Start(ctx, "HandleRequest",
        trace.WithAttributes(
            attribute.String("http.method", c.Request().Method),
            attribute.String("http.url", c.Request().URL.Path),
        ),
    )
    defer span.End()
    
    // Process request
    result, err := s.processRequest(ctx, c)
    if err != nil {
        span.RecordError(err)
        span.SetStatus(codes.Error, err.Error())
        return err
    }
    
    span.SetStatus(codes.Ok, "success")
    return c.JSON(200, result)
}