Files
asepharyana-hub/.claude/skills/event-driven.md

3.2 KiB

name, description
name description
event-driven Event-driven patterns dengan Dapr + NATS untuk Asepharyana Hub

Event-Driven Architecture — Asepharyana Hub

Stack

  • Message Backbone: NATS + JetStream (untuk streaming & job queue)
  • Pub/Sub Runtime: Dapr sidecar per service (pubsub via Redis built-in)
  • State Store: Dapr → Redis

Event Topics Convention

hub.<domain>.<action>

Contoh:
hub.image.cached       → Image selesai di-cache ke CDN
hub.image.repaired     → Image diperbaiki (CNAME change)
hub.scrape.anime.done  → Scrape anime selesai
hub.system.alert       → Error/alert dari service

CloudEvents Format

{
  "specversion": "1.0",
  "type": "hub.image.cached",
  "source": "scraper-api",
  "subject": "anime-poster",
  "id": "uuid-v4",
  "time": "2026-07-21T10:00:00Z",
  "datacontenttype": "application/json",
  "data": { ... }
}

Publish Event (Rust via HTTP API)

Gunakan reqwest langsung ke Dapr sidecar (SDK Rust masih experimental):

let event = serde_json::json!({
    "specversion": "1.0",
    "type": "hub.image.cached",
    "source": "scraper-api",
    "id": Uuid::new_v4().to_string(),
    "time": chrono::Utc::now().to_rfc3339(),
    "datacontenttype": "application/json",
    "data": { "original_url": url, "cdn_url": cdn_url }
});

reqwest::Client::new()
    .post("http://localhost:3500/v1.0/publish/pubsub/hub.image.cached")
    .json(&event)
    .send()
    .await?;

Service Invocation

curl http://localhost:3500/v1.0/invoke/<app-id>/method/<path>

State Store

# Set
curl -X POST http://localhost:3500/v1.0/state/statestore \
  -H "Content-Type: application/json" \
  -d '[{"key": "mykey", "value": "myvalue"}]'

# Get
curl http://localhost:3500/v1.0/state/statestore/mykey

# Delete
curl -X DELETE http://localhost:3500/v1.0/state/statestore/mykey

Scraper Event Integration

File yang perlu dimodifikasi untuk event-driven:

File Perubahan
src/events/bus.rs Ganti backend dari tokio broadcast ke Dapr pub/sub
src/bootstrap/mod.rs Init DaprClient, inject ke AppState
src/presentation/state.rs Tambah dapr_client field
src/proxy/use_cases.rs Publish ImageRepaired & ImageCached events
src/infrastructure/services/images/cache.rs Emit event tiap cache selesai
Cargo.toml Tambah reqwest, uuid, chrono (jika belum ada)

Event Handlers (Subscribe)

Buat src/subscribers/ untuk handler:

// src/subscribers/image_handler.rs
pub async fn handle_image_cached(event: CloudEvent) -> Result<()> {
    // Log, notifikasi, update status
}

Daftarkan subscribers di bootstrap/mod.rs dengan spawn task:

tokio::spawn(async move {
    let mut stream = dapr_client.subscribe("pubsub", "hub.image.cached");
    while let Some(event) = stream.next().await {
        handle_image_cached(event).await;
    }
});

Testing Event-Driven Code

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_publish_event() {
        let client = MockDaprClient::new();
        client.expect_publish()
            .with(...)
            .returning(|_| Ok(()));
        // ... test
    }
}