GO LANG // CONCURRENCYD::03 ORTA
24m READCOMPLETION: 81%ID::GO-201

GO GOROUTINE, CHANNEL VE REST API

Concurrent programlama, channel desenleri ve net/http ile REST API

Go'nun en güçlü özellikleri goroutine'ler ve channel'lar ile eşzamanlı (concurrent) programlama yapabilmesidir. Bu derste Go'nun concurrency modelini anlıyor ve net/http ile REST API geliştiriyoruz.

Goroutine Temelleri

// GO //
package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
func main() {
    // Goroutine — "go" anahtar kelimesiyle başlatılır
    go func() {
        fmt.Println("Ben goroutine'yim")
    }()
 
    // WaitGroup — goroutine'lerin bitmesini bekle
    var wg sync.WaitGroup
 
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(time.Duration(id) * 100 * time.Millisecond)
            fmt.Printf("Goroutine %d tamamlandı\n", id)
        }(i)
    }
 
    wg.Wait() // Hepsi bitene dek bekle
    fmt.Println("Tümü tamamlandı")
}

Channel — Goroutine İletişimi

// GO //
// Unbuffered channel — senkron iletişim
ch := make(chan int)
 
go func() {
    ch <- 42  // Gönder (alıcı hazır olana dek bekler)
}()
 
deger := <-ch  // Al (gönderici hazır olana dek bekler)
fmt.Println(deger)
 
// Buffered channel — asenkron, kapasiteye kadar beklemez
bufferedCh := make(chan string, 3)
bufferedCh <- "birinci"
bufferedCh <- "ikinci"
bufferedCh <- "üçüncü"
// bufferedCh <- "dördüncü" // BLOKLAR — kapasite dolu
 
// Range ile channel'dan okuma
sayilar := make(chan int, 5)
go func() {
    for i := 0; i < 5; i++ {
        sayilar <- i
    }
    close(sayilar) // Kapanmazsa range sonsuza döner!
}()
 
for sayi := range sayilar {
    fmt.Println(sayi)
}

Select — Çoklu Channel

// GO //
func parallelVeriCek(url1, url2 string) (string, string) {
    ch1 := make(chan string, 1)
    ch2 := make(chan string, 1)
 
    go func() { ch1 <- httpGet(url1) }()
    go func() { ch2 <- httpGet(url2) }()
 
    var sonuc1, sonuc2 string
    for i := 0; i < 2; i++ {
        select {
        case s := <-ch1:
            sonuc1 = s
        case s := <-ch2:
            sonuc2 = s
        case <-time.After(5 * time.Second):
            // Timeout
            return sonuc1, sonuc2
        }
    }
    return sonuc1, sonuc2
}
 
// Fan-out / Fan-in deseni
func fanOut(is <-chan int, isciSayisi int) []<-chan int {
    cikislar := make([]<-chan int, isciSayisi)
    for i := 0; i < isciSayisi; i++ {
        cikislar[i] = isci(is)
    }
    return cikislar
}
 
func isci(giris <-chan int) <-chan int {
    cikis := make(chan int)
    go func() {
        defer close(cikis)
        for n := range giris {
            cikis <- n * n // Karesini al
        }
    }()
    return cikis
}

Mutex — Paylaşılan Veri Koruması

// GO //
type GuvenliSayac struct {
    mu    sync.RWMutex
    deger int
}
 
func (s *GuvenliSayac) Arttir() {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.deger++
}
 
func (s *GuvenliSayac) Deger() int {
    s.mu.RLock()
    defer s.mu.RUnlock()
    return s.deger
}
 
// sync.Map — concurrent-safe map
var onbellek sync.Map
 
func veriGetir(anahtar string) (interface{}, bool) {
    return onbellek.Load(anahtar)
}
 
func veriKaydet(anahtar string, deger interface{}) {
    onbellek.Store(anahtar, deger)
}

net/http ile REST API

// GO //
// main.go
package main
 
import (
    "encoding/json"
    "log"
    "net/http"
    "time"
)
 
type Sunucu struct {
    router *http.ServeMux
    db     *VeritabaniIstemcisi
}
 
func YeniSunucu(db *VeritabaniIstemcisi) *Sunucu {
    s := &Sunucu{router: http.NewServeMux(), db: db}
    s.rotaKur()
    return s
}
 
func (s *Sunucu) rotaKur() {
    s.router.HandleFunc("GET /api/makaleler",     s.makaleListele)
    s.router.HandleFunc("GET /api/makaleler/{id}", s.makaleGetir)
    s.router.HandleFunc("POST /api/makaleler",    s.kimlikDogrula(s.makaleOlustur))
}
 
// JSON yardımcı fonksiyonlar
func jsonCevap(w http.ResponseWriter, durum int, veri any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(durum)
    json.NewEncoder(w).Encode(veri)
}
 
func jsonHata(w http.ResponseWriter, durum int, mesaj string) {
    jsonCevap(w, durum, map[string]string{"hata": mesaj})
}
 
// Handler
func (s *Sunucu) makaleListele(w http.ResponseWriter, r *http.Request) {
    makaleler, err := s.db.MakaleListele(r.Context())
    if err != nil {
        jsonHata(w, http.StatusInternalServerError, "Sunucu hatası")
        return
    }
    jsonCevap(w, http.StatusOK, makaleler)
}
 
func (s *Sunucu) makaleGetir(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")  // Go 1.22+
    makale, err := s.db.MakaleGetir(r.Context(), id)
    if err != nil {
        jsonHata(w, http.StatusNotFound, "Makale bulunamadı")
        return
    }
    jsonCevap(w, http.StatusOK, makale)
}
 
// Middleware — kimlik doğrulama
func (s *Sunucu) kimlikDogrula(sonraki http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            jsonHata(w, http.StatusUnauthorized, "Token gerekli")
            return
        }
        sonraki(w, r)
    }
}
 
func main() {
    db := VeritabaniBaglan()
    sunucu := YeniSunucu(db)
 
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      sunucu.router,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
 
    log.Println("Sunucu :8080 portunda başlatıldı")
    log.Fatal(srv.ListenAndServe())
}

Context ile İptal Yönetimi

// GO //
func (s *Sunucu) agirIslem(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context() // HTTP isteği iptal edilirse context iptal olur
 
    sonuc, err := s.veritabaniSorgu(ctx)
    if err != nil {
        select {
        case <-ctx.Done():
            // İstemci bağlantıyı kesti
            return
        default:
            jsonHata(w, 500, "Veritabanı hatası")
        }
        return
    }
 
    jsonCevap(w, 200, sonuc)
}
 
// Timeout ile context
func veritabaniSorgu(parent context.Context) ([]Makale, error) {
    ctx, iptal := context.WithTimeout(parent, 5*time.Second)
    defer iptal()
 
    return db.QueryContext(ctx, "SELECT * FROM makaleler WHERE yayinlandi = true")
}

Hata Yönetimi

// GO //
// Go'da hata, dönüş değeridir — exception yoktur
type BulunamadiHatasi struct {
    Kaynak string
    ID     string
}
 
func (h BulunamadiHatasi) Error() string {
    return fmt.Sprintf("%s bulunamadı: %s", h.Kaynak, h.ID)
}
 
func makaleGetir(id string) (*Makale, error) {
    makale, err := db.FindByID(id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, BulunamadiHatasi{Kaynak: "Makale", ID: id}
        }
        return nil, fmt.Errorf("makale getirme hatası: %w", err) // Wrap
    }
    return makale, nil
}
 
// Çağıran taraf
makale, err := makaleGetir("abc-123")
if err != nil {
    var bulunamadi BulunamadiHatasi
    if errors.As(err, &bulunamadi) {
        jsonHata(w, 404, bulunamadi.Error())
        return
    }
    jsonHata(w, 500, "Sunucu hatası")
    return
}

Sonuç

Go'nun goroutine ve channel modeli, geleneksel thread'lere kıyasla çok daha hafif ve verimli concurrent programlama sağlar. net/http ile minimal ve hızlı REST API'ler, context.Context ile iptal yönetimi ve Go'nun hata-dönüş-değeri yaklaşımı birlikte güvenilir sunucu uygulamaları oluşturmanıza olanak tanır. Bir sonraki derste Go ile PostgreSQL entegrasyonunu ve sqlc kullanımını inceleyeceğiz.