← Kembali ke Blog Career

Perjalanan 5 Tahun Backend Engineer: Dari PHP Legacy Code ke Go Microservices

10 Agustus 2026 • 10 min read • by Arif Kurniawan

Saya pernah spending 2 tahun maintenance codebase warisan. Setiap deploy adalah gambling. Sekarang saya bisa deploy 20 kali sehari tanpa worry. Perubahannya bukan cuma teknikal — tapi cara berpikir.

Starting Point: Awal Karir dengan PHP + MySQL

Semua bermula di tahun 2021. Saya lulus dari kampus, dapat kesempatan kerja di perusahaan finance kecil.-tech. Bukan startup keren — lebih ke perusahaan tradisional yang mulai go digital.

Tech stack waktu itu: PHP + MySQL. Bukan PHP 8 dengan modern tooling — tapi PHP 5.x yang sudah deprecated, codebase yang inherited dari vendor yang sudah tidak ada. Setiap file ada comment dalam Bahasa Indonesia campur aduk dengan English, kadang ada Bahasa Jawa.

Yang membuat saya bertahan bukan teknologinya — tapi opportunity untuk belajar. Di situ saya belajar hal yang tidak diajarkan di kampus:

Saya spending 8 bulan pertama mostly reading code. Tidak ada dokumentasi — kode adalah dokumentasi. Ini坏事 tapi juga好事 — forcing saya untuk benar-benar memahami bagaimana setiap pieces connect.

Turning Point: Pertama Kali Touch Go dan Kenapa Switch

Di tahun 2022, perusahaan mulai plan rebuild system. Saya dapat kesempatan untuk evaluate Go sebagai primary language. Awalnya skeptis — PHP sudah bisa handle banyak case, kenapa harus switch?

Then I actually tried it.

Beberapa hal yang langsung terasa berbeda:

  1. Compilation error as a feature — di PHP, typo di variable name baru ketahuan saat runtime. Di Go, compiler catch semuanya sebelum kode jalan. Produktivitas naik significant.
  2. Concurrency yang first-class — goroutines dan channels membuat async programming approachable. Di PHP, kita limited dengan blocking I/O atau perlu setup worker queue yang complex.
  3. Standard library yang complete — net/http, encoding/json, database/sql — semua sudah ada. Tidak perlu install 50 composer packages.
  4. Deploy sebagai single binary — tidak ada PHP-FPM, tidak ada opcache configuration yang mystical. Satu binary, jalan di mana saja.
// Go's concurrency model — straightforward dan powerful
package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

func fetchUserData(ctx context.Context, userID string, wg *sync.WaitGroup) {
    defer wg.Done()

    select {
    case <-ctx.Done():
        fmt.Printf("Request cancelled for user %s\n", userID)
        return
    case <-time.After(2 * time.Second):
        fmt.Printf("Fetched data for user %s\n", userID)
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    var wg sync.WaitGroup
    userIDs := []string{"user1", "user2", "user3"}

    for _, id := range userIDs {
        wg.Add(1)
        go fetchUserData(ctx, id, &wg)
    }

    wg.Wait()
    fmt.Println("All fetches completed")
}

Tapi yang paling saya appreciate: Go forces you to think about errors explicitly. Tidak ada exception mechanism yang memungkinkan kamu untuk mengabaikan error sampai somewhere else catch it. Every error is a value, dan kamu harus handle it.

// Error handling yang explicit di Go — tidak bisa diabaikan
func GetUser(id string) (*User, error) {
    user, err := userRepo.FindByID(id)
    if err != nil {
        if err == ErrNotFound {
            return nil, fmt.Errorf("user %s not found: %w", id, err)
        }
        return nil, fmt.Errorf("get user: %w", err) // Wrapped untuk tracing
    }
    return user, nil
}

Nightmare Story: Hardest Legacy Migration

Di PrivyID, saya dapat tugas yang levelnya berbeda: migration dari monolith PHP ke microservices Go. Bukan hanya technical challenge — tapi juga organizational challenge.

System yang existing sudah 5 tahun, dipakai oleh 50+ enterprise clients,每天Processing 1 juta+ transactions. Business tidak bisa stop — downtime budget adalah zero.

Masalah paling challenging:

1. Data Migration yang Tidak Boleh Gagal

Migrasi data dari schema lama ke schema baru dengan zero downtime. Solusi: dual-write dengan transactional outbox, seperti yang saya ceritakan di article sebelumnya tentang Kafka migration.

2. Zero-Downtime Deployment dengan Legacy Dependency

Services baru harus bisa talk ke services lama yang belum di-migrate, dan vice versa. Mismatch di API contract adalah daily occurrence.

// API Gateway untuk handle新旧 systems communication
package gateway

type APIRouter struct {
    legacyHandler *LegacyHandler // PHP services
    newHandler    *NewHandler    // Go services
}

func (r *APIRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    path := req.URL.Path

    // Route ke legacy atau new berdasarkan feature flag
    if featureflags.IsEnabled(req.Context(), "new-user-service", req.Header.Get("X-Client-ID")) {
        r.newHandler.ServeHTTP(w, req)
        return
    }

    r.legacyHandler.ServeHTTP(w, req)
}

3. Team yang Belum Familiar dengan Microservices

Tidak semua tim bisa langsung adapt. Ada resistance — "Kenapa harus ubah yang sudah jalan?" Komunikasi dan incremental demonstration of value itu krusial untuk get buy-in.

Hardest moment: saat production incident di week kedua migration. Data inconsistency between old dan new system. 3 hari tidak tidur, hotfix di production,dan aku harus объяснять ke manajemen kenapa ini happened dan bagaimana kita prevent di masa depan.

"That incident taught me more about distributed systems than any book or course ever could. Real production problems are the best teachers."

Breakthrough: Moment Ketika System Finally "Click"

Setelah 8 bulan migration, akhirnya paham. Bukan tiba-tiba — gradual accumulation of understanding. Tapi ada moment spesifik:

Saat pertama kali kita deploy feature baru (notification preference management) sebagai independent service. Tim notification bisa develop, test, dan deploy tanpa coordinated release dengan tim user management. Dalam 2 minggu mereka deliver feature yang sebelumnya butuh 2 bulan kalau integrated ke monolith.

Itu moment where I realized: ini bukan tentang technology. Ini tentang bagaimana team работает.

Beberapa insight yang baru "click":

Sekarang di Indodax, saya apply semua lessons itu. Setiap architectural decision kita tanya: "Apakah ini membantu team move faster dan lebih reliable?" Kalau tidak, why bother.

Advice Practicals untuk Engineers yang Mau Level Up

Dari 5 tahun pengalaman, berikut advice yang saya wish somebody told me earlier:

1. Learn Systems Thinking, Not Just Syntax

Bisa bahasa pemrograman itu hanya small part. Yang lebih penting: bagaimana system bekerja secara keseluruhan. Baca tentang:

// Belajar dari kode Open Source yang bagus
// Baca source code Kubernetes, Docker, atau library yang kamu pakai daily
// Contoh: bagaimana golang.org/x/net/http2 handle connection pooling?

// github.com/golang/net/http2/hpack/hpack.go
// Understanding HPACK compression bisa help kamu optimize API performance

// Belajar dari production incidents
// Postmortem dari Google, Netflix, Amazon — learn from others' mistakes
// aws.amazon.com/message/producer-amazon-kinesis/

2. Build Side Projects that Solve Real Problems

Tutorials itu bagus untuk syntax, tapi tidak teach you how to think. Build something yang kamu actually want to use:

Saya build notification aggregator untuk replace semua email subscriptions yang tidak pernah dibaca. Simple project, tapi I learned about background jobs, rate limiting, dan email parsing.

3. Find Mentors and Community

Software engineering is a team sport. Tidak bisa belajar semuanya sendiri.

4. Prioritize Debugging Skills Over Writing Code Skills

Real world: 70% of time spent debugging and maintaining, 30% writing new code. Skills yang paling valuable:

  1. Reading existing code dan understanding it quickly
  2. Debugging production issues dengan limited information
  3. Reading logs dan traces effectively
  4. Formulating hypotheses dan testing them systematically
// Debugging tool yang wajib dikuasai:
//
// 1. pprof untuk Go performance profiling
//    go tool pprof http://localhost:6060/debug/pprof/heap
//
// 2. Delve untuk debugging Go code
//    dlv debug main.go
//
// 3. Structured logging dengan correlation IDs
//    Gunakan slog atau zap dengan request ID tracking
//
// 4. Distributed tracing dengan OpenTelemetry
//    traceID dari request headers — follow request across services

5. Understand Business Context

Technical skills without business understanding gives you solutions to wrong problems. Sebelum mulai coding, tanya:

Saya pernah spend 2 minggu membangun fitur yang technically elegant, tapi business tidak pernah launch karena priority change. Waktu yang terbuang. Dengan better business context understanding, kita bisa prioritize better.

Penutup

5 tahun sounds lama kalau ditulis di CV, tapi sebenarnya masih early stage. Masih banyak yang perlu dipelajari — distributed systems di scale yang lebih besar, machine learning infrastructure, platform engineering.

Yang paling berubah dari awal karir sampai sekarang bukan teknisnya — tapi mindset. Dulu saya think of myself sebagai "PHP developer" atau "Go developer". Sekarang think of myself sebagai problem solver yang happens to use technology sebagai tool.

Technology akan terus berubah. PHP sudah berganti jadi Go, mungkin 5 tahun lagi ada new language. Tapi fundamental skills — systems thinking, debugging, communication, business context — akan tetap relevant.

"Invest di fundamentals, bukan di specific technologies. Technologies are tools, not identities."

Terima kasih sudah baca. Mungkin cerita ini resonate dengan kamu yang juga sedang di perjalanan yang sama. Mari kita continue learning together. 🚀