12 Factor App
Memahami 12-Factor App: Prinsip-Prinsip untuk Membangun Aplikasi Modern yang Scalable dan Portable
Pengantar
12-Factor App adalah sebuah metodologi yang dikembangkan oleh tim di Heroku pada tahun 2011 untuk membangun aplikasi software-as-a-service (SaaS) yang modern. Metodologi ini terdiri dari 12 prinsip utama yang dirancang untuk membuat aplikasi yang mudah dikembangkan, di-deploy, di-scale, dan di-maintain di lingkungan cloud. Prinsip-prinsip ini menekankan pada portabilitas, ketahanan, dan otomatisasi, sehingga aplikasi dapat berjalan dengan konsisten di berbagai environment seperti development, staging, dan production.
Asal-usul 12-Factor App berasal dari pengalaman Heroku dalam menangani ribuan aplikasi, dan telah menjadi standar de facto untuk pengembangan cloud-native. Metodologi ini tidak terbatas pada bahasa pemrograman tertentu, tetapi dalam artikel ini, kita akan fokus pada contoh konkret menggunakan Golang (Go) di mana relevan, termasuk sample code. Artikel ini akan menjelaskan setiap faktor secara lengkap, dengan struktur yang jelas: penjelasan, poin kunci, manfaat, dan contoh konkret.
Dengan mengikuti 12 faktor ini, developer dapat membangun aplikasi yang lebih robust, seperti yang dijelaskan dalam situs resmi metodologi ini. Mari kita bahas satu per satu.
I. Codebase: Satu Codebase dengan Banyak Deploy
Penjelasan
Faktor pertama menekankan bahwa aplikasi harus memiliki satu codebase tunggal yang dilacak menggunakan sistem version control seperti Git. Codebase ini digunakan untuk banyak deploy (misalnya, development, staging, production), tetapi tetap satu repo. Jika ada multiple codebase, itu berarti sistem terdistribusi di mana setiap komponen adalah app terpisah.
Poin Kunci
Gunakan version control system (VCS) seperti Git, Mercurial, atau Subversion.
Satu codebase untuk satu app; shared code harus diekstrak menjadi library via dependency manager.
Deploy yang berbeda (staging vs. production) berbagi codebase yang sama, meskipun mungkin ada commit yang belum di-deploy.
Manfaat
Memudahkan pelacakan perubahan dan kolaborasi tim.
Menghindari duplikasi code dan memastikan konsistensi antar environment.
Contoh Konkrit
Bayangkan sebuah aplikasi web e-commerce. Codebase-nya disimpan di GitHub, dan dari sana, Anda deploy ke Heroku untuk production, sementara developer lokal menjalankannya di mesin mereka. Jika ada fitur baru, commit ke branch utama, lalu deploy ke staging untuk testing sebelum production. Dalam Golang, struktur direktori sederhana bisa seperti ini (dari contoh implementasi microservice):
.
βββ cmd # Entry point untuk CLI/server
βββ db # Koneksi dan query database
βββ internal # Logika bisnis internal
β βββ blog # Modul spesifik seperti blog
βββ migrations # File migrasi database
βββ transport # Komunikasi client-server, e.g., HTTP
βββ http # Implementasi server HTTP
Ini memastikan satu codebase untuk semua deploy.
II. Dependencies: Deklarasikan dan Isolasi Dependensi Secara Eksplisit
Penjelasan
Aplikasi harus mendeklarasikan semua dependensi secara lengkap dalam manifest (seperti go.mod di Golang) dan mengisolasi mereka agar tidak bergantung pada paket sistem secara implisit.
Poin Kunci
Gunakan dependency manager untuk deklarasi (e.g., Go Modules).
Isolasi dependensi agar app tidak "bocor" ke dependensi sistem.
Berlaku sama untuk production dan development.
Manfaat
Memudahkan setup baru: Developer cukup checkout code, install runtime, dan dependency manager.
Menghindari masalah "works on my machine" karena dependensi eksplisit.
Contoh Konkrit
Dalam Golang, gunakan go.mod
untuk mendeklarasikan dependensi seperti Gin untuk web framework. Misalnya, app tidak boleh bergantung pada curl
sistem; vendor-kan jika diperlukan. Contoh dari praktik: Dalam microservice Golang, dependensi seperti database (PostgreSQL) dikelola via config, bukan hardcoded. Sample code sederhana untuk inisialisasi dependensi:
// go.mod
module example/app
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
// dependensi lain
)
Lalu, jalankan go mod tidy
untuk isolasi.
III. Config: Simpan Konfigurasi di Environment
Penjelasan
Konfigurasi (seperti credential database, API keys) harus dipisah dari code dan disimpan di environment variables (env vars), bukan di file config yang bisa bocor ke repo.
Poin Kunci
Config bervariasi antar deploy; code tetap sama.
Gunakan env vars karena bahasa-agnostik dan aman.
Hindari grouping config menjadi "environments" (e.g., config.dev.yml); gunakan granular env vars.
Manfaat
Mengurangi risiko kebocoran credential.
Memudahkan scaling tanpa ubah code.
Contoh Konkrit
Contoh: Database URL disimpan di DATABASE_URL
env var. Dalam Golang, akses via os.Getenv
. Dari contoh lengkap menggunakan Viper untuk config fleksibel:
package main
import (
"fmt"
"os"
"github.com/spf13/viper"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "app",
Short: "Contoh 12-Factor Config",
}
func main() {
viper.SetEnvPrefix("APP") // Prefix env vars dengan APP_
viper.AutomaticEnv() // Baca env vars otomatis
rootCmd.PersistentFlags().String("db_url", "", "URL Database")
viper.BindPFlag("db_url", rootCmd.PersistentFlags().Lookup("db_url"))
viper.SetDefault("db_url", "postgres://localhost:5432/db")
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// Akses config: viper.GetString("db_url")
}
Jalankan dengan APP_DB_URL=postgres://prod:5432/db go run main.go
.
IV. Backing Services: Perlakukan Layanan Pendukung sebagai Attached Resources
Penjelasan
Layanan eksternal seperti database, queue, atau SMTP diperlakukan sebagai resources yang bisa diganti tanpa ubah code, hanya via config.
Poin Kunci
Tidak bedakan local vs. third-party services.
Akses via URL/credential di config.
Setiap service adalah resource terpisah.
Manfaat
Fleksibilitas: Ganti provider (e.g., local MySQL ke AWS RDS) tanpa rebuild.
Loose coupling untuk scaling.
Contoh Konkrit
Contoh: Ganti database lokal dengan cloud service via env var DATABASE_URL
. Dalam Golang microservice, gunakan RabbitMQ sebagai backing service untuk events, dikonfigurasi di config.yaml
atau env vars. Sample koneksi database:
import (
"database/sql"
_ "github.com/lib/pq"
)
func ConnectDB() *sql.DB {
url := os.Getenv("DATABASE_URL")
db, err := sql.Open("postgres", url)
if err != nil {
log.Fatal(err)
}
return db
}
V. Build, Release, Run: Pisahkan Tahap Build, Release, dan Run Secara Ketat
Penjelasan
Transformasi codebase menjadi deploy melalui tiga tahap: Build (kompilasi + dependensi), Release (gabung build dengan config), Run (jalankan proses).
Poin Kunci
Tahap terpisah; tidak ubah code di runtime.
Setiap release punya ID unik (e.g., timestamp).
Run stage sederhana untuk robustness.
Manfaat
Reproducibility dan mudah rollback.
Audit trail melalui ledger release.
Contoh Konkrit
Gunakan Docker untuk build: Build image dari codebase, release dengan tag + config env, run via container. Dalam Golang, gunakan Makefile untuk stages. Contoh Dockerfile sederhana:
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp cmd/main.go
FROM alpine:latest
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]
Deploy dengan docker run -e CONFIG_VAR=value myapp-image:v1
.
VI. Processes: Jalankan App sebagai Proses Stateless
Penjelasan
App dijalankan sebagai satu atau lebih proses stateless; data persisten simpan di backing service.
Poin Kunci
Share-nothing: Tidak asumsikan state lokal tersedia di request berikutnya.
Cache sementara OK, tapi jangan bergantung.
Hindari sticky sessions.
Manfaat
Scalability: Proses bisa di-scale horizontally.
Ketahanan terhadap restart.
Contoh Konkrit
Session data simpan di Redis, bukan memory proses. Dalam Golang, gunakan Redis client untuk state:
import "github.com/redis/go-redis/v9"
var rdb = redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_ADDR"),
})
func StoreSession(key, value string) {
rdb.Set(context.Background(), key, value, time.Hour)
}
VII. Port Binding: Ekspor Layanan via Port Binding
Penjelasan
App self-contained: Bind ke port dan listen request, tanpa bergantung webserver eksternal.
Poin Kunci
Sertakan webserver library sebagai dependensi.
Akses via port (e.g., localhost:5000 di dev).
App bisa jadi backing service untuk app lain via URL.
Manfaat
Portabilitas tinggi; tidak bergantung container webserver.
Fleksibel untuk microservices.
Contoh Konkrit
Dalam Golang, gunakan net/http untuk bind port. Sample:
import (
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
})
http.ListenAndServe(":"+port, nil)
}
VIII. Concurrency: Scale Out via Model Proses
Penjelasan
Scale dengan menambah proses, bukan thread internal; handle diversity via process types (e.g., web, worker).
Poin Kunci
Inspirasi dari Unix daemon.
Gunakan goroutines untuk multiplexing internal, tapi scale via proses.
Process formation: Array tipe proses dan jumlahnya.
Manfaat
Scaling sederhana dan reliable horizontally.
Contoh Konkrit
Gunakan supervisor seperti systemd untuk manage multiple processes. Dalam Golang, graceful shutdown untuk concurrency:
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{Addr: ":8080"}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
IX. Disposability: Maksimalkan Robustness dengan Startup Cepat dan Shutdown Graceful
Penjelasan
Proses disposable: Startup cepat (detik), shutdown graceful saat SIGTERM.
Poin Kunci
Minimize startup time.
Handle shutdown: Stop listen, finish job, return ke queue.
Robust terhadap crash mendadak.
Manfaat
Scaling elastis dan deploy cepat.
Ketahanan tinggi.
Contoh Konkrit
Gunakan code shutdown seperti di atas (dari concurrency). Untuk worker, return job ke queue seperti RabbitMQ NACK.
X. Dev/Prod Parity: Jaga Kesamaan Development, Staging, dan Production
Penjelasan
Minimalkan gap antara dev dan prod: Waktu, personel, tools.
Poin Kunci
Continuous deployment: Deploy cepat.
Developer ikut deploy dan monitor.
Gunakan tools sama (e.g., Docker untuk parity).
Manfaat
Kurangi error karena perbedaan environment.
Contoh Konkrit
Gunakan Docker Compose di dev, Kubernetes di prod dengan config sama. Backing services seperti PostgreSQL di semua env.
XI. Logs: Perlakukan Logs sebagai Event Streams
Penjelasan
Logs adalah stream event; app tulis ke stdout, environment handle routing.
Poin Kunci
Unbuffered ke stdout.
Route ke file, Splunk, atau Hive untuk analisis.
Manfaat
Mudah monitor dan analisis.
Alerting berdasarkan heuristic.
Contoh Konkrit
Dalam Golang, gunakan log package ke stdout. Route via tools seperti Fluentd.
import "log"
func main() {
log.Println("Event: User logged in")
}
XII. Admin Processes: Jalankan Tugas Admin sebagai Proses One-Off
Penjelasan
Tugas admin (e.g., migrasi DB) jalankan sebagai proses terpisah di environment sama dengan app.
Poin Kunci
Gunakan codebase dan config sama.
Isolasi dependensi.
Jalankan via script committed ke repo.
Manfaat
Konsistensi; hindari sync issues.
Contoh Konkrit
Migrasi DB via command: go run migrate.go
. Gunakan Cobra untuk CLI tasks.
Kesimpulan
12-Factor App menyediakan kerangka kerja komprehensif untuk aplikasi modern. Dengan menerapkannya di Golang, seperti contoh di atas, Anda bisa membangun app yang scalable dan maintainable. Untuk detail lebih lanjut, kunjungi situs resmi atau eksplorasi library seperti Viper dan envconfig. Artikel ini bisa menjadi panduan untuk tim developer Anda.
Last updated