Загрузка
Все статьи

Ktor vs Spring Boot — выбор фреймворка для Kotlin микросервиса

Два фреймворка, два мира. Spring Boot — монолит экосистемы с тысячей стартёров и аннотаций. Ktor — лёгкий, корутин-нативный фреймворк от JetBrains. Сравниваю DX, производительность, время старта и реальный опыт миграции с одного на другой.

Когда мы начали переводить бэкенд с Java на Kotlin, первым вопросом стал выбор фреймворка. Spring Boot — привычный, проверенный, с огромной экосистемой. Ktor — новый, быстрый, написанный на Kotlin от начала до конца. Я проработал с обоими больше года на реальных продакшен-проектах, и вот что могу сказать.

Это не статья «что лучше» — это честное сравнение с конкретными метриками, примерами кода и граблями, на которые я наступил.

Spring Boot на Kotlin: удобно, но с нюансами

Spring Boot — это industrial standard для Java-бэкенда. Он работает на Kotlin, но философски остаётся Java-фреймворком. Все концепции — бины, контекст, инверсия управления — пришли из мира Java. Аннотации @RestController, @Autowired, @Configuration привычны любому Java-разработчику.

@RestController
@RequestMapping("/api/v1/products")
class ProductController(
    private val productService: ProductService,
    private val productMapper: ProductMapper
) {
    @GetMapping("/{id}")
    fun getProduct(@PathVariable id: Long): ResponseEntity<ProductDto> {
        val product = productService.findById(id) ?: return ResponseEntity.notFound().build()
        return ResponseEntity.ok(productMapper.toDto(product))
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun createProduct(@Valid @RequestBody request: CreateProductRequest): ProductDto {
        return productMapper.toDto(productService.create(request))
    }
}

Плюсы Spring Boot на Kotlin очевидны: огромная экосистема (Spring Security, Spring Data, Spring Cloud), готовые решения для любых задач, массовость — любой вопрос уже задавали на Stack Overflow. Конструктор инъекции через primary constructor работает отлично, и Kotlin убирает бойлерплейт геттеров/сеттеров.

Минусы — те, что вы ожидаете. Аннотации — это рефлексия. Cold start в Spring Boot на Kotlin составляет 2-4 секунды, даже для простого REST API. Размер базового JAR-файла — 25-40 МБ. А shadow JAR с зависимостями — 60-100 МБ. Для serverless (AWS Lambda) это критично: каждые 100 МБ увеличивают время холодного старта.

Spring Boot + Kotlin Coroutines

Начиная с Spring Boot 3.2+, корутины поддерживаются нативно. Вы можете пометить методы контроллера suspend и вернуть Flow:

@GetMapping("/products/stream")
fun streamProducts(): Flow<ProductDto> = productService.streamAll()

@GetMapping("/products/{id}")
suspend fun getProduct(@PathVariable id: Long): ProductDto {
    return productMapper.toDto(productService.findById(id)!!)
}

Но под капотом Spring оборачивает корутины в Reactive Types (Mono/Flux), что добавляет слой абстракции. Это работает, но не даёт полной производительности, которую можно получить с нативно-корутинным фреймворком.

Ktor: Kotlin-нативный подход

Ktor — фреймворк, написанный JetBrains на Kotlin с нуля. Он использует корутины на всех уровнях: каждый запрос обрабатывается в отдельной корутине, и неблокирующий I/O — это базовая модель, а не опция.

fun Application.configureRouting() {
    val productService = ProductService(ProductRepository())

    routing {
        route("/api/v1/products") {
            get("/{id}") {
                val id = call.parameters["id"]?.toLongOrNull()
                    ?: return@get call.respond(HttpStatusCode.BadRequest)
                val product = productService.findById(id)
                    ?: return@get call.respond(HttpStatusCode.NotFound)
                call.respond(product)
            }

            post {
                val request = call.receive<CreateProductRequest>()
                val product = productService.create(request)
                call.respond(HttpStatusCode.Created, product)
            }
        }
    }
}

Первое, что бросается в глаза — никаких аннотаций. Вся конфигурация через DSL-блоки. Это чище, типобезопаснее, и IDE понимает структуру маршрутов. Нет «магии» — вы видите, что происходит в каждом обработчике.

Ktor модульный: вы подключаете только то, что нужно. ContentNegotiation для сериализации, Authentication для авторизации, CORS — каждый функционал через плагин. Нет гигантского контекста приложения с сотней бинов, которые вы не использовали.

Время старта и размер

Реальные замеры для REST API с 15 эндпоинтами, PostgreSQL-подключением и JWT-авторизацией:

Метрика                 | Spring Boot 3.2  | Ktor 2.3
------------------------|-------------------|------------
Cold start (JVM)        |    3.2s           |   0.8s
Shadow JAR size         |    78MB           |   32MB
RSS (простой API)       |    180MB          |   65MB
Throughput (rps)        |    12,000         |   18,000
P99 latency (1ms req)   |    4ms            |   1.5ms
TTFB (Time to First Byte)|   8ms            |   2ms

Ktor стартует в 4 раза быстрее, занимает в 2.5 раза меньше памяти и обрабатывает на 50% больше запросов в секунду. Для микросервисов с частыми деплоями и serverless-функций разница колоссальная.

DI: Spring vs Kodein vs Koin

Spring Boot поставляется с собственным DI-контейнером. Ktor не навязывает DI — вы выбираете сами. Популярные варианты: Koin (лёгкий, на основе DSL), Kodein (похож на Dagger), или ручной injection через фабрики.

// Koin DI с Ktor
val appModule = module {
    single { DatabaseConnection.environment() }
    single { ProductRepository(get()) }
    single { ProductService(get()) }
}

fun Application.module() {
    install(Koin) {
        modules(appModule)
    }
    routing {
        val productService: ProductService by inject()
        // ...
    }
}

Koin проще Spring DI, но менее мощный. Нет scoped beans, нет lazy initialization с proxy, нет автоматической конфигурации через @ConditionalOnProperty. Для простых микросервисов Koin — отличный выбор. Для сложных систем с множеством конфигураций Spring DI удобнее.

Тестирование

Spring Boot Test — это швейцарский нож: @WebMvcTest, @DataJpaTest, @SpringBootTest — готовые аннотации для любого уровня тестирования. MockMVC для тестирования контроллеров, Testcontainers для интеграционных тестов с настоящей БД.

@SpringBootTest
@AutoConfigureMockMvc
class ProductControllerTest {
    @Autowired
    lateinit var mockMvc: MockMvc

    @MockkBean
    lateinit var productService: ProductService

    @Test
    fun `should return product by id`() {
        every { productService.findById(1L) } returns Product(1L, "Widget", BigDecimal("29.99"))
        mockMvc.perform(get("/api/v1/products/1"))
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.name").value("Widget"))
    }
}

Ktor предлагает testApplication — встроенный тестовый движок:

class ProductRouteTest {
    @Test
    fun `should return product by id`() = testApplication {
        application {
            configureRouting()
            install(ContentNegotiation) { json() }
        }
        val response = client.get("/api/v1/products/1")
        assertEquals(HttpStatusCode.OK, response.status)
    }
}

Ktor-тесты проще в настройке, но Spring Boot даёт более структурированный подход к тестированию на разных уровнях. Для unit-тестов разницы нет — обе экосистемы поддерживают JUnit 5 и MockK.

Экосистема и комьюнити

Это elephants in the room. Spring Boot — гигантская экосистема: Spring Security (OAuth2, JWT, RBAC), Spring Data (JPA, MongoDB, Redis), Spring Cloud (Service Discovery, Circuit Breaker, Config Server), Spring Actuator (метрики, health checks). Всё это «из коробки» с минимальной конфигурацией.

Ktor меньше, но активно растёт. Для базовых задач (аутентификация, сериализация, CORS, rate limiting) есть готовые плагины. Но для сложных сценариев (distributed tracing, service mesh, circuit breaking) придётся собирать решение из разных библиотек или писать свои.

Размер комьюнити — в пользу Spring Boot. Stack Overflow, блогов, курсов по Spring в десятки раз больше. Если у вас проблема со Spring — скорее всего, решение уже найдено. С Ktor иногда приходится разбираться в исходниках.

Мой вердикт

Для нового микросервиса на Kotlin без легаси-зависимостей от Spring — я выбираю Ktor. Нативная поддержка корутин, скорость, минимальный footprint и лаконичный DSL стоят того, чтобы привыкнуть к другому подходу. Мы перевели 6 микросервисов с Spring Boot на Ktor за последние 8 месяцев, и средний деплой ускорился с 45 секунд до 12.

Spring Boot — правильный выбор, если у вас уже есть Java-инфраструктура, команда привыкла к Spring-экосистеме, или вам нужны сложные Enterprise-фичи (Spring Security + OAuth2 + LDAP, Spring Cloud с десятками сервисов, распределённые транзакции через Spring Batch).

И главное: не стоит бояться смешивать оба фреймворка в одной инфраструктуре. У нас gateway на Spring Cloud Gateway (потому что Netflix/Zuul-интеграция), а бизнес-микросервисы — на Ktor. Это нормально.

Поделиться статьёй

Комментарии

0
0 / 2000

Пока нет комментариев

Будьте первым, кто оставит комментарий к этой статье

Читайте также

Micronaut vs Spring Boot — лёгкий фреймворк для Kotlin-микросервисов

Сравниваю Micronaut и Spring Boot для Kotlin-микросервисов: стартовое время, потребление памяти, холодный старт и реальный опыт миграции одного из сервисов.

12 июня 2026 11 мин

Kotlin: sealed interfaces и when-выражения — паттерн-матчинг как в Rust

Как sealed interfaces из Kotlin 1.5+ вместе с when-выражениями дают мощный паттерн-матчинг, похожий на Rust enum+match, и почему мы перешли на ADT для доменных событий.

11 июня 2026 9 мин

Kotlinx Serialization + Protobuf — быстрая сериализация для микросервисов

Разбираюсь с накладными расходами JSON-сериализации при высоких нагрузках и показываю, как интегрировать Protobuf с kotlinx-serialization в микросервисной архитектуре на Kotlin.

10 июня 2026 10 мин