Operasi pemodelan geometri parametrik berbasis boundary representation (B-Rep), evaluasi CSG (Constructive Solid Geometry), dan diskretisasi mesh memerlukan komputasi CPU dan memori intensif pada CAD kernel (seperti OpenCASCADE, CGAL, atau Blender headless). Pemrosesan geometri kompleks ini sering memakan waktu antara 10 detik hingga beberapa menit.

Mengekspos operasi tersebut melalui endpoint HTTP sinkron konvensional menimbulkan kegagalan sistemik. HTTP gateway atau reverse proxy (seperti Nginx, AWS ALB, atau Cloudflare) umumnya membatasi batas waktu baca (read timeout) antara 30 hingga 60 detik. Ketika timeout tercapai, gateway menutup koneksi dengan respons 504 Gateway Timeout. Namun di latar belakang, kernel CAD tetap mengeksekusi komputasi hingga selesai.

Masalah memburuk ketika client HTTP melakukan auto-retry. Tanpa kontrak idempotensi yang ketat, retry memicu duplikasi proses komputasi yang identik di kernel pool. Beban komputasi terakumulasi secara eksponensial (thundering herd), menghabiskan kapasitas worker pool, dan akhirnya melumpuhkan seluruh service. Solusinya adalah decoupling eksekusi melalui antrean asinkron dan manajemen idempotensi terdistribusi.

Arsitektur Asinkron: 202 Accepted & Header Location

Pola Asynchronous Request-Reply memisahkan fase penerimaan permintaan dari fase eksekusi komputasi. Klien yang meminta komputasi geometri tidak menunggu hasil akhir, melainkan menerima tanda terima tugas secara instan.

Spesifikasi Kontrak POST /v1/models/generate

Klien mengirimkan payload parameter geometri disertai header Idempotency-Key. Server memvalidasi skema payload, mengalokasikan entitas job, memasukkan pesan ke dalam task queue, lalu langsung merespons dengan HTTP status 202 Accepted.

Response wajib menyertakan header Location yang mengarahkan klien ke endpoint polling status job.

HTTP/1.1 202 Accepted
Location: /v1/jobs/cad_8f93b2a1c0d4
Content-Type: application/json

{
  "job_id": "cad_8f93b2a1c0d4",
  "status": "queued",
  "created_at": "2024-10-25T08:30:00Z"
}

Skema Payload Request Geometri

Payload memuat parameter diskret yang mendefinisikan bentuk 3D. Parameter harus divalidasi secara ketat pada boundary layer sebelum menyentuh antrean worker.

{
  "engine": "opencascade-brep",
  "export_format": "step",
  "parameters": {
    "outer_diameter": 120.5,
    "inner_diameter": 45.0,
    "length": 300.0,
    "fillet_radius": 2.5,
    "hole_pattern": {
      "count": 6,
      "pitch_diameter": 80.0,
      "hole_diameter": 10.0
    }
  }
}

Kontrak Idempotensi Berbasis Hashing Parameter

Idempotensi memastikan bahwa pemanggilan API yang identik secara berulang menghasilkan status dan keluaran yang sama tanpa memicu ulang eksekusi kernel. Klien dapat menyediakan header Idempotency-Key kustom (misal: UUIDv4). Namun, untuk sistem parametrik murni, server juga dapat menggenerasi fallback identitas deterministik berdasarkan hash kanonikal dari parameter input.

Implementasi Handler Idempotensi (Python & Redis)

Kode berikut menerapkan reservasi atomik menggunakan atomic primitive Redis SET ... NX EX guna mencegah double-execution saat request datang bersamaan.

import hashlib
import json
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)

def resolve_idempotency_key(client_key: str | None, payload: dict) -> str:
    if client_key:
        return f"idemp:{client_key}"
    canonical_bytes = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
    return f"idemp:{hashlib.sha256(canonical_bytes).hexdigest()}"

def enqueue_cad_generation(client_key: str | None, payload: dict) -> tuple[str, int]:
    key = resolve_idempotency_key(client_key, payload)
    # ponytail: redis lock ttl 3600s, upgrade to database outbox pattern if persistence is required
    token_job_id = f"cad_{hashlib.sha256(key.encode()).hexdigest()[:12]}"
    
    # SET key value NX EX -> Operasi atomik untuk reservasi eksekusi
    acquired = r.set(key, token_job_id, nx=True, ex=3600)
    
    if not acquired:
        existing_job_id = r.get(key)
        # 200/202 mengembalikan referensi job yang sudah aktif/selesai tanpa re-enqueue
        return existing_job_id, 200
        
    # Daftarkan job state dan push ke queue worker CAD
    r.hset(f"job:{token_job_id}", mapping={
        "status": "queued",
        "params": json.dumps(payload)
    })
    r.rpush("cad_work_queue", token_job_id)
    return token_job_id, 202

# Self-check assertion
if __name__ == "__main__":
    p = {"diameter": 50, "length": 100}
    j1, c1 = enqueue_cad_generation(None, p)
    j2, c2 = enqueue_cad_generation(None, p)
    assert j1 == j2, "Job ID must be deterministic on duplicate input"
    assert c1 == 202 and c2 == 200, "Status must distinguish new vs existing job"

enqueue_cad_generation() → skipped: [distributed lock across multiple Redis nodes], add when [Redis cluster deployed].

Lifecycle State Machine

Setiap tugas pemrosesan CAD harus mengikuti transisi state deterministik yang disimpan pada persistent store atau in-memory database.

  • queued: Tugas telah tervalidasi dan berada di antrean antarkomponen (misal: Redis List/Stream, RabbitMQ, SQS). Worker belum memproses payload.
  • processing: Worker CAD mengambil tugas dari antrean, mengevaluasi parameter, dan menjalankan kernel komputasi geometri.
  • completed: Model B-Rep atau mesh berhasil digenerasi dan diunggah ke object storage (S3/GCS). Metadata model dan URL unduhan siap diakses.
  • failed: Terjadi kegagalan non-retriable, seperti pelanggaran topologi geometri (misal: non-manifold edges, invalid boolean intersection) atau timeout internal worker.

Response Skema Status Polling (GET /v1/jobs/{job_id})

HTTP/1.1 200 OK
Content-Type: application/json

{
  "job_id": "cad_8f93b2a1c0d4",
  "status": "completed",
  "created_at": "2024-10-25T08:30:00Z",
  "started_at": "2024-10-25T08:30:02Z",
  "completed_at": "2024-10-25T08:30:45Z",
  "result": {
    "file_format": "step",
    "file_size_bytes": 1548290,
    "download_url": "https://storage.internal.cad/exports/cad_8f93b2a1c0d4.step?signature=...",
    "expires_at": "2024-10-25T09:30:45Z"
  },
  "error": null
}

Notifikasi Penyelesaian via Webhook Terverifikasi HMAC

Polling berkala menghabiskan throughput API gateway jika durasi pemodelan mencapai hitungan menit. Pola yang lebih efisien adalah menyediakan webhook callback, di mana server CAD mengirimkan HTTP POST ke endpoint klien ketika state mencapai completed atau failed.

Keamanan Webhook dengan Signature HMAC-SHA256

Untuk memastikan bahwa panggilan webhook berasal dari sistem resmi dan muatannya belum dimanipulasi, server menyertakan header signature berbasis HMAC (Hash-based Message Authentication Code). Klien wajib memvalidasi signature sebelum memproses payload.

import hmac
import hashlib

def generate_webhook_signature(payload_bytes: bytes, secret: str) -> str:
    return hmac.new(secret.encode("utf-8"), payload_bytes, hashlib.sha256).hexdigest()

def verify_webhook_signature(payload_bytes: bytes, secret: str, signature_header: str) -> bool:
    expected_signature = generate_webhook_signature(payload_bytes, secret)
    # Menggunakan compare_digest untuk memitigasi serangan timing attack
    return hmac.compare_digest(expected_signature, signature_header)

# Self-check assertion
if __name__ == "__main__":
    test_secret = "cad_sec_prod_9981240182"
    test_body = b'{"job_id":"cad_8f93b2a1c0d4","status":"completed"}'
    sig = generate_webhook_signature(test_body, test_secret)
    assert verify_webhook_signature(test_body, test_secret, sig) is True, "Signature validation failed"
    assert verify_webhook_signature(test_body, test_secret, "tampered_signature") is False

verify_webhook_signature() → skipped: [replay prevention timestamp header check], add when [payload is transmitted over public non-mTLS network].

Failure Mode & Edge Cases

  • Worker Crash Saat Processing: Jika worker mati mendadak (misalnya akibat OOM crash saat operasi meshing), job akan terkunci dalam status processing. Mitigasi: Terapkan heartbeat TTL pada job record. Jika heartbeat berhenti diperbarui selama ambang waktu tertentu, supervisor service otomatis memindahkan status job ke failed atau mengembalikan job ke antrean untuk retry dengan batas percobaan (maksimal 1 retry).
  • Kesalahan Floating-Point Non-Deterministik: Operasi kernel CAD pada geometri tertentu dapat menghasilkan error jika parameter berada tepat di batas toleransi toleransi B-Rep (misal: 1e-7 mm). Validasi batas toleransi input pada schema validation layer sebelum memasukannya ke pipeline komputasi.