Pada Proyek: Pipeline Data CSV ke PostgreSQL, kita mulai dari berkas CSV yang bisa dibuka langsung dengan editor teks. API tidak semudah itu: data hanya bisa dilihat lewat permintaan HTTP, respons dipotong per halaman, dan bentuk payload bisa berubah tanpa pengumuman. Sebelum menulis satu baris kode pipeline, kita harus memahami kontrak API sumber secara persis. Bagian ini membedah kontrak API pesanan TokoKita dan menyiapkan server tiruannya di lokal.
Kontrak Endpoint #
Dokumentasi ringkas API tersimpan di config/api.example.yaml sebagai rujukan tim ingestion. Kutipan berikut merangkum endpoint beserta batasannya:
endpoint:
- path: "/api/v1/pesanan"
metode: "GET"
parameter:
page: "nomor halaman, mulai dari 1 (default 1)"
per_page: "baris per halaman, 1-100 (default 50)"
updated_since: "ISO 8601; hanya pesanan dengan updated_at lebih baru"
respons: "envelope {data: [...], meta: {page, per_page, total_records, total_pages}}"
- path: "/api/v1/health"
metode: "GET"
respons: "{\"status\": \"ok\"}"
batasan:
rate_limit: "permintaan berlebih dibalas 429 dengan header Retry-After (detik)"
galat_sementara: "500 sesekali terjadi; klien wajib mencoba ulang dengan backoff"
format_waktu: "ISO 8601 dengan offset UTC, contoh 2024-01-05T10:30:00+00:00"Tiga parameter pada GET /api/v1/pesanan menopang sebagian besar proyek: page dan per_page untuk pagination pada Bagian 3, serta updated_since untuk mode incremental pada Bagian 8. Autentikasi memakai header X-API-Key dengan nilai placeholder YOUR_API_TOKEN sesuai kebiasaan menyimpan kredensial.
Menjalankan API Lokal #
Server tiruan dibangun di src/fake_api.py memakai http.server dari stdlib, tanpa dependensi eksternal. Jalankan server pada satu terminal:
python3 -m src.fake_apiOutput yang Dihasilkan:
Fake API TokoKita berjalan di http://127.0.0.1:8765
Dataset: 137 pesanan. Tekan Ctrl+C untuk berhenti.Dari terminal lain, kita bisa memeriksa respons API persis seperti API publik yang sesungguhnya:
curl -s "http://127.0.0.1:8765/api/v1/pesanan?per_page=2" | python3 -m json.toolOutput yang Dihasilkan:
{
"data": [
{
"id": 1,
"pelanggan": "Budi Santoso",
"email": "[email protected]",
"total": 1151500,
"status": "dibayar",
"created_at": "2024-01-01T08:20:00+00:00",
"updated_at": "2024-01-01T08:49:00+00:00"
},
{
"id": 2,
"pelanggan": "Siti Rahayu",
"email": "[email protected]",
"total": 2258500,
"status": "baru",
"created_at": "2024-01-01T08:52:00+00:00",
"updated_at": "2024-01-01T09:28:00+00:00"
}
],
"meta": {
"page": 1,
"per_page": 2,
"total_records": 137,
"total_pages": 69
}
}Server juga menolak parameter yang tidak valid, sama seperti API produksi yang memvalidasi masukan:
curl -s "http://127.0.0.1:8765/api/v1/pesanan?per_page=banyak"Output yang Dihasilkan:
{"error": "bad_request", "field": "page/per_page"}Anatomi Respons #
Setiap respons terdiri dari envelope data dan meta. Array data berisi baris pesanan dengan struktur berikut:
| Field | Tipe | Contoh | Keterangan |
|---|---|---|---|
id | integer | 1 | Identitas pesanan, unik |
pelanggan | string | "Budi Santoso" | Nama pembeli |
email | string | "[email protected]" | Kontak pembeli |
total | number | 1151500 | Nilai pesanan dalam rupiah |
status | string | "dibayar" | Salah satu dari lima status tetap |
created_at | string ISO 8601 | "2024-01-01T08:20:00+00:00" | Waktu pesanan dibuat |
updated_at | string ISO 8601 | "2024-01-01T08:49:00+00:00" | Waktu terakhir pesanan berubah |
Objek meta memberi tahu posisi kita dalam pagination: page adalah halaman saat ini, per_page jumlah baris per halaman, total_records total seluruh pesanan, dan total_pages jumlah halaman penuh. Struktur updated_at inilah yang nanti menjadi kunci mode incremental. Chapter Ingestion dari API membahas pola envelope semacam ini secara lebih umum.
Dataset Deterministik #
Data 137 pesanan dibangkitkan oleh fungsi buat_pesanan dengan seed tetap, sehingga setiap orang yang menjalankan server menerima dataset yang identik:
def buat_pesanan(jumlah=137, seed=42, mulai_id=1, mulai_waktu=None):
"""Buat dataset pesanan deterministik (seed sama = data sama)."""
rng = random.Random(seed)
waktu_mulai = mulai_waktu or MULAI_DATA
hasil = []
for i in range(jumlah):
pid = mulai_id + i
dibuat = waktu_mulai + timedelta(minutes=45 * i + rng.randint(0, 30))
diubah = dibuat + timedelta(minutes=rng.randint(1, 240))
nama = rng.choice(NAMA_PELANGGAN)
hasil.append({
"id": pid,
"pelanggan": nama,
"email": f"{nama.split()[0].lower()}.{pid:04d}@example.com",
"total": rng.randrange(25_000, 2_500_000, 500),
"status": rng.choice(STATUS_PESANAN),
"created_at": dibuat.isoformat(),
"updated_at": diubah.isoformat(),
})
return hasilMari kita telaah baris-baris kode di atas:
- Baris 29 (
rng = random.Random(seed)): Pembangkit acak dibuat dengan seed tetap; urutan nilai acak yang sama akan keluar setiap eksekusi. - Baris 34-35 (
dibuatdandiubah):created_atbergerak 45 menit per pesanan sejak 1 Januari 2024, danupdated_atselalu 1-240 menit setelahnya, meniru pesanan yang kadang direvisi. - Baris 40 (
email = ...): Email dibentuk dari nama depan plusidagar unik dan jelas fiktif di domainexample.com. - Baris 41 (
total): Nilai pesanan acak antara 25 ribu hingga 2,5 juta rupiah dengan kelipatan 500. - Baris 43-44 (
isoformat()): Semua stempel waktu memakai format ISO 8601 seragam dengan offset UTC, konsekuensi pentingnya dibahas pada Bagian 8.
Perilaku Khusus Server #
Kelas FakeTokoKitaAPI menerima opsi perilaku lewat konstruktor: rate_limit_first membalas 429 berikut header Retry-After untuk N permintaan pertama, fail_first membalas 500 untuk N permintaan pertama, dan mutate_record mengubah payload sebelum dikirim. Opsi tersebut dipakai bertahap pada Bagian 4, 5, dan 6. Dasar mekanisme handler server ini mengikuti dokumentasi resmi http.server
.
Dengan kontrak API yang jelas dan sumber data lokal yang stabil, kita siap menulis sisi konsumen: klien HTTP yang bertanggung jawab atas satu permintaan yang andal, yang akan kita bangun pada bagian berikutnya.