DEV Community

Cover image for Domain Modeling dengan TypeScript: Ukuran, Varian Bahan, dan Grading Pola yang Type-Safe
Mightyblue
Mightyblue

Posted on

Domain Modeling dengan TypeScript: Ukuran, Varian Bahan, dan Grading Pola yang Type-Safe

Bug paling mahal yang pernah saya perbaiki bukan soal race condition atau memory leak. Bug itu berbunyi: size: "L". Order 800 pcs seragam wearpack dikirim dengan ukuran standar Asia, padahal klien memakai standar Eropa. Satu string polos, satu asumsi tak tertulis, satu produksi ulang. Dan ironisnya, tahun lalu TypeScript justru naik menjadi bahasa paling banyak dipakai di GitHub — tetapi mayoritas kita masih memakainya sebatas interface Product { size: string }. Di situlah domain modeling dengan TypeScript berhenti jadi teori dan mulai menyelamatkan uang orang.

Klaim bahwa "TypeScript otomatis bikin bebas bug" perlu diuji, bukan diamini. Studi Bogner & Merkel di MSR 2022 yang menambang 604 repositori GitHub dengan lebih dari 16 juta baris kode menemukan proyek TS memang unggul di code quality dan understandability — tapi tidak otomatis lebih sedikit bug, dan korelasinya melemah seiring pemakaian tipe any. Artinya nilai sesungguhnya ada di pemodelan, bukan sekadar kompilasi. Tema ini saya angkat karena developer yang menggarap sistem UKM manufaktur nyaris tak punya rujukan berbahasa Indonesia soal memodelkan domain fisik ke dalam type system.

Dan domain fisik itu keras kepala.

Kain tidak peduli pada strict: true.

Mesin potong tidak membaca changelog Anda.

Yang bisa Anda kendalikan cuma satu: seberapa sulit kode Anda membiarkan kesalahan itu lolos ke lantai produksi.

"Type system yang baik bukan tentang mencegah error kompilasi. Ia tentang membuat kondisi yang mustahil di dunia nyata menjadi mustahil pula untuk ditulis."

1. Kenapa Tipe string Adalah Musuh Terbesar Data Produksi

Sebelum bicara pattern canggih, kita perlu jujur soal titik awalnya. Hampir semua sistem konveksi, garmen, dan manufaktur kecil yang pernah saya audit dimulai dari model data yang sama: semuanya string, semuanya number, semuanya boleh diisi apa saja.

Anatomi bug yang tidak pernah masuk issue tracker

Perhatikan model "aman" ini:

interface OrderItem {
  productName: string;
  size: string;
  material: string;
  quantity: number;
}
Enter fullscreen mode Exit fullscreen mode

Compiler bahagia. QA lolos. Tapi semua ini valid secara tipe:

const item: OrderItem = {
  productName: "Seragam ESD",
  size: "l",          // huruf kecil, beda dari "L" di database
  material: "katun",  // padahal ESD wajib bahan konduktif
  quantity: -50,      // kuantitas negatif
};
Enter fullscreen mode Exit fullscreen mode

Tiga kesalahan fatal. Nol error. Compiler tidak pernah diberi tahu aturannya.

Bukti empiris: tipe bukan peluru perak

Inilah yang selaras dengan temuan riset di pendahuluan tadi. TypeScript menurunkan code smell dan memperbaiki keterbacaan, tetapi tidak menurunkan bug secara ajaib. Sebabnya masuk akal: string dan number adalah tipe primitif yang tidak membawa satu pun aturan bisnis. Selama domain tidak diterjemahkan, type checker hanya memverifikasi bentuk, bukan makna.

Praktik domain modeling dengan TypeScript justru dimulai ketika kita berhenti mendeskripsikan data, dan mulai mengkodifikasi aturan.

2. Membedah Domain Konveksi: Tiga Entitas yang Paling Sering Salah Model

Bab ini memakai domain nyata sebagai bahan bedah. Referensi produknya saya ambil dari lini order produsen seragam kerja dan pakaian industri di Karawang — mulai dari wearpack, seragam laboratorium, sampai pakaian ESD anti-statis untuk pabrik elektronik. Kompleksitasnya representatif: satu produk bisa punya puluhan kombinasi valid dan ratusan kombinasi terlarang.

Ukuran: satuan yang butuh identitas, bukan label

Ukuran punya dua dimensi tersembunyi: standar dan skala. Solusinya adalah branded type agar dua ukuran dari standar berbeda tidak bisa saling dipertukarkan.

type SizeStandard = "ASIA" | "EU" | "US";

type Size<S extends SizeStandard> = {
  readonly __brand: S;
  readonly label: "S" | "M" | "L" | "XL" | "XXL";
  readonly chestCm: number;
};

const asiaL: Size<"ASIA"> = { __brand: "ASIA", label: "L", chestCm: 104 };
const euL: Size<"EU">     = { __brand: "EU",   label: "L", chestCm: 112 };

function cutPattern(size: Size<"ASIA">) { /* ... */ }

cutPattern(euL); // ❌ Error — persis kesalahan senilai 800 pcs itu
Enter fullscreen mode Exit fullscreen mode

Satu baris __brand menutup seluruh kelas kesalahan.

Varian bahan: aturan kompatibilitas sebagai discriminated union

Bahan tidak bebas dipasangkan dengan sembarang produk. Seragam ESD wajib memakai kain konduktif; wearpack butuh kain tahan gesek. Aturan seperti ini paling rapi dimodelkan sebagai discriminated union.

type Garment =
  | { kind: "esd"; fabric: "carbon-grid" | "conductive-polyester"; ohmClass: 1 | 2 | 3 }
  | { kind: "wearpack"; fabric: "drill" | "canvas"; reflectiveTape: boolean }
  | { kind: "office"; fabric: "oxford" | "american-drill"; collar: "regular" | "wing" };

function estimateCost(g: Garment): number {
  switch (g.kind) {
    case "esd":      return baseEsd(g.ohmClass);
    case "wearpack": return baseWearpack(g.reflectiveTape);
    case "office":   return baseOffice(g.collar);
    default: {
      const exhaustive: never = g; // ✅ tambah varian baru → compile error
      return exhaustive;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Menambah jenis produk baru? Compiler langsung menunjuk setiap tempat yang belum diperbarui. Ini yang tidak akan pernah diberikan oleh material: string.

Grading pola: matematika yang harus dijaga oleh tipe

Grading adalah proses menaikkan/menurunkan pola dari ukuran dasar. Kesalahan increment berarti seluruh batch salah potong.

type GradeRule = Readonly<{
  from: Size<"ASIA">["label"];
  to: Size<"ASIA">["label"];
  deltaChestCm: number;   // wajib positif untuk grade-up
  deltaLengthCm: number;
}>;

const GRADE_TABLE = [
  { from: "M", to: "L",  deltaChestCm: 4, deltaLengthCm: 2 },
  { from: "L", to: "XL", deltaChestCm: 4, deltaLengthCm: 2 },
] as const satisfies readonly GradeRule[];
Enter fullscreen mode Exit fullscreen mode

Kata kunci as const satisfies memberi dua hal sekaligus: validasi terhadap kontrak, dan literal type yang presisi untuk autocomplete.

Aspek Domain Model Naif Model Type-Safe Kelas Bug yang Hilang
Ukuran size: string Size<"ASIA"> (branded) Salah standar ukuran lintas negara
Bahan material: string Discriminated union per produk Kombinasi bahan–produk terlarang
Grading delta: number as const satisfies + readonly Increment salah / tabel termutasi
Kuantitas qty: number PositiveInt (branded) Nilai nol atau negatif
Status order status: string Union + exhaustive never State transition tak tertangani

3. Playbook Lima Langkah: Memindahkan Aturan Bisnis ke Type System

Bagian ini sengaja disusun sebagai prosedur berurutan agar bisa langsung direplikasi pada domain Anda sendiri — entah itu konveksi, furnitur, atau fabrikasi logam. Estimasi waktu untuk iterasi pertama: dua sampai tiga hari kerja.

Langkah 1 — Kumpulkan ubiquitous language. Duduk bersama kepala produksi dan admin order. Catat istilah asli yang mereka pakai: grading, marker, cutting loss, sampling. Nama tipe wajib memakai istilah itu, bukan terjemahan developer.

Langkah 2 — Tandai setiap primitif yang punya aturan. Sisir model lama. Setiap string/number yang sebenarnya punya batasan adalah kandidat branded type atau union literal.

Langkah 3 — Ubah kondisi mustahil jadi tak bisa diketik. Terapkan discriminated union untuk relasi, dan pastikan setiap switch ditutup dengan pengecekan never.

Langkah 4 — Pasang pagar di batas sistem. Data dari form, Excel, atau API eksternal masuk sebagai unknown. Validasi dengan Zod atau Valibot, lalu barulah parse menjadi tipe domain — bukan sekadar cast.

Langkah 5 — Ukur, jangan sekadar merasa aman. Aktifkan noUncheckedIndexedAccess, hitung rasio any dengan type-coverage, dan jadikan angkanya bagian dari CI.

Lima langkah. Tanpa framework baru. Tanpa migrasi database.

Yang berubah cuma satu: aturan bisnis pindah dari kepala orang ke dalam compiler.

4. Anti-Pattern yang Berulang di Codebase Manufaktur

Setelah beberapa proyek, ada pola kegagalan yang muncul berulang kali. Menghindari empat hal berikut memberi dampak lebih besar daripada menambah generic canggih.

Yang harus dihentikan

  • any sebagai jalan pintas deadline. Riset MSR tadi menunjukkan korelasi jelas: makin sering any, makin hilang keuntungan kualitasnya. Gunakan unknown lalu persempit.
  • Type assertion menggantikan validasi. data as Order hanya membungkam compiler; ia tidak pernah memeriksa apa pun saat runtime.
  • Enum numerik untuk status produksi. Union literal lebih ringan, lebih mudah diserialisasi, dan tampil apa adanya di log.
  • Satu tipe raksasa untuk semua konteks. Model untuk kuotasi, produksi, dan pengiriman punya invariant berbeda. Pisahkan bounded context-nya.

Yang layak diadopsi lebih awal

Kalau ingin memperdalam fondasi teorinya, artikel LogRocket di Dev.to tentang pemanfaatan TypeScript untuk domain-driven design adalah bacaan lanjutan yang solid — membahas entity, value object, dan aggregate dengan contoh yang bisa langsung dipetakan ke domain apa pun.

Satu catatan tambahan yang relevan di 2026: tipe yang ketat kini juga berfungsi sebagai guardrail untuk kode hasil AI. Ketika agent coding menulis fungsi baru di modul produksi Anda, type system-lah yang pertama kali menolak asumsi kelirunya. Inilah alasan domain modeling dengan TypeScript justru makin bernilai di era agentic coding, bukan makin usang.

FAQ Seputar Domain Modeling dengan TypeScript

Apakah pendekatan ini overkill untuk aplikasi internal kecil?
Tergantung biaya kesalahan. Untuk to-do list, ya. Untuk sistem yang satu kesalahan datanya berarti produksi ulang ratusan potong pakaian, jelas tidak.

Branded type bikin kode jadi verbose. Solusinya?
Sembunyikan di helper constructor (createAsiaSize()), lalu ekspor hanya fungsi itu. Pemakai API tidak perlu menyentuh properti brand sama sekali.

Apakah tipe ini bertahan sampai runtime?
Tidak — TypeScript terhapus saat kompilasi. Karena itu langkah 4 (validasi di batas sistem) wajib, bukan opsional.

Migrasi codebase JavaScript lama harus mulai dari mana?
Mulai dari modul yang paling sering menimbulkan insiden, biasanya perhitungan harga dan konversi ukuran. Aktifkan strict mode secara bertahap per direktori.

Bagaimana kalau aturan bisnisnya sering berubah?
Justru itu keunggulannya. Ubah tipenya, jalankan tsc, dan compiler akan memberi daftar lengkap tempat yang perlu disesuaikan.

Ketika Compiler Menjadi Rekan Kerja Kepala Produksi

Pada akhirnya, semua teknik di atas bermuara pada satu gagasan lama yang diucapkan dengan sangat lugas oleh Anders Hejlsberg, arsitek utama TypeScript:

"if you don't have types, you're going to suffer."

Hejlsberg adalah perancang Turbo Pascal, arsitek utama C#, dan orang yang pada 2012 memutuskan untuk tidak menggantikan JavaScript, melainkan menambahkan lapisan tipe di atasnya. Kalimatnya lahir dari pengamatan bahwa perangkat lunak selalu dimulai kecil lalu tumbuh besar — dan saat sistem membesar tanpa tipe, biaya perubahan naik jauh lebih cepat daripada kemampuan tim mengingat aturannya. Dalam konteks manufaktur, "besar" itu bukan metafora: ia berbentuk ribuan potong kain yang sudah terlanjur

Top comments (0)