Sequence Diagrams¶
This document provides detailed sequence diagrams for key flows in the application.
Authentication Flow¶
HTTP Login Flow¶
sequenceDiagram
participant Client
participant FastAPI
participant KeycloakManager
participant Keycloak
participant Redis
Client->>FastAPI: POST /login {username, password}
FastAPI->>KeycloakManager: login(username, password)
KeycloakManager->>Keycloak: Token request (OAuth2 password grant)
Keycloak-->>KeycloakManager: {access_token, refresh_token, expires_in}
KeycloakManager-->>FastAPI: Token data
FastAPI->>Redis: Store user session
Redis-->>FastAPI: OK
FastAPI-->>Client: {access_token, refresh_token, expires_in} WebSocket Authentication Flow¶
sequenceDiagram
participant Client
participant WebSocket
participant AuthBackend
participant Keycloak
participant ConnectionLimiter
participant ConnectionManager
Client->>WebSocket: Connect ws://host/web?token=<jwt>
WebSocket->>AuthBackend: authenticate(connection)
AuthBackend->>AuthBackend: Extract token from query params
AuthBackend->>Keycloak: Validate JWT signature
Keycloak-->>AuthBackend: Token valid, user claims
AuthBackend-->>WebSocket: UserModel(sub, roles, ...)
WebSocket->>ConnectionLimiter: check_limit(user_id)
alt Connection limit exceeded
ConnectionLimiter-->>WebSocket: Limit exceeded
WebSocket-->>Client: Close 1008 (Policy Violation)
else Limit OK
ConnectionLimiter-->>WebSocket: OK
WebSocket->>ConnectionManager: connect(websocket)
ConnectionManager-->>WebSocket: Connection added
WebSocket-->>Client: Connection established
end HTTP Request Flow¶
GET /authors with Filtering¶
sequenceDiagram
participant Client
participant PrometheusMiddleware
participant RateLimitMiddleware
participant AuthMiddleware
participant Handler
participant RBACDependency as require_roles()
participant Database
Client->>PrometheusMiddleware: GET /authors?name=John
PrometheusMiddleware->>PrometheusMiddleware: Start metrics timer
PrometheusMiddleware->>RateLimitMiddleware: Forward request
RateLimitMiddleware->>RateLimitMiddleware: Get rate limit key
RateLimitMiddleware->>RateLimitMiddleware: Check Redis counter
alt Rate limit exceeded
RateLimitMiddleware-->>Client: 429 Too Many Requests
else Rate limit OK
RateLimitMiddleware->>AuthMiddleware: Forward request
AuthMiddleware->>AuthMiddleware: Extract Authorization header
AuthMiddleware->>AuthMiddleware: Validate JWT token
AuthMiddleware->>AuthMiddleware: Populate request.state.user
alt Invalid token
AuthMiddleware-->>Client: 401 Unauthorized
else Valid token
AuthMiddleware->>RBACDependency: Check permissions (FastAPI dependency)
RBACDependency->>RBACDependency: Verify user has required roles
alt Permission denied
RBACDependency-->>Client: 403 Forbidden
else Permission granted
RBACDependency->>Handler: Forward to endpoint
Handler->>Handler: Validate query params
Handler->>Database: SELECT * FROM author WHERE name ILIKE '%John%'
Database-->>Handler: [Author rows]
Handler-->>RBACDependency: [Author list]
RBACDependency-->>AuthMiddleware: Response
AuthMiddleware-->>RateLimitMiddleware: Response
RateLimitMiddleware->>RateLimitMiddleware: Add X-RateLimit headers
RateLimitMiddleware-->>PrometheusMiddleware: Response
PrometheusMiddleware->>PrometheusMiddleware: Record metrics
PrometheusMiddleware-->>Client: 200 OK + [Authors]
end
end
end POST /authors (Create)¶
sequenceDiagram
participant Client
participant Middleware
participant Handler
participant Session
participant Database
Client->>Middleware: POST /authors {name: "New Author"}
Note over Middleware: Authentication & Authorization
Middleware->>Handler: Validated request
Handler->>Handler: Validate request body (Pydantic)
alt Validation error
Handler-->>Client: 422 Validation Error
else Valid data
Handler->>Repository: Inject AuthorRepository via Depends
Repository-->>Handler: Repository instance
Handler->>Repository: repo.create(author)
Repository->>Database: INSERT INTO author VALUES (...)
Database-->>Repository: Author with ID
Repository-->>Handler: Created Author
Handler->>Database: COMMIT transaction
Database-->>Handler: OK
Handler-->>Client: 200 OK + {id: 1, name: "New Author"}
end WebSocket Request Flow¶
GET_AUTHORS Request (PkgID: 1)¶
sequenceDiagram
participant Client
participant WebEndpoint
participant RateLimiter
participant PackageRouter
participant RBACManager
participant Handler
participant Database
Client->>WebEndpoint: Send JSON: {pkg_id: 1, req_id: "uuid", data: {filters: {name: "John"}}}
WebEndpoint->>WebEndpoint: Parse JSON message
WebEndpoint->>WebEndpoint: Create RequestModel
WebEndpoint->>RateLimiter: check_rate_limit(user_id)
alt Rate limit exceeded
RateLimiter-->>WebEndpoint: Limit exceeded
WebEndpoint-->>Client: {status_code: 1, msg: "Rate limit exceeded"}
else Rate OK
RateLimiter-->>WebEndpoint: OK
WebEndpoint->>PackageRouter: handle_request(request)
PackageRouter->>RBACManager: check_ws_permission(pkg_id, user)
alt Permission denied
RBACManager-->>PackageRouter: Permission denied
PackageRouter-->>WebEndpoint: {status_code: 3, msg: "Permission denied"}
WebEndpoint-->>Client: Error response
else Permission granted
RBACManager-->>PackageRouter: OK
PackageRouter->>PackageRouter: Validate data against JSON schema
alt Schema validation error
PackageRouter-->>WebEndpoint: {status_code: 2, msg: "Invalid data"}
WebEndpoint-->>Client: Error response
else Valid schema
PackageRouter->>Handler: Execute handler(request)
Handler->>Database: SELECT * FROM author WHERE name ILIKE '%John%'
Database-->>Handler: [Author rows]
Handler->>Handler: Build ResponseModel
Handler-->>PackageRouter: {status_code: 0, data: [...]}
PackageRouter-->>WebEndpoint: ResponseModel
WebEndpoint-->>Client: {pkg_id: 1, req_id: "uuid", status_code: 0, data: [...]}
end
end
end GET_PAGINATED_AUTHORS Request (PkgID: 2)¶
sequenceDiagram
participant Client
participant WebEndpoint
participant PackageRouter
participant Handler
participant PaginationUtil
participant Database
Client->>WebEndpoint: {pkg_id: 2, req_id: "uuid", data: {page: 1, per_page: 20}}
Note over WebEndpoint: Rate limiting & auth checks passed
WebEndpoint->>PackageRouter: handle_request(request)
PackageRouter->>Handler: get_paginated_authors_handler(request)
Handler->>PaginationUtil: get_paginated_results(Author, page=1, per_page=20)
PaginationUtil->>Database: SELECT COUNT(id) FROM author
Database-->>PaginationUtil: total=42
PaginationUtil->>PaginationUtil: Calculate pages (42/20 = 3)
PaginationUtil->>Database: SELECT * FROM author OFFSET 0 LIMIT 20
Database-->>PaginationUtil: [20 Author rows]
PaginationUtil-->>Handler: (results, MetadataModel{page: 1, per_page: 20, total: 42, pages: 3})
Handler->>Handler: Build ResponseModel with meta
Handler-->>WebEndpoint: {status_code: 0, data: [...], meta: {...}}
WebEndpoint-->>Client: Complete response with pagination metadata Broadcast Flow¶
Server-Initiated Broadcast¶
sequenceDiagram
participant BackgroundTask
participant ConnectionManager
participant WebSocket1
participant WebSocket2
participant WebSocket3
participant Client1
participant Client2
participant Client3
BackgroundTask->>BackgroundTask: Event occurs (e.g., data update)
BackgroundTask->>BackgroundTask: Build BroadcastDataModel
BackgroundTask->>ConnectionManager: broadcast(message)
ConnectionManager->>ConnectionManager: Create connections snapshot
ConnectionManager->>ConnectionManager: asyncio.gather(...)
par Broadcast to all clients
ConnectionManager->>WebSocket1: send_json(message)
WebSocket1-->>Client1: {pkg_id: 1, req_id: "00000000-...", data: {...}}
and
ConnectionManager->>WebSocket2: send_json(message)
WebSocket2-->>Client2: {pkg_id: 1, req_id: "00000000-...", data: {...}}
and
ConnectionManager->>WebSocket3: send_json(message)
Note over WebSocket3: Connection failed
WebSocket3--xClient3: Exception
ConnectionManager->>ConnectionManager: Safe error handling
ConnectionManager->>ConnectionManager: disconnect(WebSocket3)
end
ConnectionManager-->>BackgroundTask: Broadcast complete Rate Limiting Flow¶
Sliding Window Rate Limiting¶
sequenceDiagram
participant Request
participant RateLimiter
participant Redis
Request->>RateLimiter: check_rate_limit(key="user:john", limit=60, window=60s)
RateLimiter->>RateLimiter: current_time = now()
RateLimiter->>RateLimiter: window_start = current_time - 60
RateLimiter->>Redis: ZREMRANGEBYSCORE(key, -inf, window_start)
Note over Redis: Remove old requests outside window
Redis-->>RateLimiter: Removed count
RateLimiter->>Redis: ZCARD(key)
Note over Redis: Count requests in current window
Redis-->>RateLimiter: current_count=45
alt current_count >= limit
RateLimiter-->>Request: (False, 0) - Rate limit exceeded
Note over Request: Return 429 error
else current_count < limit
RateLimiter->>Redis: ZADD(key, current_time, request_id)
Note over Redis: Add current request to set
Redis-->>RateLimiter: OK
RateLimiter->>Redis: EXPIRE(key, window * 2)
Note over Redis: Set TTL for automatic cleanup
Redis-->>RateLimiter: OK
RateLimiter->>RateLimiter: remaining = limit - current_count - 1
RateLimiter-->>Request: (True, remaining) - Request allowed
Note over Request: Continue processing
end WebSocket Connection Limiting¶
sequenceDiagram
participant Client
participant WebSocket
participant ConnectionLimiter
participant Redis
Client->>WebSocket: Connect (user_id="user123")
WebSocket->>ConnectionLimiter: add_connection(user_id, connection_id)
ConnectionLimiter->>Redis: SADD("ws:connections:user123", connection_id)
Redis-->>ConnectionLimiter: OK
ConnectionLimiter->>Redis: SCARD("ws:connections:user123")
Note over Redis: Count connections for user
Redis-->>ConnectionLimiter: count=5
alt count > max_connections (e.g., 5)
ConnectionLimiter->>Redis: SREM("ws:connections:user123", connection_id)
Redis-->>ConnectionLimiter: OK
ConnectionLimiter-->>WebSocket: False - Limit exceeded
WebSocket-->>Client: Close 1008 (Policy Violation)
else count <= max_connections
ConnectionLimiter-->>WebSocket: True - Connection allowed
WebSocket-->>Client: Connection established
end Error Handling Flow¶
Handler Error Recovery¶
sequenceDiagram
participant Client
participant Handler
participant Database
participant Logger
Client->>Handler: Request with valid data
Handler->>Database: Execute query
alt Database error (SQLAlchemyError)
Database--xHandler: SQLAlchemyError
Handler->>Logger: log.error("Database error: ...")
Handler->>Handler: Build error ResponseModel
Handler-->>Client: {status_code: 1, msg: "Database error occurred"}
else Validation error
Database-->>Handler: Success
Handler->>Handler: Process results
Handler->>Handler: Validation fails
Handler->>Logger: log.error("Validation error: ...")
Handler-->>Client: {status_code: 2, msg: "Invalid data"}
else Unexpected error
Database-->>Handler: Success
Handler->>Handler: Processing...
Handler--xHandler: Unexpected exception
Note over Handler: Exception propagates up
Handler->>Logger: log.exception("Unexpected error")
Handler-->>Client: {status_code: 1, msg: "Internal error"}
else Success
Database-->>Handler: Results
Handler->>Handler: Process and format
Handler-->>Client: {status_code: 0, data: [...]}
end Health Check Flow¶
Health Check with Dependency Verification¶
sequenceDiagram
participant Client
participant HealthEndpoint
participant Database
participant Redis
Client->>HealthEndpoint: GET /health
par Check all dependencies
HealthEndpoint->>Database: SELECT 1
alt Database OK
Database-->>HealthEndpoint: Success
HealthEndpoint->>HealthEndpoint: db_status = "healthy"
else Database failed
Database--xHealthEndpoint: Exception
HealthEndpoint->>HealthEndpoint: db_status = "unhealthy"
end
and
HealthEndpoint->>Redis: PING
alt Redis OK
Redis-->>HealthEndpoint: PONG
HealthEndpoint->>HealthEndpoint: redis_status = "healthy"
else Redis failed
Redis--xHealthEndpoint: Exception
HealthEndpoint->>HealthEndpoint: redis_status = "unhealthy"
end
end
HealthEndpoint->>HealthEndpoint: Aggregate statuses
alt All healthy
HealthEndpoint-->>Client: 200 OK {status: "healthy", database: "healthy", redis: "healthy"}
else Any unhealthy
HealthEndpoint-->>Client: 503 Service Unavailable {status: "unhealthy", ...}
end Metrics Collection Flow¶
Prometheus Metrics Scraping¶
sequenceDiagram
participant Prometheus
participant MetricsEndpoint
participant MetricsRegistry
loop Every scrape_interval (15s)
Prometheus->>MetricsEndpoint: GET /metrics
MetricsEndpoint->>MetricsRegistry: Collect all metrics
MetricsRegistry->>MetricsRegistry: Gather HTTP request counters
MetricsRegistry->>MetricsRegistry: Gather WebSocket metrics
MetricsRegistry->>MetricsRegistry: Gather application metrics
MetricsRegistry-->>MetricsEndpoint: Metrics in text format
MetricsEndpoint-->>Prometheus: text/plain metrics
Note over MetricsEndpoint,Prometheus: # HELP http_requests_total...<br/># TYPE http_requests_total counter<br/>http_requests_total{...} 1234.0
Prometheus->>Prometheus: Store time series data
Prometheus->>Prometheus: Evaluate alerting rules
end Notes¶
These diagrams use Mermaid syntax and can be rendered using: - GitHub (automatic rendering) - Mermaid Live Editor: https://mermaid.live/ - VS Code with Mermaid extension - Documentation tools that support Mermaid
For complex flows, refer to the source code in: - app/middlewares/ - Middleware implementations - app/routing.py - WebSocket routing logic - app/api/ws/handlers/ - WebSocket handlers - app/api/http/ - HTTP handlers