Belajar Kotlin - Metaprogramming & DSL
Episode 16 of 23

Belajar Kotlin - Metaprogramming & DSL

Episode ini menguasai metaprogramming dan DSL di Kotlin: membangun domain-specific language dengan sintaks Kotlin, type-safe builders dengan lambda receiver, annotation processing dengan kapt dan KSP, serta reflection dan fitur compile-time yang dipakai library modern.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Salah satu alasan Kotlin begitu ekspresif adalah kemampuannya membangun Domain-Specific Language (DSL) — bahasa kecil di dalam bahasa yang cocok untuk satu domain. Episode 16 membahas type-safe builders, lambda receiver, annotation processing, dan reflection.

DSL di Kotlin bukan magis: mereka dibangun dari fitur bahasa biasa seperti extension functions dan lambda dengan receiver. Framework populer seperti Gradle Kotlin DSL, Ktor routing, dan Compose semuanya adalah DSL.

Setelah episode ini, kalian akan memahami cara kerja DSL dari dalam dan tahu kapan harus membangunnya.

Membangun DSL dengan Sintaks Kotlin

DSL: Bahasa untuk Satu Domain

DSL membuat API membaca seperti dokumen konfigurasi. Contoh paling dikenal di ekosistem Kotlin adalah Gradle Kotlin DSL dan Ktor routing — keduanya memakai blok bersarang yang terlihat seperti konfigurasi, padahal itu kode Kotlin biasa.

Kunci untuk membangun DSL: kombinasi lambda dengan receiver, extension functions, dan fungsi bernama intuitif. Ketiganya sudah kalian kuasai di episode 3, 4, dan 7.

DSL Sederhana untuk Konfigurasi

Mari membangun DSL kecil untuk konfigurasi server:

KotlinDSL dasar
class ServerConfig {
    var host: String = "localhost"
    var port: Int = 8080
    val routes = mutableListOf<String>()
 
    fun route(path: String) {
        routes.add(path)
    }
}
 
fun server(blok: ServerConfig.() -> Unit): ServerConfig {
    val config = ServerConfig()
    config.blok()
    return config
}

Fungsi server menerima lambda dengan receiver ServerConfig. Pemakai DSL bisa menulis server { host = ...; route("/") } dan semua panggilan dieksekusi dalam konteks instance config.

Memakai DSL

KotlinMemakai DSL server
val cfg = server {
    host = "0.0.0.0"
    port = 9000
    route("/api/v1")
    route("/health")
}
 
println("${cfg.host}:${cfg.port}")

Pemakaian di atas terlihat seperti deklarasi konfigurasi, padahal server { ... } adalah fungsi Kotlin biasa. Inilah kekuatan DSL: API domain terbaca alami tanpa parsing bahasa lain. Kalian bisa menguji DSL ini dengan kotlinc Main.kt -include-runtime -d dsl.jar lalu menjalankan JAR-nya.

Type-safe Builders dan Lambda Receiver

Receiver yang Menyediakan Konteks

Lambda receiver membuat properti dan fungsi receiver tersedia langsung di dalam blok. Pada DSL server di atas, host dan route tersedia tanpa prefix karena receiver-nya ServerConfig. Compiler memastikan seluruh pemakaian valid — itulah "type-safe" dalam type-safe builder.

Membangun Hierarki Builder

DSL besar membangun hierarki dengan builder bersarang. Pola ini dipakai Compose dan Ktor:

KotlinHierarki builder
fun application(blok: Application.() -> Unit): Application {
    val app = Application()
    app.blok()
    return app
}
 
class Application {
    fun routing(blok: Routing.() -> Unit) {
        val r = Routing()
        r.blok()
        routes.add(r)
    }
}

routing membangun sub-builder dan menambahkannya ke induk. Setiap level membawa receiver sendiri, sehingga kalian bisa menulis blok routing { get("/") { ... } } tanpa menyebut objek secara eksplisit.

Annotation Processing: kapt dan KSP

kapt: Generasi Lama

kapt (Kotlin Annotation Processing Tool) memungkinkan processor annotation Java bekerja pada kode Kotlin. Ia mengompilasi Kotlin ke stub Java agar processor lama bisa berjalan:

KotlinMengaktifkan kapt
plugins {
    id("org.jetbrains.kotlin.kapt")
}
 
dependencies {
    kapt("com.example:annotation-processor:1.0")
}

Blok kapt(...) di dependencies menambahkan processor. kapt berfungsi tetapi lambat dan meniru Java; statusnya kini maintenance mode di JetBrains.

KSP: Penerus Modern

KSP (Kotlin Symbol Processing) dirancang khusus untuk Kotlin, memproses symbol langsung tanpa stub Java. Hasilnya kompilasi lebih cepat dan API yang memahami fitur Kotlin seperti data class dan nullable types:

KotlinMengaktifkan KSP
plugins {
    id("com.google.devtools.ksp") version "2.0.0-1.0.24"
}
 
dependencies {
    ksp("com.example:kotlin-processor:1.0")
}

KSP adalah standar baru untuk library seperti Room, Moshi, dan Arrow. Untuk project baru, pilih KSP kecuali library yang dipakai masih mengharuskan kapt. Episode 8 menyinggung bahwa kotlinx.serialization bahkan tidak memerlukan annotation processing sama sekali.

Reflection dan Fitur Compile-time

Reflection: Memeriksa Kode di Runtime

Reflection memungkinkan membaca struktur class saat runtime — nama fungsi, anotasi, dan properties:

KotlinReflection dasar
data class Produk(val nama: String)
 
fun main() {
    val props = Produk::class.memberProperties
    props.forEach { println("${it.name}: ${it.returnType}") }
}

Produk::class.memberProperties mengembalikan metadata properties class. Reflection fleksibel namun lambat di hot path — episode 15 sudah mengingatkan untuk menghindarinya di jalur kritis.

Metaprogramming Compile-time sebagai Alternatif

Untuk performa, lakukan metaprogramming di compile time, bukan runtime. Code generation via KSP atau plugin compiler menghasilkan kode yang cepat dan type-safe. Arah ekosistem Kotlin jelas: dari reflection runtime menuju code generation saat kompilasi.

Penutup

Episode 16 membuka metaprogramming dan DSL: membangun bahasa domain dengan lambda receiver, hierarki builder yang type-safe, perbedaan kapt dan KSP, serta refleksi dan code generation sebagai dua pendekatan metaprogramming.

Inti yang harus dibawa pulang:

  • DSL dibangun dari lambda receiver dan extension functions biasa.
  • Lambda receiver menyediakan konteks sehingga blok terbaca alami.
  • kapt menjembatani processor Java tetapi kini maintenance mode.
  • KSP adalah penerus modern yang memahami fitur Kotlin.
  • Reflection fleksibel namun lambat di hot path.
  • Code generation compile-time menggantikan reflection untuk performa.

Di episode 17 selanjutnya kita akan membahas interoperability dan migration — memanggil Java dari Kotlin dan sebaliknya, bekerja dengan codebase Java legacy, anotasi nullability dan keamanan tipe lintas batas, serta memigrasi project Java ke Kotlin secara bertahap.

Belajar Kotlin - Metaprogramming & DSL | Belajar Kotlin