Pengembangan backend dengan Kotlin didominasi dua framework: Ktor dari JetBrains dan Spring Boot dari VMware. Ktor coroutine-native dan ringan; Spring Boot matang dengan ekosistem luas. Pada chapter ini kita memetakan kedua framework, mendemokan pola MVC sederhana tanpa dependency eksternal, dan membandingkan kapan memilih salah satunya.

Pola MVC Backend #

Sebagian besar backend Kotlin mengikuti pola Model-View-Controller yang disederhanakan menjadi Routing-Service-Model. Tiga lapisan tersebut terlihat seperti ini:

LapisanTugasContoh KtorContoh Spring Boot
RoutingPetakan HTTP method + path ke handlerget("/users") { }@GetMapping("/users")
ServiceLogika bisnis + persistenceclass UserService@Service class UserService
ModelData class untuk request/responsedata class UserDtodata class UserDto

Demo Router dan Service #

Cuplikan berikut memodelkan router Ktor/Spring sebagai class Kotlin murni. Routing didefinisikan dengan get() dan post(), dispatch mencocokkan method dan path, lalu handler memanggil service layer:

kotlin
// panduan_backend.kt
// Tour singkat Panduan Backend Kotlin:
// - Ktor: https://ktor.io/docs/
// - Spring Boot: https://spring.io
//
// File ini mendemokan konsep server HTTP sederhana dan pola MVC
// tanpa dependency eksternal — dijalankan di JVM biasa.

import java.util.concurrent.ConcurrentHashMap

// 1. Routing — simulasi router Ktor/Spring
data class Route(val method: String, val path: String, val handler: (Map<String, String>) -> String)

class Router {
    private val routes = mutableListOf<Route>()

    fun get(path: String, handler: (Map<String, String>) -> String) {
        routes.add(Route("GET", path, handler))
    }

    fun post(path: String, handler: (Map<String, String>) -> String) {
        routes.add(Route("POST", path, handler))
    }

    fun dispatch(method: String, path: String, params: Map<String, String> = emptyMap()): String {
        val route = routes.find { it.method == method && it.path == path }
            ?: return "404 Not Found: $method $path"
        return route.handler(params)
    }
}

// 2. Service layer — bisnis logika
class PenggunaService {
    private val storage = ConcurrentHashMap<Int, String>()

    fun simpan(id: Int, nama: String) { storage[id] = nama }
    fun ambil(id: Int): String? = storage[id]
    fun hapus(id: Int): Boolean = storage.remove(id) != null
    fun daftar(): List<Pair<Int, String>> = storage.entries.map { it.key to it.value }.sortedBy { it.first }
}

// 3. Request / Response model
data class PenggunaRequest(val id: Int, val nama: String)
data class PenggunaResponse(val id: Int, val nama: String, val sukses: Boolean)

fun main() {
    println("=== Panduan Backend Kotlin ===")
    println("Ktor: https://ktor.io/docs/   |   Spring Boot: https://spring.io")
    println()

    val service = PenggunaService()
    val router = Router()

    // Definisi route
    router.get("/users") { _ ->
        val users = service.daftar()
        if (users.isEmpty()) "[]"
        else users.joinToString(prefix = "[", postfix = "]") { "{\"id\":${it.first},\"nama\":\"${it.second}\"}" }
    }
    router.post("/users") { params ->
        val id = params["id"]?.toIntOrNull() ?: -1
        val nama = params["nama"] ?: ""
        if (id < 0 || nama.isEmpty()) {
            "400 Bad Request"
        } else {
            service.simpan(id, nama)
            "201 Created: {\"id\":$id,\"nama\":\"$nama\"}"
        }
    }
    router.get("/users/{id}") { params ->
        val id = params["id"]?.toIntOrNull() ?: -1
        service.ambil(id)?.let { "200 OK: {\"id\":$id,\"nama\":\"$it\"}" }
            ?: "404 Not Found"
    }

    println("--- 1. POST /users ---")
    println(router.dispatch("POST", "/users", mapOf("id" to "1", "nama" to "Budi")))
    println(router.dispatch("POST", "/users", mapOf("id" to "2", "nama" to "Siti")))
    println(router.dispatch("POST", "/users", mapOf("id" to "3", "nama" to "Dewi")))

    println()
    println("--- 2. GET /users ---")
    println(router.dispatch("GET", "/users"))

    println()
    println("--- 3. GET /users/{id} ---")
    println(router.dispatch("GET", "/users/{id}", mapOf("id" to "2")))
    println(router.dispatch("GET", "/users/{id}", mapOf("id" to "999")))

    println()
    println("--- 4. GET /unknown ---")
    println(router.dispatch("GET", "/unknown"))

    println()
    println("--- 5. Framework backend populer ---")
    val frameworks = listOf(
        Triple("Ktor", "JetBrains", "Lightweight, coroutine-native, DSL routing"),
        Triple("Spring Boot", "VMware/Pivotal", "Mature, dependency injection, annotation-based"),
        Triple("http4k", "Community", "Functional HTTP, serverless-ready"),
        Triple("Javalin", "Community", "Simple, Express.js-like untuk Kotlin/Java"),
        Triple("Quarkus", "Red Hat", "Cloud-native, low memory, hot reload"),
        Triple("Micronaut", "OCI", "Compile-time DI, fast startup, low overhead")
    )
    frameworks.forEach { (nama, pembuat, desk) ->
        println("  ${nama.padEnd(14)} ($pembuat): $desk")
    }

    println()
    println("--- 6. Perbandingan Ktor vs Spring Boot ---")
    val perbandingan = listOf(
        "Startup:    Ktor <1s, Spring Boot 2-5s",
        "Memory:     Ktor ~50MB, Spring Boot ~200MB",
        "Learning:   Ktor mudah, Spring Boot kompleks (banyak konsep)",
        "Ecosystem:  Ktor kecil, Spring Boot masif (Spring Security, Data, Batch)",
        "Coroutine:  Ktor native, Spring Boot 5+ mendukung",
        "Skala:      Ktor microservice, Spring Boot enterprise monolith"
    )
    perbandingan.forEach { println("  $it") }

    println()
    println("--- 7. Panduan belajar backend Kotlin ---")
    val panduan = listOf(
        "1. Mulai dengan Ktor 'Hello World' (2 menit jalan)",
        "2. Pelajari routing DSL: get(), post(), dll",
        "3. Tambahkan persistence (Exposed, JOOQ, atau Hibernate)",
        "4. Tambahkan JSON serialization (kotlinx.serialization)",
        "5. Tambahkan autentikasi (JWT, OAuth2)",
        "6. Deploy ke container (Docker, Kubernetes)",
        "7. Untuk enterprise: migrasi ke Spring Boot bila butuh ekosistem"
    )
    panduan.forEach { println("  $it") }
}

Output yang Dihasilkan:

text
=== Panduan Backend Kotlin ===
Ktor: https://ktor.io/docs/   |   Spring Boot: https://spring.io

--- 1. POST /users ---
201 Created: {"id":1,"nama":"Budi"}
201 Created: {"id":2,"nama":"Siti"}
201 Created: {"id":3,"nama":"Dewi"}

--- 2. GET /users ---
[{"id":1,"nama":"Budi"}, {"id":2,"nama":"Siti"}, {"id":3,"nama":"Dewi"}]

--- 3. GET /users/{id} ---
200 OK: {"id":2,"nama":"Siti"}
404 Not Found

--- 4. GET /unknown ---
404 Not Found: GET /unknown

--- 5. Framework backend populer ---
  Ktor           (JetBrains): Lightweight, coroutine-native, DSL routing
  Spring Boot    (VMware/Pivotal): Mature, dependency injection, annotation-based
  http4k         (Community): Functional HTTP, serverless-ready
  Javalin        (Community): Simple, Express.js-like untuk Kotlin/Java
  Quarkus        (Red Hat): Cloud-native, low memory, hot reload
  Micronaut      (OCI): Compile-time DI, fast startup, low overhead

--- 6. Perbandingan Ktor vs Spring Boot ---
  Startup:    Ktor <1s, Spring Boot 2-5s
  Memory:     Ktor ~50MB, Spring Boot ~200MB
  Learning:   Ktor mudah, Spring Boot kompleks (banyak konsep)
  Ecosystem:  Ktor kecil, Spring Boot masif (Spring Security, Data, Batch)
  Coroutine:  Ktor native, Spring Boot 5+ mendukung
  Skala:      Ktor microservice, Spring Boot enterprise monolith

--- 7. Panduan belajar backend Kotlin ---
  1. Mulai dengan Ktor 'Hello World' (2 menit jalan)
  2. Pelajari routing DSL: get(), post(), dll
  3. Tambahkan persistence (Exposed, JOOQ, atau Hibernate)
  4. Tambahkan JSON serialization (kotlinx.serialization)
  5. Tambahkan autentikasi (JWT, OAuth2)
  6. Deploy ke container (Docker, Kubernetes)
  7. Untuk enterprise: migrasi ke Spring Boot bila butuh ekosistem

Perhatikan alur lengkap: tiga POST membuat pengguna baru, GET /users mengembalikan daftar lengkap sebagai JSON, GET /users/{id} mencari satu pengguna, dan GET path yang tidak terdaftar mengembalikan 404.

Ktor vs Spring Boot #

Ktor cocok untuk microservice dan project baru yang ingin startup cepat dan jejak memori kecil. DSL routing Ktor terlihat seperti kode Kotlin biasa, sehingga minim boilerplate. Spring Boot cocok untuk aplikasi enterprise yang butuh ekosistem matang: Spring Security, Spring Data JPA, Spring Batch, dan integrasi messaging. Spring Boot lebih berat (startup 2-5 detik, memori ~200 MB) tetapi kompensasinya adalah banyaknya modul siap pakai.

Referensi Lanjutan #

Selanjutnya #

Backend yang baik selalu diiringi pengujian yang baik. Chapter berikutnya membahas panduan testing — kotlin-test, JUnit 5, MockK, Testcontainers, dan Turbine untuk Flow.