Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions gears/system/oagw/docs/ADR/0005-data-plane-caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Control Plane handles config resolution for Data Plane during proxy requests. Co

## Decision Drivers

* Fast lookups for hot configs (<1μs L1, ~1-2ms L2)
* Fast lookups for hot configs (<1us L1, ~1-2ms L2)
* Reduced database load (queries only on cache miss)
* Support for both single-exec (no Redis) and microservice (shared L2) deployment modes
* Correct cache invalidation on config writes
Expand All @@ -59,7 +59,7 @@ Chosen option: "Multi-layer caching: L1 (in-memory) + optional L2 (Redis) + Data

| Layer | Scope | Capacity | TTL | Access Time | Notes |
|---|---|---|---|---|---|
| L1 (In-Memory) | Per-instance LRU | 10,000 entries | No TTL (LRU eviction) | <1μs | |
| L1 (In-Memory) | Per-instance LRU | 10,000 entries | No TTL (LRU eviction) | <1us | |
| L2 (Redis, optional) | Shared across instances | Unbounded | 5 minutes | ~1-2ms | MessagePack serialization |
| Database (PostgreSQL) | Source of truth (JSON text) | Unlimited | N/A | ~5-10ms | Queried only on L1+L2 miss |

Expand All @@ -69,7 +69,7 @@ Chosen option: "Multi-layer caching: L1 (in-memory) + optional L2 (Redis) + Data
async fn get_config(key: &CacheKey) -> Result<ConfigValue> {
// Check L1
if let Some(value) = l1_cache.get(key) {
return Ok(value); // <1μs
return Ok(value); // <1us
}

// Check L2 (if enabled)
Expand Down Expand Up @@ -112,7 +112,7 @@ On config write (e.g., `PUT /upstreams/{id}`): (1) CP writes to database, (2) CP

### Consequences

* Good, because fast lookups for hot configs (<1μs L1)
* Good, because fast lookups for hot configs (<1us L1)
* Good, because reduced database load
* Good, because shared cache in microservice mode (L2)
* Good, because simple deployment in single-exec mode (no Redis)
Expand Down Expand Up @@ -163,5 +163,5 @@ Integration tests verify: L1 cache hit returns correct config, L1 miss falls thr

This decision directly addresses the following requirements or design elements:

* `cpt-cf-oagw-nfr-low-latency` — L1 cache provides <1μs config lookups on hot path
* `cpt-cf-oagw-nfr-low-latency` — L1 cache provides <1us config lookups on hot path
* `cpt-cf-oagw-fr-request-proxy` — Config resolution during proxy request execution
10 changes: 5 additions & 5 deletions gears/system/oagw/docs/ADR/0006-state-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub struct CPState {
```text
DP receives proxy request
├─ Check DP L1 cache for resolved (upstream, route) config
│ ├─ Hit: Use cached config (<1μs)
│ ├─ Hit: Use cached config (<1us)
│ └─ Miss: Call CP.resolve_proxy_target(alias, method, path)
│ ├─ Single tenant hierarchy walk: alias shadowing + route match
│ ├─ Effective config merge (upstream < route < tenant)
Expand All @@ -117,7 +117,7 @@ On config write: CP writes to DB, flushes own caches, returns success. API Handl

### Consequences

* Good, because fast path — DP serves hot configs from L1 (<1μs)
* Good, because fast path — DP serves hot configs from L1 (<1us)
* Good, because reduced CP calls (only for cache misses)
* Good, because simple rate limiting (no distributed coordination for MVP)
* Bad, because DP L1 can temporarily diverge from CP (stale data)
Expand All @@ -138,7 +138,7 @@ DP makes CP call for every request (no L1 cache).

### DP with L1 cache + rate limiters

* Good, because fast reads (<1μs for cached configs)
* Good, because fast reads (<1us for cached configs)
* Good, because rate limiter has full request context
* Bad, because cache consistency lag after writes

Expand All @@ -155,7 +155,7 @@ DP calls CP to check rate limits.

* DP handles every proxy request
* Reduces CP calls for hot configs
* <1μs access time for cached configs
* <1us access time for cached configs
* Small cache (1000 entries) has negligible memory overhead

**Why rate limiters in DP**:
Expand Down Expand Up @@ -188,6 +188,6 @@ DP calls CP to check rate limits.

This decision directly addresses the following requirements or design elements:

* `cpt-cf-oagw-nfr-low-latency` — DP L1 cache provides <1μs config lookups on hot path
* `cpt-cf-oagw-nfr-low-latency` — DP L1 cache provides <1us config lookups on hot path
* `cpt-cf-oagw-fr-rate-limiting` — Rate limiters owned by DP for per-instance enforcement
* `cpt-cf-oagw-fr-request-proxy` — Caching strategy optimizes proxy request execution
3 changes: 3 additions & 0 deletions gears/system/oagw/oagw/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ test-utils = [
"tokio/rt",
]

[lints]
workspace = true

[dependencies]
toolkit = { workspace = true }
toolkit-auth = { workspace = true }
Expand Down
4 changes: 4 additions & 0 deletions gears/system/oagw/oagw/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! REST layer: control-plane management handlers and the data-plane proxy
//! handler.

pub mod rest;
39 changes: 39 additions & 0 deletions gears/system/oagw/oagw/src/api/rest/dto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! REST DTOs for the OAGW management API.
//!
//! Create/update request bodies and single-resource responses reuse the
//! domain models directly (`Upstream`, `Route`, `PluginRecord`); this module
//! only defines the wrapper shapes that differ (list pages, plugin source).

use serde::Serialize;
use uuid::Uuid;

/// List response shape: `{ "items": [...], "page_info": {...} }`.
#[derive(Debug, Clone, Serialize)]
pub struct ListResponse {
pub items: Vec<serde_json::Value>,
pub page_info: PageInfoDto,
}

#[derive(Debug, Clone, Serialize)]
pub struct PageInfoDto {
pub limit: u64,
}

impl ListResponse {
/// Wrap a page of projected items (already OData-applied).
#[must_use]
pub fn of(items: Vec<serde_json::Value>) -> Self {
let limit = items.len() as u64;
Self {
items,
page_info: PageInfoDto { limit },
}
}
}

/// Response for `GET /plugins/{id}/source`.
#[derive(Debug, Clone, Serialize)]
pub struct PluginSourceResponse {
pub id: Uuid,
pub source: String,
}
Loading