DevOps e CI/CD nao sao mais diferenciais, sao requisitos basicos em projetos profissionais. Para projetos Kotlin, montar pipelines de integracao e entrega continua garante qualidade, velocidade e confianca em cada deploy. Neste artigo, vamos explorar como configurar CI/CD eficaz para projetos Kotlin.

O Que e CI/CD e Por Que Importa

CI (Continuous Integration) e a pratica de integrar codigo frequentemente, com verificacoes automaticas a cada push. CD (Continuous Delivery/Deployment) automatiza o processo de levar o codigo para producao.

Para projetos Kotlin, isso significa:

  • Compilar e testar automaticamente a cada commit
  • Verificar qualidade de codigo com analise estatica
  • Gerar artefatos (JARs, APKs, imagens Docker) automaticamente
  • Fazer deploy em ambientes de staging e producao

Estrutura de Pipeline para Kotlin

Um pipeline tipico para um projeto Kotlin backend tem as seguintes etapas:

1. Build e Compilacao

// build.gradle.kts - Configuracao otimizada para CI
plugins {
    kotlin("jvm") version "2.1.0"
    kotlin("plugin.spring") version "2.1.0"
    id("org.springframework.boot") version "3.3.0"
    id("io.spring.dependency-management") version "1.1.4"
    id("org.jetbrains.kotlinx.kover") version "0.7.5"  // Cobertura de codigo
    id("io.gitlab.arturbosch.detekt") version "1.23.4"  // Analise estatica
}

detekt {
    config.setFrom(files("config/detekt/detekt.yml"))
    buildUponDefaultConfig = true
    allRules = false
}

kover {
    reports {
        filters {
            excludes {
                classes("*.config.*", "*.Application*")
            }
        }
        verify {
            rule {
                minBound(80)  // Minimo 80% de cobertura
            }
        }
    }
}

2. Testes Automatizados

// Testes bem estruturados sao a base do CI
@SpringBootTest
class PedidoServiceIntegrationTest {

    @Autowired
    private lateinit var pedidoService: PedidoService

    @Autowired
    private lateinit var pedidoRepository: PedidoRepository

    @Test
    fun `deve criar pedido e persistir no banco`() = runTest {
        // Given
        val request = CriarPedidoRequest(
            clienteId = "cliente-1",
            itens = listOf(
                ItemRequest(produtoId = "prod-1", quantidade = 2),
                ItemRequest(produtoId = "prod-2", quantidade = 1)
            )
        )

        // When
        val pedido = pedidoService.criarPedido(request)

        // Then
        assertThat(pedido.id).isNotNull()
        assertThat(pedido.status).isEqualTo(PedidoStatus.CRIADO)

        val persistido = pedidoRepository.findById(pedido.id)
        assertThat(persistido).isNotNull()
        assertThat(persistido!!.itens).hasSize(2)
    }

    @AfterEach
    fun cleanup() = runBlocking {
        pedidoRepository.deleteAll()
    }
}

3. Analise Estatica com Detekt

Detekt e a ferramenta padrao para analise estatica de codigo Kotlin:

// config/detekt/detekt.yml (em formato YAML, referenciado pelo Gradle)
// Configuracoes tipicas incluem:
// - Complexidade ciclomatica maxima
// - Tamanho maximo de funcoes
// - Deteccao de code smells
// - Regras de formatacao

// Exemplo de codigo que detekt flagraria:
// RUIM - Funcao muito complexa
fun processarDados(dados: List<Any>): String {
    // detekt avisaria sobre complexidade excessiva
    var resultado = ""
    for (dado in dados) {
        when (dado) {
            is String -> resultado += dado.uppercase()
            is Int -> resultado += dado.toString()
            is List<*> -> resultado += dado.size.toString()
            else -> resultado += "unknown"
        }
    }
    return resultado
}

// BOM - Funcao simples e clara
fun processarDados(dados: List<Any>): String {
    return dados.joinToString("") { dado ->
        formatarDado(dado)
    }
}

private fun formatarDado(dado: Any): String = when (dado) {
    is String -> dado.uppercase()
    is Int -> dado.toString()
    is List<*> -> dado.size.toString()
    else -> "unknown"
}

4. Build de Imagem Docker

// Dockerfile para aplicacao Kotlin/Spring Boot
// Multi-stage build para imagem otimizada

// Usando Gradle para build
// Stage 1: Build
// FROM gradle:8.5-jdk21 AS build
// COPY . /app
// WORKDIR /app
// RUN gradle bootJar --no-daemon

// Stage 2: Runtime
// FROM eclipse-temurin:21-jre-alpine
// COPY --from=build /app/build/libs/*.jar app.jar
// EXPOSE 8080
// ENTRYPOINT ["java", "-jar", "app.jar"]

Para otimizar o build Docker em CI, use cache de dependencias Gradle:

// build.gradle.kts - Task para copiar dependencias (util para cache Docker)
tasks.register<Copy>("copyDependencies") {
    from(configurations.runtimeClasspath)
    into("$buildDir/dependencies")
}

Ferramentas de CI/CD

GitHub Actions

A ferramenta mais popular para projetos open source e muitas empresas:

O workflow tipico inclui:

  • Checkout do codigo
  • Setup do JDK 21
  • Cache de dependencias Gradle
  • Execucao de testes
  • Analise estatica com detekt
  • Verificacao de cobertura com kover
  • Build da imagem Docker
  • Deploy para staging/producao

GitLab CI

Popular em empresas que usam GitLab para hospedagem de codigo. Oferece pipelines visuais e integracao nativa com Kubernetes.

Jenkins

Ainda muito usado em empresas enterprise, especialmente bancos e fintechs. Oferece maxima flexibilidade mas exige mais manutencao.

Pipeline para Projeto Android

Para projetos Android com Kotlin, o pipeline tem particularidades:

// Pipeline Android tipico:
// 1. Build do APK/AAB
// 2. Testes unitarios
// 3. Testes instrumentados (opcional, com emulador)
// 4. Lint e detekt
// 5. Publicacao na Play Store (via Fastlane ou Gradle Play Publisher)

// build.gradle.kts (app module)
android {
    lint {
        abortOnError = true
        warningsAsErrors = true
        xmlReport = true
    }

    testOptions {
        unitTests.all {
            it.useJUnitPlatform()
        }
    }
}

// Plugin para publicacao automatica
plugins {
    id("com.github.triplet.play") version "3.9.0"
}

play {
    serviceAccountCredentials.set(file("play-service-account.json"))
    track.set("internal")  // internal -> alpha -> beta -> production
    defaultToAppBundles.set(true)
}

Melhores Praticas de CI/CD para Kotlin

Cache de Dependencias

Gradle downloads podem levar minutos. Configurar cache e essencial:

// Configuracao de cache no build.gradle.kts
buildCache {
    local {
        isEnabled = true
        directory = File(rootDir, ".gradle/build-cache")
    }
}

Testes Paralelos

// Executar testes em paralelo para reduzir tempo de CI
tasks.withType<Test> {
    maxParallelForks = Runtime.getRuntime().availableProcessors() / 2
    forkEvery = 100  // Reinicia JVM a cada 100 testes para evitar memory leaks
}

Build Incremental

// Habilitar compilacao incremental
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xuse-k2")  // Compilador K2 mais rapido
    }
}

Segredos e Configuracao

Nunca coloque segredos no codigo. Use variaveis de ambiente ou vault:

// Acessar configuracoes de forma segura
@Configuration
class DatabaseConfig(
    @Value("\${DATABASE_URL}") private val databaseUrl: String,
    @Value("\${DATABASE_USERNAME}") private val username: String,
    @Value("\${DATABASE_PASSWORD}") private val password: String
) {
    @Bean
    fun dataSource(): HikariDataSource {
        return HikariDataSource().apply {
            jdbcUrl = databaseUrl
            this.username = this@DatabaseConfig.username
            this.password = this@DatabaseConfig.password
            maximumPoolSize = 10
        }
    }
}

Monitoramento do Pipeline

Acompanhe metricas do seu pipeline:

  • Tempo medio de build: Deve ficar abaixo de 10 minutos para feedback rapido
  • Taxa de falha: Acima de 10% indica problemas no processo
  • Cobertura de testes: Mantenha acima de 80% para o core business
  • Tempo de deploy: Do merge ao producao, idealmente menos de 30 minutos

Estrategias de Deploy

Blue-Green Deployment

Mantem duas versoes do ambiente (blue e green). O deploy vai para o ambiente inativo, e apos validacao, o trafego e direcionado para ele.

Canary Release

Direciona uma pequena porcentagem do trafego (1-5%) para a nova versao. Se tudo estiver bem, aumenta gradualmente ate 100%.

Rolling Update

Atualiza instancias gradualmente, substituindo uma por vez. E a abordagem padrao no Kubernetes.

Conclusao

CI/CD bem implementado transforma a forma como voce desenvolve e entrega software Kotlin. Automacao de testes, analise estatica, builds reprodutiveis e deploys automatizados criam um ciclo de feedback rapido que aumenta a qualidade e a velocidade de entrega.

Comece com o basico (build e testes automaticos) e evolua gradualmente para um pipeline completo com analise de codigo, cobertura de testes e deploy automatizado. O investimento em CI/CD se paga rapidamente em menos bugs, menos trabalho manual e mais confianca em cada release.