Go vs Kotlin per a backend: dues formes molt diferents de construir serveis

Comparació entre Go i Kotlin per a backend des de l'experiència real. Spring Boot/Ktor enfront de Go, tipat, desplegament i productivitat.

Cover for Go vs Kotlin per a backend: dues formes molt diferents de construir serveis

Porto anys escrivint Kotlin. És el llenguatge en el qual penso quan dissenyo un servei, el que faig servir quan vull que un domini complex quedi ben modelat, i el que triaria si hagués de mantenir un monòlit durant una dècada. Quan vaig començar a aprendre Go, la sensació va ser estranya: com tornar a un món on moltes de les coses que dono per descomptat simplement no existeixen. I el sorprenent és que això no sempre és dolent.

Aquesta comparació no és neutral. Kotlin és la meva eina principal, i això significa que conec les seves virtuts, les seves trampes i els moments en què et salva la vida. Però també significa que puc ser honest sobre quan Go ofereix alguna cosa que Kotlin no dóna, o no dóna tan fàcilment.


Filosofia de disseny: expressivitat vs minimalisme intencional

La diferència fonamental entre Go i Kotlin no és a la sintaxi ni en el rendiment. Està en alguna cosa més subtil: en el que cada llenguatge decideix no donar-te. I aquesta decisió diu més sobre la filosofia del llenguatge que qualsevol llista de funcionalitats.

Kotlin hereta la tradició dels llenguatges expressius. Vol que escriguis menys, que el codi es llegeixi gairebé com a prosa, que el compilador detecti tants errors com sigui possible abans d’executar res. T’ofereix null safety, extension functions, coroutines, sealed classes, data classes, delegated properties, smart casts… La llista és llarga i cada funcionalitat té la seva raó de ser.

Go pren la direcció contrària. Rob Pike i l’equip de Google van dissenyar Go amb una premissa clara: la simplicitat no és absència de funcionalitats, és una funcionalitat en si mateixa. No hi ha herència, no hi ha excepcions, no hi ha generics amb la potència de Kotlin (tot i que Go 1.18 va afegir generics bàsics), no hi ha extension functions, no hi ha operator overloading. I això és deliberat.

Go no vol que escriguis codi elegant. Vol que qualsevol persona del teu equip pugui llegir i entendre qualsevol fitxer del projecte en trenta segons.

Quan véns de Kotlin, això se sent restrictiu. Gairebé frustrant, diria. Però quan mantens un servei escrit per una altra persona en Go, comences a entendre el punt. No perquè Go sigui “millor” que Kotlin. Sinó perquè resol el problema de la llegibilitat d’una forma que Kotlin, per disseny, delega en la disciplina de l’equip.


Frameworks de backend: Spring Boot/Ktor vs Go stdlib/Gin

Però deixem la filosofia i anem al concret, que és on aquestes diferències es noten de veritat.

L’ecosistema Kotlin

A Kotlin per a backend tens dos camins principals:

  • Spring Boot amb Kotlin: l’estàndard de facto en el món JVM corporatiu. Injecció de dependències, autoconfiguració, un ecosistema d’starters enorme, integració amb tot el que és imaginable.
  • Ktor: framework natiu de Kotlin de JetBrains. Més lleuger, basat en coroutines, amb un model de plugins. Molt agradable d’usar, però amb un ecosistema més reduït.

Un endpoint típic en Spring Boot amb Kotlin:

@RestController
@RequestMapping(\"/api/users\")
class UserController(
    private val userService: UserService
) {
    @GetMapping(\"/{id}\")
    suspend fun getUser(@PathVariable id: Long): ResponseEntity<UserDto> {
        val user = userService.findById(id)
            ?: return ResponseEntity.notFound().build()
        return ResponseEntity.ok(user.toDto())
    }

    @PostMapping
    suspend fun createUser(@RequestBody @Valid request: CreateUserRequest): ResponseEntity<UserDto> {
        val user = userService.create(request)
        return ResponseEntity.status(HttpStatus.CREATED).body(user.toDto())
    }
}

I el mateix en Ktor:

fun Route.userRoutes(userService: UserService) {
    route(\"/api/users\") {
        get(\"/{id}\") {
            val id = call.parameters[\"id\"]?.toLongOrNull()
                ?: return@get call.respond(HttpStatusCode.BadRequest)
            val user = userService.findById(id)
                ?: return@get call.respond(HttpStatusCode.NotFound)
            call.respond(user.toDto())
        }
        post {
            val request = call.receive<CreateUserRequest>()
            val user = userService.create(request)
            call.respond(HttpStatusCode.Created, user.toDto())
        }
    }
}

L’ecosistema Go

Go té una biblioteca estàndard potent per a HTTP. No necessites un framework per muntar una API funcional. Però a la pràctica, la majoria de projectes fan servir alguna cosa com Gin, Chi o Echo per no reinventar el routing.

El mateix endpoint en Go amb Gin:

func (h *UserHandler) SetupRoutes(r *gin.Engine) {
    users := r.Group(\"/api/users\")
    {
        users.GET(\"/:id\", h.GetUser)
        users.POST(\"\", h.CreateUser)
    }
}

func (h *UserHandler) GetUser(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param(\"id\"), 10, 64)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{\"error\": \"invalid id\"})
        return
    }

    user, err := h.userService.FindByID(c.Request.Context(), id)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{\"error\": \"user not found\"})
        return
    }

    c.JSON(http.StatusOK, toUserDTO(user))
}

func (h *UserHandler) CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})
        return
    }

    user, err := h.userService.Create(c.Request.Context(), &req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{\"error\": \"failed to create user\"})
        return
    }

    c.JSON(http.StatusCreated, toUserDTO(user))
}

I amb la biblioteca estàndard de Go (sense framework):

mux := http.NewServeMux()
mux.HandleFunc(\"GET /api/users/{id}\", h.GetUser)
mux.HandleFunc(\"POST /api/users\", h.CreateUser)

Comparació directa

AspecteKotlin (Spring Boot)Kotlin (Ktor)Go (Gin/stdlib)
Corba d’aprenentatgeAlta (Spring és enorme)MitjanaBaixa
Productivitat inicialMitjana (molta config)AltaAlta
Màgia implícitaMolta (DI, proxies, AOP)PocaCap
Ecosistema de bibliotequesEnorme (tot el món JVM)CreixentSuficient
Arrencada en fredLenta (JVM)Lenta (JVM)Instantània
Mida de l’artefacteGran (~50-200 MB)Mitjà (~30-80 MB)Petit (~10-20 MB)

A Spring Boot cal saber molt per entendre què està passant realment. A Go, el que veus és el que s’executa. Ktor es troba en un punt intermedi que m’agrada bastant.


Sistema de tipus: dos mons diferents

Aquí és on he de ser honest amb el meu biaix. El sistema de tipus de Kotlin és un dels seus arguments més grans, i és la part de Kotlin que més trobo a faltar quan escric Go. Ho dic sense reserves: si el teu domini és complex, Kotlin et deixa modelar-lo d’una forma que Go simplement no pot igualar.

Kotlin: tipus com a documentació del domini

sealed interface PaymentResult {
    data class Success(val transactionId: String, val amount: Money) : PaymentResult
    data class Declined(val reason: DeclineReason) : PaymentResult
    data class Error(val exception: PaymentException) : PaymentResult
}

@JvmInline
value class Money(val cents: Long) {
    init {
        require(cents >= 0) { \"Money cannot be negative\" }
    }
    operator fun plus(other: Money) = Money(cents + other.cents)
}

enum class DeclineReason {
    INSUFFICIENT_FUNDS, EXPIRED_CARD, FRAUD_SUSPECTED
}

// El when t'obliga a gestionar tots els casos
fun handlePayment(result: PaymentResult): String = when (result) {
    is PaymentResult.Success -> \"Pagament ${result.transactionId} completat\"
    is PaymentResult.Declined -> \"Rebutjat: ${result.reason}\"
    is PaymentResult.Error -> \"Error: ${result.exception.message}\"
}

Aquesta és expressivitat real. El compilador et garanteix que gestiones tots els casos. Les value classes eviten errors del tipus “passar un Long on anava un altre Long”. Les sealed interfaces modelen estats finits amb dades associades.

Go: tipus simples, composició explícita

type PaymentResult struct {
    Status        string
    TransactionID string
    Amount        int64
    DeclineReason string
    Err           error
}

func handlePayment(result PaymentResult) string {
    switch result.Status {
    case \"success\":
        return fmt.Sprintf(\"Pagament %s completat\", result.TransactionID)
    case \"declined\":
        return fmt.Sprintf(\"Rebutjat: %s\", result.DeclineReason)
    case \"error\":
        return fmt.Sprintf(\"Error: %v\", result.Err)
    default:
        return \"Estat desconegut\"
    }
}

El default a Go és revelador, i crec que resumeix bé la diferència de filosofies. Kotlin t’obliga a gestionar tots els casos. Go no sap quins són tots els casos, així que necessites aquest default defensiu. Si algú afegeix un nou estat, el compilador de Kotlin t’avisa a tots els llocs on falta. A Go, el default s’empassa el cas silenciosament. Es pot argumentar que això és un problema de disciplina i no de llenguatge? Sí. Però la realitat és que els compiladors són millors sent disciplinats que els humans.

En dominis complexos (finances, salut, logística), la capacitat de modelatge de Kotlin no té preu. Si el teu servei és un CRUD amb poques regles de negoci, la diferència importa menys.


Concurrència: coroutines vs goroutines

Aquesta és una de les comparacions més interessants, i la que més m’ha fet reflexionar. Perquè tots dos llenguatges resolen el mateix problema d’una forma semblant conceptualment, però molt diferent a la pràctica.

Kotlin coroutines

suspend fun fetchUserWithOrders(userId: Long): UserWithOrders {
    return coroutineScope {
        val userDeferred = async { userService.findById(userId) }
        val ordersDeferred = async { orderService.findByUserId(userId) }

        val user = userDeferred.await()
            ?: throw UserNotFoundException(userId)
        val orders = ordersDeferred.await()

        UserWithOrders(user, orders)
    }
}

// Concurrència estructurada amb timeout
suspend fun fetchWithTimeout(userId: Long): UserWithOrders {
    return withTimeout(5.seconds) {
        fetchUserWithOrders(userId)
    }
}

Goroutines a Go

func fetchUserWithOrders(ctx context.Context, userID int64) (*UserWithOrders, error) {
    g, ctx := errgroup.WithContext(ctx)

    var user *User
    var orders []Order

    g.Go(func() error {
        var err error
        user, err = userService.FindByID(ctx, userID)
        return err
    })

    g.Go(func() error {
        var err error
        orders, err = orderService.FindByUserID(ctx, userID)
        return err
    })

    if err := g.Wait(); err != nil {
        return nil, err
    }

    return &UserWithOrders{User: user, Orders: orders}, nil
}

Diferències que importen

AspecteKotlin coroutinesGo goroutines
Concurrència estructuradaNativa (coroutineScope)Amb errgroup o manual
Cancel·lacióCooperativa, propagadaVia context.Context
Corba d’aprenentatgeMitjana-alta (scopes, dispatchers)Baixa (però fàcil equivocar-se)
ChannelsSí (Channel<T>)Sí (part del llenguatge)
Pes del runtimeDepèn de JVMMolt lleuger
FuitesLa concurrència estructurada les prevéFàcil crear goroutine leaks

El que més m’agrada de les coroutines de Kotlin és la concurrència estructurada: si el scope pare es cancel·la, tots els fills es cancel·len automàticament. A Go has de ser molt disciplinat amb el context i la propagació d’errors, o acabes amb goroutines penjades que ningú cancel·la.

Però les goroutines tenen un avantatge que no puc ignorar: qualsevol funció pot llançar una goroutine amb go. No necessites marcar funcions com a suspend, no hi ha coloring problem, no necessites entendre dispatchers ni scopes. La barrera d’entrada és molt menor. A costa de què? Que és més fàcil ficar la pota sense que ningú t’avisi. És un trade-off, no una victòria clara.


Gestió d’errors: sealed classes vs if err != nil

I aquí arribem al punt que més em costa tractar amb equanimitat. Aquesta és probablement la diferència més polaritzant i la que més frustració genera quan véns d’un llenguatge expressiu.

Kotlin: errors com a tipus

sealed interface Result<out T> {
    data class Ok<T>(val value: T) : Result<T>
    data class Err(val error: AppError) : Result<Nothing>
}

sealed interface AppError {
    data class NotFound(val entity: String, val id: String) : AppError
    data class Validation(val field: String, val message: String) : AppError
    data class Infrastructure(val cause: Throwable) : AppError
}

fun findUser(id: Long): Result<User> {
    val user = userRepository.findById(id)
        ?: return Result.Err(AppError.NotFound(\"User\", id.toString()))
    return Result.Ok(user)
}

// Al handler
when (val result = findUser(id)) {
    is Result.Ok -> respond(result.value)
    is Result.Err -> when (result.error) {
        is AppError.NotFound -> respond(HttpStatusCode.NotFound)
        is AppError.Validation -> respond(HttpStatusCode.BadRequest)
        is AppError.Infrastructure -> respond(HttpStatusCode.InternalServerError)
    }
}

Go: if err != nil, i punt

func (s *UserService) FindUser(ctx context.Context, id int64) (*User, error) {
    user, err := s.repo.FindByID(ctx, id)
    if err != nil {
        return nil, fmt.Errorf(\"finding user %d: %w\", id, err)
    }
    if user == nil {
        return nil, ErrNotFound
    }
    return user, nil
}

// Al handler
user, err := userService.FindUser(ctx, id)
if err != nil {
    if errors.Is(err, ErrNotFound) {
        c.JSON(http.StatusNotFound, gin.H{\"error\": \"user not found\"})
        return
    }
    c.JSON(http.StatusInternalServerError, gin.H{\"error\": \"internal error\"})
    return
}
c.JSON(http.StatusOK, toDTO(user))

Seré directe: la gestió d’errors de Go és verbosa i repetitiva. No hi ha forma elegant de dir-ho. Després d’escriure la teva funció número cent amb cinc blocs if err != nil, trobes profundament a faltar el when exhaustiu de Kotlin, la propagació automàtica d’errors, o fins i tot les excepcions de Java. Tècnicament no estàs fent res malament, però la sensació és d’estar escrivint boilerplate.

Però hi ha un argument a favor de Go que és difícil d’ignorar: mai hi ha un error ocult. Cada punt de fallada és visible. No hi ha excepcions que volin per l’stack sense que ningú les atrapi. No hi ha un runCatching que s’empassi un error crític perquè algú no va pensar en aquell cas.

La verbositat de Go en la gestió d’errors és molesta. Però obligar-te a decidir què fer a cada punt de fallada produeix codi més robust, encara que no ho sembli a primera vista.


Desplegament: JVM vs binari estàtic

Canviant de tema, aquesta és la secció on Go guanya de forma categòrica. I aquí sí que no hi ha gaire a matisar.

Kotlin sobre JVM

FROM eclipse-temurin:21-jre-alpine
COPY build/libs/app.jar /app.jar
EXPOSE 8080
ENTRYPOINT [\"java\", \"-jar\", \"/app.jar\"]
  • Necessites un JRE a la imatge (o GraalVM per a native).
  • L’arrencada en fred de Spring Boot pot ser de 3 a 15 segons.
  • El consum de memòria base ronda els 150-400 MB.
  • La imatge Docker pesa entre 150-300 MB.

Go

FROM scratch
COPY app /app
EXPOSE 8080
ENTRYPOINT [\"/app\"]

Sí, FROM scratch. Un binari Go estàticament compilat no necessita absolutament res. Ni sistema operatiu base, ni runtime, ni biblioteques compartides.

  • Arrencada en mil·lisegons.
  • Consum de memòria base de 5-20 MB.
  • Imatge Docker de 10-20 MB.
AspecteKotlin (JVM)Kotlin (GraalVM native)Go
Temps d’arrencada3-15 s0.1-0.5 s< 50 ms
Memòria base150-400 MB50-100 MB5-20 MB
Imatge Docker150-300 MB50-100 MB10-20 MB
CompilacióRàpidaMolt lenta (minuts)Ràpida
Debugging en produccióBo (JVM tools)LimitatBo (pprof, delve)

Si treballes amb Kubernetes i necessites escalar ràpidament, la diferència entre 10 segons d’arrencada i 50 mil·lisegons és la diferència entre un autoscaling que funciona i un que arriba tard.

GraalVM native image millora bastant la situació per a Kotlin/Spring Boot, però la compilació és lenta, hi ha limitacions amb la reflexió (que Spring fa servir massivament), i el debugging es complica. He provat GraalVM en projectes reals i l’experiència és… desigual. És una solució viable però amb trade-offs importants que convé experimentar abans d’apostar fort.


Llegibilitat i manteniment: bonic vs predictible

Aquí entra la meva experiència personal més directa, i la part on més m’he autocorregit amb el temps.

El codi Kotlin pot ser preciós. Extension functions, DSLs, operadors personalitzats, funcions d’ordre superior… pots escriure codi que es llegeix com un conte. I confesso que m’encanta. Però aquest poder té un cost que al principi no volia veure: un desenvolupador júnior o algú que no coneix bé Kotlin pot perdre’s llegint un builder DSL complex o una cadena d’extension functions encadenades amb receivers implícits.

// Kotlin idiomàtic i expressiu
val report = users
    .filter { it.isActive && it.registeredAfter(cutoffDate) }
    .groupBy { it.department }
    .mapValues { (_, users) ->
        users.sumOf { it.totalPurchases }
    }
    .toSortedMap()
    .also { log.info(\"Report generated for ${it.size} departments\") }

L’equivalent en Go:

// Go explícit i pas a pas
report := make(map[string]int64)
for _, user := range users {
    if !user.IsActive || user.RegisteredAt.Before(cutoffDate) {
        continue
    }
    report[user.Department] += user.TotalPurchases
}

keys := make([]string, 0, len(report))
for k := range report {
    keys = append(keys, k)
}
sort.Strings(keys)

log.Printf(\"Report generated for %d departments\", len(report))

El de Kotlin és més curt i més declaratiu. El de Go és més llarg però absolutament explícit: veus cada pas, cada decisió, cada iteració. No hi ha màgia. No hi ha funcions d’extensió que hagis d’anar a buscar. No hi ha lambdes amb receivers implícits. Quin és “millor”? Depèn de a qui preguntis, i sobretot de qui mantindrà aquell codi d’aquí a dos anys.

En un equip on tothom domina Kotlin, el primer estil és clarament millor. En un equip heterogeni, o en un projecte open source on contribueixen persones amb nivells diferents, Go té un avantatge real: tothom llegeix Go igual, perquè no hi ha moltes formes d’escriure’l.

Go és el llenguatge on el codi que escrius el primer mes s’assembla molt al que escrius el tercer any. Kotlin és el llenguatge on cada any descobreixes una forma millor de fer les coses. Tots dos tenen valor.


Ecosistema i biblioteques

I això ens porta a l’ecosistema, que és on la conversa es torna més pragmàtica. Kotlin té accés a tot l’ecosistema JVM. Això són dècades de biblioteques madures, provades en producció per milions d’aplicacions:

  • Hibernate/Exposed per a ORM
  • Jackson/kotlinx.serialization per a JSON
  • Spring Security per a autenticació
  • Apache Kafka clients, Elasticsearch, Redis…

La llista és infinita. Si existeix una biblioteca Java, funciona en Kotlin.

Go té un ecosistema més jove però sorprenentment complet per a backend:

  • GORM/sqlx/pgx per a bases de dades
  • encoding/json a stdlib (i opcions com sonic per a rendiment)
  • Gin/Chi/Echo per a HTTP
  • Clients oficials de Google Cloud, AWS SDK, etc.

On Go falla és en dominis enterprise complexos. No hi ha un equivalent a Spring Security amb la mateixa profunditat. No hi ha un ORM tan complet com Hibernate — i molts a la comunitat Go argumenten que això és una virtut, cosa que és un debat interessant però que no et resol el problema quan necessites un ORM de veritat. Les biblioteques de validació són més bàsiques.

DominiKotlin/JVMGo
HTTP/APIExcel·lentExcel·lent
Bases de dadesExcel·lent (Hibernate, Exposed)Bo (sqlx, pgx)
Missatgeria (Kafka, RabbitMQ)Excel·lentBo
ObservabilitatExcel·lent (Micrometer, etc.)Bo (OpenTelemetry)
Machine LearningBo (DL4J, interop Python)Bàsic
Eines CLILimitatExcel·lent
Infraestructura (Docker, K8s)No és el seu fortNatiu

Quan Kotlin és la millor elecció

Després de treballar amb tots dos, crec que tinc bastant clar quan Kotlin guanya. Tot i que reconec que el meu biaix cap a Kotlin pot influir aquí:

Dominis complexos amb lògica de negoci rica. Si el teu servei gestiona estats, regles de negoci amb moltes variants, workflows complexos… les sealed classes, el sistema de tipus i l’expressivitat de Kotlin t’estalviaran bugs i faran el codi molt més mantenible.

Equips amb experiència JVM. Si el teu equip ve de Java, Kotlin és una millora directa sense canviar d’ecosistema. Spring Boot segueix funcionant, les biblioteques que coneixes segueixen sent-hi, però escrius menys i amb més seguretat.

Projectes que necessiten l’ecosistema JVM. Si depens de biblioteques específiques de Java (certs clients enterprise, frameworks de reporting, integració amb sistemes legacy), Kotlin sobre JVM és l’opció òbvia.

Android i multiplataforma. Kotlin Multiplatform permet compartir lògica entre backend, Android, iOS i web. Go no juga en aquest terreny.

Quan la correcció del model de dades és crítica. En Spring Boot amb Kotlin, poder modelar el teu domini amb sealed interfaces i value classes redueix una categoria sencera de bugs.


Quan Go és la millor elecció

Però la pregunta interessant és una altra: quan Go ofereix alguna cosa que Kotlin no dóna tan fàcilment? I la resposta em va sorprendre més del que esperava:

Microserveis simples i APIs REST sense lògica complexa. Si el teu servei rep peticions, consulta una base de dades i retorna JSON, Go et deixa fer-ho amb un consum mínim de recursos i un desplegament trivial. Per a APIs REST senzilles, Go és difícil de superar.

Infraestructura i eines de sistema. Docker, Kubernetes, Terraform, Prometheus… estan escrits en Go per bones raons. Si construeixes eines per a DevOps, Go és l’estàndard de facto.

CLIs. Un binari estàtic que funciona en qualsevol plataforma, sense dependències. Go és imbatible per a això.

Quan el rendiment operatiu importa més que l’expressivitat. En entorns on necessites arrencada ràpida, baix consum de memòria i desplegaments lleugers (edge computing, funcions serverless amb cold starts freqüents), Go guanya clarament.

Equips heterogenis o amb alta rotació. La simplicitat de Go redueix el temps d’onboarding. Qualsevol desenvolupador pot ser productiu en Go en una o dues setmanes. Kotlin idiomàtic requereix més temps per dominar-lo.

Quan vols una arquitectura neta sense màgia. Go et força a ser explícit amb les dependències, a passar-ho tot per paràmetre, a no dependre d’injecció automàtica. Per a alguns projectes, aquesta rigidesa és exactament el que necessites.


Es poden fer servir tots dos? Sí, i és una estratègia vàlida

En el món real, la pregunta “Go o Kotlin” no sempre té una sola resposta. I crec que la maduresa està en acceptar això sense frustrar-te. Conec equips (i jo mateix ho plantejo per a certs projectes) que fan servir tots dos:

  • Kotlin per al servei de domini principal, on la lògica de negoci és complexa i el sistema de tipus de Kotlin protegeix contra errors subtils.
  • Go per a serveis auxiliars: proxies, workers de processament de cues, eines d’infraestructura, APIs gateway.

Això no és overengineering si es fa amb criteri. Cada llenguatge juga on és més fort. L’important és que l’equip tingui competència en tots dos i que hi hagi convencions clares sobre quan usar cadascun.

┌─────────────────────────────────────────────────┐
│             Arquitectura mixta                  │
├────────────────────┬────────────────────────────┤
│  Go                │  Kotlin                    │
│  ──────────────    │  ────────────────────      │
│  API Gateway       │  Servei de pagaments       │
│  Worker de cues    │  Motor de regles           │
│  CLI de deploy     │  Servei d'usuaris          │
│  Proxy de mètriques│  Orquestrador de workflows │
└────────────────────┴────────────────────────────┘

El que he après triant entre tots dos

No fingiré equidistància, perquè seria deshonest. Kotlin segueix sent el meu llenguatge preferit per a backend. El seu sistema de tipus, la seva expressivitat i l’ecosistema JVM el fan imbatible per a projectes amb complexitat real. Quan un domini té molts estats, moltes regles i necessita evolucionar durant anys, Kotlin em dóna confiança que el codi aguantarà. Per a dominis complexos, modelatge de dades ric, o quan ja tens un equip amb experiència JVM, Kotlin és l’elecció que em surt sola.

Però Go m’ha ensenyat alguna cosa que no esperava: el valor del simple. M’ha obligat a qüestionar si realment necessito aquella abstracció, aquell DSL, aquell patró sofisticat. I sent honestos, més vegades de les que m’agradaria admetre la resposta era no. Quan la resposta és sí, Kotlin brilla. Però quan és no, Go et recompensa amb un servei que arrenca en mil·lisegons, consumeix res, i que qualsevol pot mantenir. Per a microserveis lleugers, CLIs, eines d’infra, o qualsevol cosa on l’arrencada ràpida i el baix consum importin, Go m’ha demostrat que menys abstracció pot ser més productivitat.

Si estàs començant amb Go venint de Kotlin o Java, et recomano revisar la comparació Go vs Java que entra més en detall sobre les diferències amb el món JVM. I si vols veure com és construir alguna cosa real en Go, fes un cop d’ull a com muntar una API REST des de zero. La millor eina depèn del problema. I la millor senyal de maduresa com a enginyer és no enamorar-te de cap.

OshyTech

Enginyeria backend i de dades orientada a sistemes escalables, automatització i IA.

Navegació

Copyright 2026 OshyTech. Tots els drets reservats