Kontrak API untuk capability discovery perangkat sering gagal bukan karena datanya kurang, tetapi karena responsnya terlalu dekat ke detail teknis mentah. Klien akhirnya harus menebak: apakah perangkat bisa mengisi daya cepat, mengirim video, membaca data, atau hanya terdeteksi secara fisik tanpa fungsi yang benar-benar usable.
Solusi yang lebih baik adalah membuat API yang menjelaskan kemampuan nyata perangkat dalam bahasa domain yang jelas, sambil tetap menyimpan detail teknis sebagai bukti dan konteks. Dengan pendekatan ini, frontend, integrator, dan sistem downstream tidak perlu memahami seluruh kompleksitas hardware hanya untuk menjawab pertanyaan sederhana seperti "apa yang benar-benar bisa dilakukan perangkat ini?"
Masalah utama: status teknis mentah tidak cukup
Respons seperti berikut sering terlihat masuk akal, tetapi membingungkan saat dipakai aplikasi:
{
"connected": true,
"pd": true,
"alt_mode": false,
"usb_mode": "device",
"vendor_id": "0x1234"
}Masalahnya, respons ini memaksa klien menerjemahkan sendiri arti kombinasi field tersebut. Apakah artinya laptop bisa di-charge? Apakah monitor bisa menerima video? Apakah aksesori hanya bisa memberi daya tanpa data?
Untuk capability discovery, kontrak API yang baik harus menjawab pertanyaan bisnis atau UX secara langsung, misalnya:
- Daya: apakah perangkat dapat menerima daya, memberi daya, atau keduanya?
- Data: apakah data USB tersedia, dan dalam peran apa?
- Video: apakah output video mungkin, terverifikasi, atau tidak didukung?
- Kepercayaan hasil: apakah ini hasil pasti, inferensi, atau masih unknown?
Prinsip penting: jangan jadikan klien sebagai parser hardware. Klien seharusnya membaca capability yang sudah dinormalisasi, bukan menyusun aturan inferensi sendiri.
Prinsip desain kontrak API untuk capability discovery perangkat
1. Pisahkan fakta mentah, capability ternormalisasi, dan kesimpulan turunan
Satu respons idealnya memiliki tiga lapisan:
- Raw observations: data rendah tingkat seperti descriptor, identitas chip, sinyal protokol, hasil probe, atau metadata scan.
- Normalized capabilities: kemampuan yang sudah dinyatakan dalam vocabulary stabil dan mudah dipakai klien.
- Derived summary: ringkasan atau verdict yang disusun dari capability, untuk kebutuhan UI atau keputusan cepat.
Dengan pemisahan ini, backend tetap bisa berkembang tanpa memaksa klien bergantung pada detail scanning yang rapuh.
2. Gunakan vocabulary capability yang stabil
Jangan gunakan nama capability yang terlalu dekat dengan implementasi hardware saat nama itu mudah berubah. Lebih aman mendefinisikan vocabulary berbasis fungsi nyata, misalnya:
power.sinkpower.sourcedata.usb_devicedata.usb_hostvideo.outputaudio.output
Nama seperti ini lebih stabil dibanding field seperti supports_pd_rev3 jika yang ingin diketahui klien sebenarnya adalah kemampuan fungsional, bukan detail negosiasi protokol tertentu.
3. Representasikan unknown sebagai state resmi
Pada integrasi hardware, unknown bukan error. Unknown bisa berarti data belum tersedia, scan parsial, perangkat ambigu, atau metadata dari vendor tidak lengkap.
Jika Anda memaksa unknown menjadi false, klien akan menganggap fitur tidak didukung padahal sebenarnya belum bisa dipastikan. Ini sumber bug yang sangat umum.
Gunakan state eksplisit seperti:
supportednot_supportedunknowndegradedataulimitedjika perlu
Skema response yang praktis dan tidak membingungkan
Berikut contoh struktur respons untuk endpoint discovery:
{
"device_id": "dev_01HV7Z9Q8M4A",
"schema_version": "2025-01",
"resource_version": 12,
"observed_at": "2026-07-15T09:12:44Z",
"scan_status": "complete",
"capabilities": {
"power": {
"sink": {
"state": "supported",
"confidence": "high"
},
"source": {
"state": "not_supported",
"confidence": "medium"
}
},
"data": {
"usb_device": {
"state": "supported",
"confidence": "high"
},
"usb_host": {
"state": "unknown",
"confidence": "low",
"reason_code": "insufficient_hardware_data"
}
},
"video": {
"output": {
"state": "unknown",
"confidence": "low",
"reason_code": "alt_mode_not_observable"
}
}
},
"derived": {
"user_facing_summary": "Perangkat dapat menerima daya dan mendukung data USB device, tetapi dukungan video belum dapat dipastikan.",
"recommended_actions": [
"retry_scan_after_reconnect",
"request_vendor_descriptor"
]
},
"evidence": {
"signals": [
{
"type": "usb_enumeration",
"value": "device_mode_detected"
},
{
"type": "power_negotiation",
"value": "sink_capability_observed"
}
]
},
"links": {
"refresh": "/devices/dev_01HV7Z9Q8M4A/discovery-jobs",
"self": "/devices/dev_01HV7Z9Q8M4A/capabilities"
}
}Field yang stabil vs field turunan
Tidak semua field memiliki tingkat kestabilan yang sama. Ini penting untuk kompatibilitas klien.
Field stabil adalah field yang sebaiknya dijaga kontraknya dalam jangka panjang:
device_idschema_versionresource_versionobserved_atscan_statuscapabilities.*.*.statecapabilities.*.*.confidencereason_codeyang terdokumentasi
Field derived boleh berubah lebih sering selama tidak merusak makna inti:
derived.user_facing_summaryderived.recommended_actionsevidence.signalstambahan
Praktiknya, klien sebaiknya mengambil keputusan dari field stabil, bukan dari teks ringkasan. Ringkasan berguna untuk UI atau debugging, tetapi bukan sumber kebenaran utama.
Normalisasi capability yang konsisten
Normalisasi berarti Anda mengubah banyak sinyal hardware menjadi vocabulary yang konsisten. Misalnya, beberapa vendor mungkin melaporkan mode video dengan cara berbeda, tetapi API tetap mengeluarkan field yang sama: capabilities.video.output.
Strategi yang efektif:
- Tentukan daftar capability publik yang kecil dan stabil.
- Map semua sumber data hardware ke capability publik itu di backend.
- Simpan detail vendor atau protokol di area
evidenceatau sistem internal, bukan sebagai field publik utama.
Keuntungan utamanya adalah frontend tidak perlu tahu apakah dukungan video terdeteksi dari descriptor, probe aktif, atau basis pengetahuan vendor.
Versioning, backward compatibility, dan perubahan skema
Versioning skema
Untuk kontrak seperti ini, versi skema lebih berguna daripada versi endpoint semata. Endpoint bisa tetap sama, tetapi payload memiliki schema_version agar klien tahu bentuk dan arti field yang diharapkan.
Pedoman aman:
- Perubahan non-breaking: menambah field baru, menambah evidence baru, menambah capability opsional baru.
- Perubahan breaking: mengganti nama field, mengubah arti state, memindahkan struktur capability utama, menghapus reason code yang sudah dipakai klien.
Jika ada perubahan breaking, gunakan versi skema atau media type baru, dan dukung masa transisi.
Menjaga backward compatibility
Beberapa aturan praktis:
- Jangan ubah makna
unknownmenjadinot_supported. - Jangan ubah tipe field, misalnya dari object menjadi string.
- Tambahkan field baru sebagai opsional.
- Dokumentasikan reason code sebagai enum terbuka jika memungkinkan, agar klien siap menghadapi nilai baru.
Jika Anda perlu menambah capability baru, misalnya audio.output, klien lama tetap bisa berjalan selama mereka mengabaikan field yang tidak dikenal.
Kapan perlu versioning endpoint?
Versioning endpoint terpisah biasanya masuk akal jika:
- Struktur payload berubah total.
- Model autentikasi berbeda.
- Representasi domain berubah signifikan.
Jika hanya menambah capability atau evidence, biasanya cukup dengan versi skema dalam payload dan dokumentasi changelog yang disiplin.
Desain endpoint: auth, cache, refresh scan, dan webhook
Endpoint discovery yang umum
GET /devices/{device_id}/capabilities
POST /devices/{device_id}/discovery-jobs
GET /discovery-jobs/{job_id}Pemisahan ini membuat GET capabilities tetap idempotent dan cacheable, sementara refresh scan dilakukan sebagai operasi terpisah.
Auth untuk endpoint discovery
Capability perangkat bisa terlihat tidak sensitif, tetapi dalam banyak sistem data ini bisa mengungkap model perangkat, pola penggunaan, atau fitur yang aktif. Karena itu, endpoint discovery tetap perlu auth yang jelas.
Pilihan yang umum:
- User-scoped token jika perangkat dimiliki user tertentu.
- Service-to-service token untuk integrasi backend internal.
- Scoped API key hanya jika kebutuhan sederhana dan bisa dibatasi per resource atau per tenant.
Hal yang sebaiknya dicek di authorization layer:
- Apakah caller berhak melihat perangkat ini?
- Apakah caller boleh memicu refresh scan?
- Apakah caller boleh melihat raw evidence atau hanya normalized capability?
Sering kali berguna memisahkan level akses: aplikasi frontend mungkin cukup melihat capabilities dan derived, sedangkan tim support internal bisa melihat evidence.
Cache dan ETag
Hasil capability discovery biasanya tidak berubah setiap detik, sehingga ETag sangat berguna untuk mengurangi traffic dan parsing yang tidak perlu.
Contoh respons:
HTTP/1.1 200 OK
ETag: "cap-dev_01HV7Z9Q8M4A-v12"
Cache-Control: private, max-age=30Lalu klien dapat mengirim:
GET /devices/dev_01HV7Z9Q8M4A/capabilities
If-None-Match: "cap-dev_01HV7Z9Q8M4A-v12"Jika tidak ada perubahan, server balas 304 Not Modified.
Praktik yang aman:
- Bangun ETag dari
resource_versionatau fingerprint payload stabil. - Gunakan
Cache-Control: privatejika datanya user-specific. - Jangan cache respons error ambigu terlalu lama.
Idempotency untuk refresh scan
Memicu scan ulang sering bersifat mahal atau memerlukan interaksi ke hardware nyata. Karena itu, endpoint refresh sebaiknya mendukung idempotency key.
POST /devices/dev_01HV7Z9Q8M4A/discovery-jobs
Idempotency-Key: 8c9a62a2-17b6-4e69-95d8-3b4b2d38f605
Content-Type: application/json
{
"reason": "user_requested_refresh"
}Jika request yang sama terkirim ulang karena timeout jaringan, server dapat mengembalikan job yang sama alih-alih membuat scan baru.
Ini penting untuk mencegah:
- duplikasi pekerjaan scan,
- beban berlebih ke adapter hardware,
- race condition pada pembaruan capability.
Webhook saat capability berubah
Jika klien perlu reaktif terhadap perubahan capability, webhook lebih efisien daripada polling agresif.
Contoh event:
{
"event_id": "evt_01J0CAPCHANGE",
"event_type": "device.capabilities.changed",
"occurred_at": "2026-07-15T09:15:02Z",
"device_id": "dev_01HV7Z9Q8M4A",
"resource_version": 13,
"changes": [
{
"path": "capabilities.video.output.state",
"old": "unknown",
"new": "supported"
}
],
"links": {
"capabilities": "/devices/dev_01HV7Z9Q8M4A/capabilities"
}
}Untuk webhook, pastikan ada:
- signature verification,
- retry dengan backoff,
- deduplication berdasarkan
event_id, - ordering yang tidak diasumsikan sempurna oleh konsumer.
Konsumer sebaiknya menganggap webhook sebagai sinyal perubahan, lalu mengambil state terbaru dari endpoint GET capabilities jika konsistensi absolut dibutuhkan.
Error model yang jelas dan dapat di-debug
Jangan gunakan respons error yang terlalu generik. Untuk integrasi capability discovery, detail error membantu klien memutuskan apakah harus retry, menunggu, atau memperbaiki input.
Contoh error model:
{
"error": {
"code": "discovery_in_progress",
"message": "Capability discovery is still running for this device.",
"retryable": true,
"retry_after_seconds": 10,
"details": {
"device_id": "dev_01HV7Z9Q8M4A",
"job_id": "job_01HV7ZBPK9"
}
}
}Contoh error lain yang berguna:
device_not_foundforbiddenhardware_data_unavailablediscovery_in_progressrefresh_rate_limitedambiguous_hardware_identity
Gunakan kode HTTP secara masuk akal:
400untuk request tidak valid,401untuk auth gagal,403untuk akses ditolak,404untuk device tidak ditemukan atau tidak terlihat oleh caller,409jika konflik status terjadi,422untuk perangkat dikenal tetapi input scan tidak cukup diproses,429untuk rate limit,503untuk dependency hardware atau discovery subsystem tidak tersedia.
Jangan memaksakan semua kegagalan menjadi 500, karena klien kehilangan konteks tindakan berikutnya.
Retry, data ambigu, dan edge case integrasi hardware
Data hardware tidak lengkap
Ini kondisi normal. Misalnya hanya ada identitas fisik perangkat, tetapi belum ada hasil negosiasi protokol atau enumerasi penuh. Dalam kasus ini:
- set capability ke
unknown, - isi
reason_code, - jika relevan beri
recommended_actions, - hindari menyimpulkan terlalu dini.
Contoh:
{
"state": "unknown",
"confidence": "low",
"reason_code": "awaiting_active_probe"
}Perangkat ambigu atau multi-fungsi
Satu aksesori bisa berubah perilaku tergantung host, firmware, sumber daya, atau kabel lain di antaranya. Karena itu, capability tidak selalu mutlak. Ada dua pendekatan:
- State tunggal + confidence: sederhana untuk klien.
- State + conditions: lebih akurat jika perilaku bergantung konteks.
Jika perlu menyatakan syarat, lakukan secara eksplisit:
{
"video": {
"output": {
"state": "supported",
"confidence": "medium",
"conditions": [
"requires_compatible_host",
"requires_alt_mode_support"
]
}
}
}Ini lebih jujur daripada menyatakan supported tanpa konteks lalu membuat pengguna salah paham.
Retry strategy
Retry tidak boleh buta. Bedakan antara retry yang masuk akal dan kondisi terminal.
Layak retry:
- scan masih berjalan,
- dependency sementara tidak tersedia,
- probe timeout,
- webhook delivery gagal sementara.
Tidak layak retry tanpa perubahan input:
- device tidak punya izin akses,
- format request salah,
- identitas perangkat bertabrakan dan perlu intervensi,
- perangkat secara pasti tidak mendukung capability tertentu.
Gunakan retryable dan retry_after_seconds dalam error model jika memungkinkan. Ini lebih baik daripada memaksa integrator menebak.
Race condition saat scan dan read berjalan bersamaan
Kasus umum: klien memicu refresh, lalu segera membaca endpoint capability. Jika backend belum selesai memperbarui state, klien bisa melihat data lama.
Beberapa strategi untuk mengatasinya:
- Kembalikan
scan_statusdanobserved_atagar klien tahu freshness data. - Sediakan endpoint job status terpisah.
- Naikkan
resource_versionhanya saat state final dipublikasikan. - Gunakan webhook untuk memberi tahu perubahan final.
Contoh desain endpoint yang konsisten
Mengambil capability terbaru
GET /devices/dev_01HV7Z9Q8M4A/capabilitiesRespons:
{
"device_id": "dev_01HV7Z9Q8M4A",
"schema_version": "2025-01",
"resource_version": 12,
"observed_at": "2026-07-15T09:12:44Z",
"scan_status": "complete",
"capabilities": {
"power": {
"sink": { "state": "supported", "confidence": "high" }
},
"data": {
"usb_device": { "state": "supported", "confidence": "high" }
},
"video": {
"output": {
"state": "unknown",
"confidence": "low",
"reason_code": "alt_mode_not_observable"
}
}
}
}Memulai refresh scan
POST /devices/dev_01HV7Z9Q8M4A/discovery-jobs
Idempotency-Key: 8c9a62a2-17b6-4e69-95d8-3b4b2d38f605
{
"reason": "hardware_reconnected"
}Respons:
{
"job_id": "job_01HV7ZBPK9",
"status": "queued",
"device_id": "dev_01HV7Z9Q8M4A",
"created_at": "2026-07-15T09:13:00Z"
}Melihat status scan
GET /discovery-jobs/job_01HV7ZBPK9Respons:
{
"job_id": "job_01HV7ZBPK9",
"device_id": "dev_01HV7Z9Q8M4A",
"status": "running",
"started_at": "2026-07-15T09:13:02Z",
"last_update_at": "2026-07-15T09:13:06Z"
}Common mistakes yang sering membuat kontrak discovery membingungkan
- Mengekspos terlalu banyak raw field tetapi tidak memberi capability yang sudah dinormalisasi.
- Menyamakan unknown dengan false, sehingga UI menampilkan "tidak didukung" padahal belum pasti.
- Mengandalkan teks summary sebagai kontrak utama, bukan field terstruktur.
- Tidak mendefinisikan reason code, sehingga semua kegagalan terlihat sama.
- Tidak ada resource version atau ETag, membuat polling mahal dan sulit dioptimalkan.
- Refresh scan tidak idempotent, mengakibatkan duplicate job.
- Webhook dianggap authoritative tanpa re-fetch, padahal delivery bisa out of order.
- Breaking change diam-diam pada nama field atau makna state.
Checklist implementasi backend
- Tentukan vocabulary capability publik yang kecil, jelas, dan stabil.
- Pisahkan raw observations, normalized capabilities, dan derived summary.
- Gunakan state eksplisit:
supported,not_supported,unknown. - Tambahkan
confidencedanreason_codeuntuk hasil yang tidak pasti. - Sediakan
schema_version,resource_version,observed_at, danscan_status. - Jaga field stabil agar tidak berubah makna tanpa versioning.
- Gunakan endpoint terpisah untuk baca capability dan memicu refresh scan.
- Dukung
Idempotency-Keypada endpoint refresh. - Implementasikan ETag dan conditional request untuk endpoint GET.
- Gunakan auth yang scoped per device atau tenant.
- Batasi akses ke evidence mentah jika bersifat sensitif atau internal.
- Sediakan error model terstruktur dengan
code,message, dan indikator retry. - Tambahkan webhook
device.capabilities.changedbila integrasi butuh notifikasi. - Pastikan webhook punya signature, retry, dan deduplication.
- Dokumentasikan edge case: data tidak lengkap, konflik identitas, scan parsial, timeout probe.
- Uji backward compatibility dengan consumer contract test.
- Pastikan klien dapat mengabaikan field baru tanpa rusak.
- Catat changelog skema secara disiplin.
Penutup
Kontrak API untuk capability discovery perangkat yang baik tidak sekadar melaporkan apa yang terdeteksi, tetapi menjelaskan apa yang benar-benar bisa dilakukan perangkat secara jelas, stabil, dan jujur terhadap ketidakpastian. Kuncinya adalah normalisasi capability, unknown state yang eksplisit, pemisahan field stabil dan turunan, serta desain operasional yang matang: auth, cache, idempotency, webhook, dan error model yang dapat diandalkan.
Jika Anda merancang discovery API seperti ini sejak awal, frontend tidak perlu menebak-nebak, integrator tidak perlu menulis logika inferensi sendiri, dan backend punya ruang untuk berkembang tanpa merusak klien lama.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!