Go Modül Sistemi: go mod ile Bağımlılık Yönetimi
Go ile bir proje geliştirmeye başladığınızda, ilk karşılaştığınız sorulardan biri şu olur: “Bu bağımlılıkları nasıl yöneteceğim?” Eski GOPATH döneminde bu soru gerçekten baş ağrıtıcıydı. Neyse ki Go 1.11 ile hayatımıza giren ve Go 1.16 itibarıyla varsayılan hale gelen Go Modül Sistemi, bu sorunu temiz ve anlaşılır bir şekilde çözüyor. Bu yazıda go mod komutlarını, gerçek dünya senaryolarıyla birlikte ele alacağız.
Go Modül Sistemi Neden Önemli?
Eski GOPATH modelinde tüm Go kodlarınızı tek bir dizin altında tutmak zorundaydınız. Farklı projeler için farklı sürümde aynı kütüphaneyi kullanamazdınız. Bu durum özellikle ekip ortamında, CI/CD pipeline’larında ve production deployment’larında ciddi sorunlar yaratıyordu.
Go modülleri bu problemi şu şekilde çözüyor:
- Her proje kendi bağımlılık listesini
go.moddosyasında tutar go.sumdosyası ile her bağımlılığın hash değeri doğrulanır, güvenlik sağlanır- Aynı makinede farklı projeler farklı sürümleri kullanabilir
- Reproducible build garantisi elde edersiniz
Bir sysadmin olarak şunu söyleyeyim: Go modüllerini doğru kullandığınızda, 6 ay sonra başka bir makinede projeyi derlerken “neden çalışmıyor” sorusunu çok daha az sorarsınız.
Modül Sistemi Kurulumu ve İlk Adımlar
Öncelikle Go kurulumunuzun güncel olduğundan emin olun. Go 1.16 ve üzeri sürümlerde modül sistemi zaten varsayılan olarak aktiftir.
# Go sürümünü kontrol et
go version
# GOPATH ve GOMODCACHE kontrolü
go env GOPATH
go env GOMODCACHE
go env GOFLAGS
Yeni bir proje başlatmak için önce dizini oluşturup go mod init komutunu çalıştırıyoruz:
# Proje dizini oluştur
mkdir ~/projects/myapp
cd ~/projects/myapp
# Modülü başlat (modül adı genellikle repo yoluyla eşleşir)
go mod init github.com/kullaniciadi/myapp
# Oluşturulan dosyayı incele
cat go.mod
Bu komutun çıktısı şöyle görünecek:
module github.com/kullaniciadi/myapp
go 1.21
İki satır, gayet sade. Modül adı ve minimum Go sürümü. Şimdi bu yapıyı genişletelim.
go.mod Dosyasını Anlamak
go.mod dosyası, projenizin kimlik kartı gibidir. Zamanla şu bölümlerden oluşur:
- module: Modülün import yolu
- go: Minimum Go sürümü direktifi
- require: Doğrudan ve dolaylı bağımlılıklar
- replace: Bağımlılığı local ya da fork edilmiş versiyonla değiştirme
- exclude: Belirli sürümleri dışlama
Gerçek bir go.mod dosyası şöyle görünebilir:
# Örnek go.mod içeriği (cat komutuyla görüntüleme)
cat go.mod
# Çıktı:
# module github.com/kullaniciadi/myapp
#
# go 1.21
#
# require (
# github.com/gin-gonic/gin v1.9.1
# github.com/redis/go-redis/v9 v9.3.0
# go.uber.org/zap v1.26.0
# github.com/stretchr/testify v1.8.4 // indirect
# )
// indirect etiketi gördüğünüzde paniklemeyín. Bu, doğrudan import etmediğiniz ama bağımlılıklarınızın bağımlılıklarına işaret eder.
Bağımlılık Ekleme ve Güncelleme
Paket Ekleme
# Belirli bir paket ekle
go get github.com/gin-gonic/gin
# Belirli bir sürüm belirt
go get github.com/gin-gonic/[email protected]
# En son minor/patch sürümünü al
go get github.com/gin-gonic/gin@latest
# Belirli bir commit hash ile ekle (dikkatli kullanın)
go get github.com/gin-gonic/gin@abc1234
# go.mod ve go.sum güncellendikten sonra kontrol et
cat go.mod
cat go.sum | head -5
Pratik bir senaryo: Bir web API projesi geliştiriyorsunuz. Gin framework, Redis client ve structured logging için Zap kütüphanesine ihtiyacınız var.
# Web framework
go get github.com/gin-gonic/[email protected]
# Redis client
go get github.com/redis/go-redis/v9
# Structured logging
go get go.uber.org/[email protected]
# Tüm bağımlılıkları indir
go mod download
# Bağımlılık ağacını görüntüle
go mod graph | head -20
Bağımlılık Güncelleme
# Tek bir paketi güncelle
go get github.com/gin-gonic/gin@latest
# Tüm direct bağımlılıkları minor/patch ile güncelle
go get -u ./...
# Sadece patch güncellemeleri al (daha güvenli)
go get -u=patch ./...
# Sonrasında mutlaka test et
go test ./...
Production ortamında go get -u ./... komutunu kullanırken dikkatli olun. Özellikle büyük projelerde beklenmedik breaking change’ler gelebilir. Ben genellikle bağımlılıkları tek tek güncellemeyi ve her adımda testleri çalıştırmayı tercih ederim.
go.sum Dosyası ve Güvenlik
go.sum dosyası genellikle göz ardı edilir ama aslında çok kritik bir rol oynuyor. Her bağımlılığın kriptografik hash değerini saklar.
# go.sum örnek satırları
cat go.sum | grep "gin-gonic"
# go.sum dosyasını doğrula
go mod verify
# Başarılı çıktı:
# all modules verified
Neden önemli? Diyelim ki bir paket yayıncısı kötü niyetli bir güncelleme yaptı veya CDN üzerinde dosya değiştirildi. go mod verify komutu bunu anında yakalar. CI/CD pipeline’ınızda bu komutu mutlaka çalıştırın.
go.sum dosyasını .gitignore‘a eklemek yaygın bir hatadır. Bu dosyayı her zaman Git’e commit edin.
Vendor Dizini Kullanımı
Bazı production ortamlarında internet erişimi kısıtlı olabilir. Air-gapped sistemler, katı güvenlik politikaları olan kurumlar veya sadece “bağımlılıklarımı kontrol altında tutmak istiyorum” diyen sysadmin’ler için vendor modu var:
# Tüm bağımlılıkları vendor/ dizinine kopyala
go mod vendor
# Dizin yapısını kontrol et
ls vendor/
ls vendor/github.com/
# Vendor modunda build al
go build -mod=vendor ./...
# Vendor modunda test çalıştır
go test -mod=vendor ./...
# vendor dizinini doğrula (go.mod ile tutarlılık kontrolü)
go mod verify
Vendor dizinini kullandığınızda, vendor/ klasörünü de Git’e commit etmeniz gerekir. Bu klasör büyük olabilir ama offline build imkanı sağlar. CI/CD sistemlerinde network bağımlılığını ortadan kaldırır.
Workspace Modu: Çoklu Modül Geliştirme
Go 1.18 ile gelen workspace modu, birden fazla modül üzerinde eş zamanlı geliştirme yapıyorsanız hayat kurtarır. Önceden replace direktifine başvurmak zorunda kalıyorduk, artık go.work dosyası işimizi görüyor.
Senaryo: Hem bir kütüphane (mylib) hem de bu kütüphaneyi kullanan bir uygulama (myapp) geliştiriyorsunuz. Kütüphaneyi yayınlamadan önce uygulamada test etmek istiyorsunuz.
# Dizin yapısı
# ~/projects/
# ├── mylib/
# └── myapp/
cd ~/projects
# Her iki modülü başlat
mkdir mylib && cd mylib
go mod init github.com/kullaniciadi/mylib
cd ..
mkdir myapp && cd myapp
go mod init github.com/kullaniciadi/myapp
cd ..
# Workspace oluştur (projects/ dizininde)
go work init ./mylib ./myapp
# go.work dosyasını incele
cat go.work
go.work dosyası şu şekilde görünecek:
# go.work içeriği
cat go.work
# Çıktı:
# go 1.21
#
# use (
# ./myapp
# ./mylib
# )
Artık myapp içinde mylib‘i publish etmeden import edebilirsiniz. Geliştirme bittikten sonra go.work dosyasını .gitignore‘a ekleyip her modülü ayrı ayrı release edebilirsiniz.
# Workspace'e yeni modül ekle
go work use ./yenimodul
# Workspace senkronizasyonu
go work sync
Pratik Senaryo: Microservice Projesi
Gerçek dünya senaryosu olarak bir microservice projesi kuralım. Bu servis bir PostgreSQL veritabanına bağlanıyor, HTTP endpoint’leri sunuyor ve Prometheus metrikleri export ediyor.
# Proje başlat
mkdir ~/projects/user-service
cd ~/projects/user-service
go mod init github.com/sirketim/user-service
# Bağımlılıkları ekle
go get github.com/gin-gonic/[email protected]
go get github.com/lib/[email protected]
go get gorm.io/[email protected]
go get gorm.io/driver/[email protected]
go get github.com/prometheus/[email protected]
go get github.com/spf13/[email protected]
# Gereksiz bağımlılıkları temizle
go mod tidy
# Sonucu kontrol et
cat go.mod
go mod tidy komutu çok önemli. Şunları yapar:
- Import ettiğiniz ama
go.mod‘da olmayan paketleri ekler go.mod‘da olup hiç kullanılmayan paketleri kaldırırgo.sum‘ı günceller
Bu komutu hem geliştirme sırasında hem de CI/CD’de çalıştırın. Eğer go mod tidy sonrası go.mod değiştiyse, commit’i reddedin.
CI/CD Pipeline Entegrasyonu
GitHub Actions veya GitLab CI’da Go projesi için örnek bir bağımlılık yönetimi adımı:
# Önce bağımlılıkları cache'den yükle, yoksa indir
# Bu komut dizisi CI ortamında kullanılır
# go.sum kontrolü - değişiklik varsa build başarısız olsun
go mod tidy
git diff --exit-code go.mod go.sum
# Bağımlılıkları doğrula
go mod verify
# Download (cache'leme için)
go mod download
# Build
go build -mod=readonly ./...
# Test
go test -mod=readonly -race ./...
-mod=readonly flag’i production build’larında kullanın. Bu flag, build sırasında go.mod dosyasının değiştirilmesini engeller. Beklenmedik bağımlılık değişikliklerinin önüne geçer.
Özel Paket Sunucusu (GOPROXY) Yapılandırması
Kurumsal ortamlarda genellikle özel bir proxy kullanmanız gerekir. Türkiye’deki bazı şirketlerin iç ağ kısıtlamaları nedeniyle doğrudan proxy.golang.org‘a erişemediğini de gördüm.
# Mevcut proxy ayarlarını gör
go env GOPROXY
go env GONOSUMCHECK
go env GONOSUMDB
go env GOPRIVATE
# Özel proxy ayarla (Nexus, Artifactory, Athens kullanıyorsanız)
go env -w GOPROXY="https://proxy.sirketim.internal,https://proxy.golang.org,direct"
# Private repo'lar için sum database bypass
go env -w GONOSUMDB="github.com/sirketim/*"
go env -w GOPRIVATE="github.com/sirketim/*"
# Ayarları kalıcı olarak .bashrc veya .profile'a ekle
echo 'export GOPROXY="https://proxy.golang.org,direct"' >> ~/.bashrc
echo 'export GOPRIVATE="github.com/sirketim/*"' >> ~/.bashrc
GOPROXY değerleri:
- direct: Kaynağa doğrudan git, proxy kullanma
- off: Bu kaynağa erişimi tamamen engelle
- https://proxy.golang.org: Google’ın resmi proxy’si
Birden fazla proxy virgülle ayrılır ve sırası önemlidir. İlk başarılı yanıt dönen proxy kullanılır.
replace Direktifi ile Local Geliştirme
Workspace modu yoksa veya Go 1.18 öncesindeyseniz, replace direktifi local geliştirme için kullanışlıdır:
# go.mod dosyasına replace ekle
cat go.mod
# go mod edit ile replace ekle
go mod edit -replace github.com/kullaniciadi/mylib=../mylib
# Sonucu kontrol et
cat go.mod
# Artık go.mod'da şu satır var:
# replace github.com/kullaniciadi/mylib => ../mylib
# Local değişiklikleri test et
go build ./...
go test ./...
# İş bitince replace'i kaldır
go mod edit -dropreplace github.com/kullaniciadi/mylib
go mod tidy
Dikkat: replace direktiflerini production build’larına sakın bırakmayın. CI/CD’de bu direktiflerin varlığını kontrol eden bir adım ekleyin.
Sürüm Yönetimi ve Semantic Versioning
Go modülleri semantic versioning (semver) kullanır. Bu noktada birkaç önemli kural var:
- v1 ve altı için import path değişmez:
github.com/kullaniciadi/mylib - v2 ve üzeri için import path değişmeli:
github.com/kullaniciadi/mylib/v2
Bir kütüphanenin v2’sine geçiş yaparken:
# v1 kullanımı
go get github.com/some/[email protected]
# v2'ye geçiş (import path değişir!)
go get github.com/some/library/[email protected]
# go.mod'da kontrol et
grep "some/library" go.mod
# Kodda da import path'i güncellemeyi unutmayın
# Eski: import "github.com/some/library"
# Yeni: import "github.com/some/library/v2"
Bu v2 kuralı başta garip geliyor ama mantıklı. Import path değişince, aynı projede hem v1 hem v2’yi kullanabilirsiniz. Geçiş sürecini kademeli yapabilirsiniz.
Sorun Giderme
Sık karşılaşılan sorunlar ve çözümleri:
# "cannot find module providing package" hatası
# Çözüm: go mod tidy çalıştır
go mod tidy
# "ambiguous import" hatası - aynı paket iki farklı yerden
# go mod graph ile bağımlılık ağacını incele
go mod graph | grep "sorunlu-paket"
# Checksum uyuşmazlığı
# go.sum'ı temizle ve yeniden oluştur
go clean -modcache
go mod download
# Önbellekteki modülleri tamamen temizle
go clean -cache -modcache -testcache
# Hangi modülün hangi paketi sağladığını bul
go mod why github.com/bazi/paket
# Bağımlılık neden kullanılıyor, izini sür
go mod why -m github.com/bazi/paket
go mod why komutu özellikle “bu // indirect bağımlılık nereden geliyor?” sorusunu cevaplamak için mükemmel.
Modül Önbelleği Yönetimi
# Önbellek konumunu öğren
go env GOMODCACHE
# Önbellekteki tüm modülleri listele
ls $(go env GOMODCACHE)/cache/download/
# Disk kullanımını kontrol et (zamanla büyür)
du -sh $(go env GOMODCACHE)
# Sadece belirli bir modülü önbellekten sil
go clean -modcache
# Dikkat: Bu tüm önbelleği siler!
# Daha seçici temizlik için rm kullan
rm -rf $(go env GOMODCACHE)/github.com/gin-gonic/
Sunucularda disk alanı sorun olabilir. Go modül önbelleği zamanla büyür. Ben genellikle CI/CD sunucularında haftalık bir cron job ile eski önbellekleri temizlerim.
Güvenlik Taraması
Bağımlılıklarınızda bilinen güvenlik açıkları olup olmadığını kontrol etmek için:
# Go'nun resmi güvenlik aracı
go install golang.org/x/vuln/cmd/govulncheck@latest
# Projeyi tara
govulncheck ./...
# Sadece binary'yi tara
govulncheck -mode=binary ./mybinary
# Çıktıda "No vulnerabilities found" görmek istiyorsunuz
# Varsa hangi bağımlılığı güncelleyeceğinizi gösterir
Bu taramayı CI/CD pipeline’ınıza ekleyin. Özellikle finansal veya healthcare sektöründe çalışıyorsanız bu adım zorunlu olmalı.
Sonuç
Go modül sistemi, GOPATH döneminin kaosunu gerçekten çözdü. go.mod ve go.sum dosyaları küçük görünse de arkasında sağlam bir düşünce var: reproducible builds, güvenlik doğrulaması ve sürüm izolasyonu.
Günlük pratikte şu alışkanlıkları edinin:
- Yeni bağımlılık ekledikten sonra her zaman
go mod tidyçalıştırın go.sumdosyasını asla.gitignore‘a eklemeyin- CI/CD’de
go mod verifyvego build -mod=readonlykullanın - Düzenli olarak
govulncheckçalıştırın - Production ortamlarında
go mod vendorile vendor dizini kullanmayı değerlendirin
Private repo’larla, özel proxy’lerle veya workspace modu ile çalışıyorsanız başta karmaşık gelecek, ama temel komutları özümsedikten sonra sistem oldukça mantıklı. Go ekibi bu araçları gerçekten sysadmin dostu tasarlamış, hem tekrarlı hem de otomasyon için uygun.
Herhangi bir projede go mod tidy && go mod verify ikilisi sorunlarınızın büyük çoğunluğunu çözer. Geri kalanlar için go mod why ile izleri takip edin.
