Loading...
Functional Requirements
Non-Functional Requirements
All APIs: JSON over HTTPS; standard error envelope:
{ "code":"BAD_REQUEST","message":"reason","details":{} }
1. Subscribe
POST /v1/subscribe/{city}
Request:
{
"user_id":"uuid",
"push_token":"string",
"platform":"ios|android|web",
"notify_level":"ALL|CRISIS_ONLY",
"channels":["push","email"] // optional
}
Responses:
201 Created (or 200 OK if updated)400 Bad Request, 404 Not Found (city), 409 Conflict (semantic)user_id+city.2. Unsubscribe
DELETE /v1/unsubscribe/{city}
Body: { "user_id": 123123412 }
Responses: 200 OK, 404 Not Found.
3. Read Forecast
GET /v1/weather/{city}/{YYYY-MM-DD}
{
"city_id":"city:2643743",
"date":"2025-12-08",
"forecast":{ "temp_min_c":4, "temp_max_c":8, "temp_avg_c":6, "humidity_percent":78, "precip_probability":0.6, "wind_kph":40 },
"hazards":[{"type":"HIGH_WIND","level":"SEVERE","confidence":0.9}],
"generated_at":"2025-12-07T23:00:00Z",
"source":["provA","provB"]
}
Errors: 400, 404, 500.
Operational notes: cache-aside pattern for Redis; fallback to canonical store.
Operational controls
Architecture summary
Key notes
shard = hash(city_id) % N_SHARDS.weather::city::<city_id>::date::<YYYY-MM-DD>subscribers::city::<city_id>::shard::<n>A. Subscription Service
POST /v1/subscribe/{city} → store in NoSQL (PK=city#<id>, SK=user#<id>) → invalidate/refresh Redis shard cache subscribers::city::<id>::shard::<n>.{city_id, user_id, push_token, platform, notify_level, channels, status, updated_at}.city_id; hot-city sharding city#id#shard-<0..k-1>; autoscale DB throughput; Redis cluster for cached shards.Users/Cities → 1,000,000/10,000 = 100; hot-city (200k) → increase k until shard size ≈ target chunk (e.g., ≤ 10k entries).B. Kafka (topics) + Delivery Workers
weather.normal, weather.urgent) → Redis sink consumer updates cache → delivery consumer reads topic partitions → lookup subscribers → batch & send pushes → commit offsets.hash(city_id) % P; create separate urgent topic to prioritize processing.P for throughput; consumer group size ≤ P; autoscale workers by consumer lag and delivery latency.(Cities × updates/hour)/3600 → 10,000×4/3600 ≈ 11.11; deliveries/sec ≈ events/sec × avg_subs → 11.11×100 ≈ 1,111. worker_count = ceil(deliveries/sec / worker_throughput) (e.g., throughput=400 → 3 workers baseline).delivered::<event_id>::<user_id> in Redis; DLQ on permanent failures.C. Redis (cache + sink + dedupe)
GET, fast subscriber cache for delivery, dedupe store for events/deliveries.weather::city::<id>::date::<YYYY-MM-DD> → JSON/value with TTL.subscribers::city::<id>::shard::<n> → serialized array or Redis set.event_dedup::<event_hash> and delivered::<event_id>::<user_id> → TTLed keys.