docs/INFRA_RATE_LIMITER.md

# Infrastructure - Rate Limiter

This document provides a detailed breakdown of how the `foundation` library integrates the `Hammer` rate limiting library. It explains the design choices, the abstraction layers, and how developers can use the feature effectively.

## Overview

The standard `Hammer` integration pattern involves installing the `Hammer` backend and making direct calls to its API for rate limiting operations. The `foundation` library abstracts this process within the **`RateLimiter`** module to provide validation, observability, and a more user-friendly configuration format.

The `foundation` library adheres to this pattern but provides a robust abstraction layer through its `Infrastructure` and `RateLimiter` modules. This abstraction offers several advantages:

- **Centralized Control:** Simplifies management of rate limiting across the application.
- **Entity-Based Limiting:** Provides independent rate limiting buckets per entity and operation.
- **Standardized Errors:** Converts `Hammer`'s responses into the `Foundation.Error` struct for consistent error handling.
- **Observability:** Automatically emits telemetry events for rate limiting decisions and exceptions.

## The Integration in Detail

### 1. Rate Limiter Architectural Context

This diagram shows how the `RateLimiter` module fits within the overall Foundation architecture. It illustrates its role as a wrapper around the `Hammer` library and its interactions with the `Infrastructure` facade, the `HammerBackend` (which uses ETS), and the `TelemetryService`.

```mermaid
graph TD
    subgraph "Client Application"
        Client["Client Code"]
    end

    subgraph "Foundation Library"
        Facade["Foundation.Infrastructure"]
        RateLimiter["Foundation.Infrastructure.RateLimiter"]
        Telemetry["Foundation.Services.TelemetryService"]
        
        subgraph "Hammer Integration"
            HammerBackend["RateLimiter.HammerBackend (use Hammer)"]
            ETS["ETS Table (Hammer State)"]
        end
    end
    
    Client -- "execute_protected(...)" --> Facade
    Facade -- "check_rate(...)" --> RateLimiter
    
    RateLimiter -- "calls" --> HammerBackend
    HammerBackend -- "reads/writes to" --> ETS
    
    RateLimiter -- "emits :request_allowed / :request_denied" --> Telemetry
    
    style Client fill:#e9ecef,stroke:#333,stroke-width:1px,color:#000
    style Facade fill:#cce5ff,stroke:#333,stroke-width:2px,color:#000
    style RateLimiter fill:#d1ecf1,stroke:#333,stroke-width:2px,color:#000
    style Telemetry fill:#f8d7da,stroke:#333,stroke-width:1px,color:#000
    style HammerBackend fill:#fff3cd,stroke:#333,stroke-width:1px,color:#000
    style ETS fill:#e2e3e5,stroke:#333,stroke-width:1px,color:#000
```

### 2. Logical Flow of `check_rate`

This flowchart details the internal decision-making process of the `RateLimiter.check_rate/5` function. It visualizes how an incoming request is processed, how the underlying `Hammer` library is invoked, and how different outcomes (allow, deny, exception) are handled and reported.

```mermaid
flowchart TD
    Start["Start check_rate(entity_id, operation, limit, window, metadata)"] --> A["build_rate_key(entity_id, operation)"]
    A --> B{Key Generation Successful?}
    B -- "No (Invalid Input)" --> B_Fail["Create :validation_failed Error"]
    B_Fail --> EndError["Return {:error, Error.t()}"]
    
    B -- "Yes" --> C["try...rescue block"]
    
    subgraph "Hammer Interaction"
        C --> D["HammerBackend.hit(key, window, limit, 1)"]
        D --> E{Result?}
        E -- "{:allow, count}" --> F["Emit :request_allowed Telemetry"]
        F --> G["Return :ok"]
        
        E -- "{:deny, _limit}" --> H["Create :rate_limit_exceeded Error"]
        H --> I["Emit :request_denied Telemetry"]
        I --> EndError
    end
    
    subgraph "Exception Handling"
        C -- "rescue exception" --> J["Create :rate_limiter_exception Error"]
        J --> K["Emit :rate_limiter_exception Telemetry"]
        K --> EndError
    end

    G --> EndSuccess["Return :ok"]

    style Start fill:#cce5ff,stroke:#333,stroke-width:2px,color:#000
    style A fill:#e2e3e5,stroke:#333,stroke-width:1px,color:#000
    style D fill:#e2e3e5,stroke:#333,stroke-width:1px,color:#000
    style B fill:#fff3cd,stroke:#333,stroke-width:1px,color:#000
    style E fill:#fff3cd,stroke:#333,stroke-width:1px,color:#000
    style F fill:#d1ecf1,stroke:#333,stroke-width:1px,color:#000
    style I fill:#d1ecf1,stroke:#333,stroke-width:1px,color:#000
    style K fill:#d1ecf1,stroke:#333,stroke-width:1px,color:#000
    style G fill:#d4edda,stroke:#333,stroke-width:1px,color:#000
    style EndSuccess fill:#d4edda,stroke:#333,stroke-width:2px,color:#000
    style H fill:#f8d7da,stroke:#333,stroke-width:1px,color:#000
    style J fill:#f8d7da,stroke:#333,stroke-width:1px,color:#000
    style B_Fail fill:#f8d7da,stroke:#333,stroke-width:1px,color:#000
    style EndError fill:#f5c6cb,stroke:#333,stroke-width:2px,color:#000
```

## End-to-End Workflow

This sequence diagram demonstrates how the rate limiter handles concurrent requests from two different entities (`user_A` and `user_B`). It clearly shows that each entity has its own independent rate-limiting bucket within the shared ETS backend, preventing one user's activity from affecting another's.

```mermaid
sequenceDiagram
    participant user_A as "Client A (user1)"
    participant user_B as "Client B (user2)"
    participant RL as "RateLimiter"
    participant HB as "HammerBackend"
    participant ETS as "ETS Table"
    
    Note over user_A, ETS: Scenario Limit: 2 requests / window
    
    user_A->>+RL: check_rate("user1", :api, 2, 60000)
    RL->>+HB: hit("...:user1:api", ...)
    HB->>+ETS: update_counter("...:user1:api")
    ETS-->>-HB: count = 1
    HB-->>-RL: {:allow, 1}
    RL-->>-user_A: :ok
    
    user_B->>+RL: check_rate("user2", :api, 2, 60000)
    RL->>+HB: hit("...:user2:api", ...)
    HB->>+ETS: update_counter("...:user2:api")
    Note right of ETS: Separate bucket for user2
    ETS-->>-HB: count = 1
    HB-->>-RL: {:allow, 1}
    RL-->>-user_B: :ok

    user_A->>+RL: check_rate("user1", :api, 2, 60000)
    RL->>+HB: hit("...:user1:api", ...)
    HB->>+ETS: update_counter("...:user1:api")
    ETS-->>-HB: count = 2
    HB-->>-RL: {:allow, 2}
    RL-->>-user_A: :ok

    user_A->>+RL: check_rate("user1", :api, 2, 60000)
    RL->>+HB: hit("...:user1:api", ...)
    HB->>+ETS: get_counter("...:user1:api")
    Note right of ETS: Limit for user1 reached (count=2)
    ETS-->>-HB: count = 2
    HB-->>-RL: {:deny, 2}
    RL-->>-user_A: {:error, Error(rate_limit_exceeded)}
    
    user_B->>+RL: check_rate("user2", :api, 2, 60000)
    RL->>+HB: hit("...:user2:api", ...)
    HB->>+ETS: update_counter("...:user2:api")
    Note right of ETS: user2 bucket is independent
    ETS-->>-HB: count = 2
    HB-->>-RL: {:allow, 2}
    RL-->>-user_B: :ok
```

## Comparison Summary

This table summarizes how `foundation` implements the standard `Hammer` patterns.

| `Hammer` Documentation Pattern | `foundation` Library Implementation |
| :------------------------------ | :---------------------------------- |
| Direct calls to `Hammer` backend functions. | Rate limiting operations are abstracted through the **`RateLimiter.check_rate/5`** function. |
| Manual error handling for rate limit violations. | Standardized error handling with **`Foundation.Error`** structs for consistent responses. |
| Manual telemetry implementation for observability. | Automatic telemetry emission for `:request_allowed`, `:request_denied`, and `:rate_limiter_exception` events. |
| Entity-specific rate key management. | Automatic rate key generation using **`build_rate_key/2`** with entity and operation context. |

By using these abstractions, `foundation` provides a more integrated, observable, and developer-friendly way to leverage the power of `Hammer`'s rate limiting capabilities.