Modern web uygulamaları, kullanıcıların dosya yüklemesine sıkça ihtiyaç duyar. Fotoğraflar, videolar, belgeler veya diğer medya türleri olsun, bu dosyaların güvenli, ölçeklenebilir ve erişilebilir bir şekilde depolanması kritik öneme sahiptir. Geleneksel dosya yükleme yöntemleri karmaşık olabilirken, Go dilinin performansı ve GraphQL’in esnekliği bir araya geldiğinde, bu süreç oldukça basitleşebilir. Bu makale, Go ve GraphQL kullanarak dosyaları S3 uyumlu Object Storage (Spaces gibi) hizmetlerine yüklemek için güçlü bir API nasıl oluşturulacağını adım adım açıklayacaktır.
GraphQL Nedir ve Dosya Yüklemede Neden Tercih Edilmeli?
GraphQL, API’ler için bir sorgu dili ve çalışma zamanıdır. Facebook tarafından geliştirilen ve açık kaynak hale getirilen GraphQL, istemcilerin tam olarak ihtiyaç duydukları veriyi tek bir istekte almalarını sağlar. RESTful API’lerin aksine, GraphQL’de istemci, hangi veriye ihtiyacı olduğunu belirten bir sorgu gönderir ve sunucu yalnızca o veriyi döndürür. Bu, “aşırı veri çekme” (over-fetching) veya “eksik veri çekme” (under-fetching) sorunlarını ortadan kaldırır ve ağ trafiğini optimize eder. Peki, dosya yükleme gibi ikili (binary) veri işlemlerinde GraphQL neden tercih edilmeli?
Geleneksel REST API’lerde dosya yükleme genellikle ayrı bir endpoint (örn. POST /upload) üzerinden multipart/form-data formatında gerçekleştirilir. GraphQL de benzer bir mekanizmayı destekler ancak bunu kendi şema tanımı içine entegre eder. GraphQL’in en büyük avantajlarından biri, API’nizin tüm yeteneklerini tek bir şema altında tanımlayabilmenizdir. Dosya yükleme, diğer tüm veri işlemleri (sorgular, mutasyonlar) gibi bir mutasyon olarak tanımlanır ve bu, API’nizin tutarlılığını artırır. Ayrıca, GraphQL istemcileri, dosya yükleme mutasyonuna ek olarak, yüklenen dosyanın metadata’sını (URL, boyut, tür gibi) hemen geri alabilirler. Bu, istemci tarafında ek bir istek yapma ihtiyacını ortadan kaldırır ve geliştirme sürecini hızlandırır. Örneğin, bir kullanıcı profil fotoğrafını yüklediğinde, sunucu fotoğrafı depolayıp, hemen ardından güncellenmiş profil bilgilerini ve fotoğrafın yeni URL’sini tek bir GraphQL yanıtında döndürebilir. Bu entegrasyon, kullanıcı deneyimini iyileştirirken, geliştiricilerin de daha az kod yazmasını sağlar. Özellikle mikroservis mimarilerinde veya karmaşık veri modellerine sahip uygulamalarda, GraphQL’in sunduğu bu esneklik, dosya yükleme işlemlerini yönetmeyi çok daha basit ve öngörülebilir hale getirir. Bu yaklaşım, API’nizin zamanla evrim geçirmesini de kolaylaştırır, zira yeni dosya türleri veya yükleme gereksinimleri ortaya çıktığında, şema üzerinde yapılacak küçük değişikliklerle mevcut yapıya kolayca adapte olunabilir.
Go Neden Bu Mimari İçin İdeal Bir Seçimdir?
Go (Golang), Google tarafından geliştirilen, derlenmiş, statik olarak yazılmış bir programlama dilidir. Performansı, eşzamanlılık yetenekleri ve basit sözdizimi sayesinde modern, yüksek performanslı backend servisleri geliştirmek için popüler bir tercihtir. Dosya yükleme gibi I/O yoğun işlemler için Go’nun sunduğu avantajlar saymakla bitmez:
- Eşzamanlılık (Concurrency): Go’nun goroutine’leri ve kanalları, birden fazla dosya yükleme isteğini aynı anda ve verimli bir şekilde işlemenize olanak tanır. Her bir yükleme isteği için hafif bir goroutine başlatılabilir, bu da sunucunun diğer işlemleri engellemeden çalışmaya devam etmesini sağlar. Bu, özellikle yüksek trafikli uygulamalarda yanıt sürelerini önemli ölçüde iyileştirir.
- Performans: Go, derlenmiş bir dil olduğu için çalışma zamanı performansı oldukça yüksektir. Düşük bellek kullanımı ve hızlı işlem yeteneği, büyük dosyaların işlenmesi ve ağ üzerinden aktarılması sırasında kritik bir avantaj sağlar. Bu, sunucu kaynaklarının daha verimli kullanılmasına ve maliyetlerin düşürülmesine yardımcı olur.
- Basitlik ve Okunabilirlik: Go’nun temiz ve minimalist sözdizimi, kodun yazılmasını ve bakımını kolaylaştırır. Bu, özellikle büyük ekiplerde veya uzun vadeli projelerde geliştirme hızını artırır ve hataları azaltır.
- Kapsamlı Standart Kütüphane: Go, ağ işlemleri, dosya sistemi etkileşimleri ve kriptografi gibi birçok yaygın görevi destekleyen zengin bir standart kütüphaneye sahiptir. Bu, harici bağımlılıklara olan ihtiyacı azaltır ve geliştirme sürecini basitleştirir.
- Güçlü Ekosistem: GraphQL için
99designs/gqlgenve S3 uyumlu depolama hizmetleriyle entegrasyon için AWS SDK for Go gibi olgun ve iyi desteklenen kütüphaneler mevcuttur. Bu kütüphaneler, geliştiricilerin karmaşık işlevleri hızlı bir şekilde uygulamasına olanak tanır.
Bu özellikler, Go’yu dosya yükleme ve genel API geliştirme için sağlam ve güvenilir bir seçim haline getirir. Go’nun derlenmiş yapısı sayesinde, uygulamanız tek bir ikili dosya olarak dağıtılabilir, bu da dağıtım süreçlerini basitleştirir. Ayrıca, statik tipleme, derleme zamanında birçok hatanın yakalanmasına yardımcı olur, bu da çalışma zamanı hatalarını azaltır ve uygulamanın kararlılığını artırır. Sonuç olarak, Go ile geliştirilen bir GraphQL API, hem performans hem de sürdürülebilirlik açısından beklentileri karşılayacaktır.
Object Storage (Spaces) Nedir ve Nasıl Çalışır?
Object Storage (Nesne Depolama), verileri nesneler olarak depolayan, ölçeklenebilir ve dayanıklı bir depolama mimarisidir. Geleneksel dosya sistemlerinin (örneğin, sunucudaki bir dizin) veya blok depolamanın (veritabanı diskleri) aksine, nesne depolama, verileri hiyerarşik bir yapı yerine düz (flat) bir adresleme şemasıyla yönetir. Her nesne, verinin kendisi, benzersiz bir tanımlayıcı (anahtar) ve isteğe bağlı metadata (veri hakkında bilgi) içerir. DigitalOcean Spaces, Amazon S3, Google Cloud Storage ve Azure Blob Storage gibi hizmetler, bu teknolojinin popüler örnekleridir.
Spaces gibi S3 uyumlu bir Object Storage hizmeti, özellikle web uygulamalarında dosya yükleme için idealdir çünkü:
- Ölçeklenebilirlik: Milyonlarca hatta milyarlarca nesneyi depolayabilir ve depolama alanınızı gerektiğinde otomatik olarak genişletebilirsiniz. Bu, uygulamanızın büyümesiyle birlikte depolama altyapınızı endişelenmeden ölçekleyebileceğiniz anlamına gelir.
- Dayanıklılık ve Güvenilirlik: Nesneler genellikle birden fazla fiziksel konumda çoğaltılır, bu da donanım arızalarına karşı yüksek dayanıklılık sağlar. Verilerinizin kaybolma riski oldukça düşüktür.
- Maliyet Etkinliği: Kullanım başına ödeme modeli sayesinde, yalnızca depoladığınız veri miktarı ve yaptığınız istekler için ödeme yaparsınız. Bu, özellikle büyük miktarda veri depolayan ancak nadiren erişilen durumlar için çok ekonomiktir.
- Erişilebilirlik: Nesneler HTTP/HTTPS üzerinden URL’ler aracılığıyla erişilebilir. Bu, doğrudan web tarayıcılarından veya diğer uygulamalardan dosyalara kolayca erişilmesini sağlar. Ayrıca, çoğu Object Storage hizmeti, CDN (İçerik Dağıtım Ağı) entegrasyonunu kolaylaştırarak içeriğin dünya çapında hızlı bir şekilde dağıtılmasına yardımcı olur.
- Güvenlik: Detaylı erişim kontrol politikaları (ACL’ler, bucket politikaları, IAM rolleri) ile hangi kullanıcıların veya uygulamaların hangi nesnelere erişebileceğini hassas bir şekilde yönetebilirsiniz.
Bir dosya Spaces’a yüklendiğinde, aslında bir “bucket” (kova) adı verilen bir konteynere bir “nesne” olarak kaydedilir. Her nesnenin benzersiz bir anahtarı (genellikle dosya adı) ve isteğe bağlı olarak ek metadata’sı bulunur. Örneğin, bir resim dosyası yüklendiğinde, anahtar “profil-resmi-123.jpg” olabilir ve metadata “Content-Type: image/jpeg” içerebilir. Bu yapı, Go ile GraphQL API’mizin dosyaları depolama, yönetme ve erişme şeklini doğrudan etkileyecektir. Dolayısıyla, bu hizmetleri anlamak, API’mizi doğru bir şekilde tasarlamak için temel bir adımdır. Nesne depolamanın bu esnek yapısı, sadece dosya yükleme için değil, aynı zamanda statik web sitelerinin barındırılması, yedekleme ve arşivleme gibi birçok farklı senaryo için de tercih edilmesini sağlar. Bu sayede, uygulamanızın depolama gereksinimlerini geleceğe yönelik olarak da karşılayabilirsiniz.
Go Projesini Başlatma ve Temel Bağımlılıkları Kurma
Go ile GraphQL API’mizi oluşturmaya başlamak için öncelikle yeni bir Go modülü başlatmamız ve gerekli bağımlılıkları eklememiz gerekiyor. Bu bölüm, projenin temel iskeletini oluşturma adımlarını içermektedir.
Go Modülünü Başlatma ve Bağımlılıkları Ekleme
İlk olarak, projeniz için bir dizin oluşturun ve bu dizine gidin:
mkdir go-graphql-upload
cd go-graphql-upload
Şimdi, Go modülünü başlatın. Bu komut, projenizin bağımlılıklarını yönetecek olan go.mod dosyasını oluşturacaktır:
go mod init github.com/kullaniciadi/go-graphql-upload
github.com/kullaniciadi/go-graphql-upload yerine kendi GitHub kullanıcı adınızı veya tercih ettiğiniz modül yolunu kullanabilirsiniz.
Ardından, GraphQL sunucusu için gqlgen kütüphanesini ve S3 uyumlu Object Storage (Spaces) ile etkileşim kurmak için AWS SDK for Go’yu (özellikle S3 ve S3 Manager paketlerini) eklememiz gerekiyor. gqlgen, GraphQL şemanızdan Go kodunu otomatik olarak oluşturan güçlü bir araçtır.
go get github.com/99designs/gqlgen
go get github.com/aws/aws-sdk-go/aws
go get github.com/aws/aws-sdk-go/aws/session
go get github.com/aws/aws-sdk-go/service/s3
go get github.com/aws/aws-sdk-go/service/s3/s3manager
go get github.com/joho/godotenv # Ortam değişkenlerini yönetmek için (opsiyonel ama önerilir)
Bu komutlar, belirtilen kütüphaneleri projenize indirir ve go.mod dosyanıza ekler. godotenv, yerel geliştirme ortamınızda API anahtarları gibi hassas bilgileri .env dosyasından yüklemek için kullanışlıdır.
gqlgen ile Temel Yapılandırma
gqlgen, projenizin GraphQL şemasını temel alarak birçok boilerplate kodunu sizin için oluşturur. Bu, geliştirme sürecini hızlandırır ve hataları azaltır. Temel yapılandırmayı oluşturmak için şu komutu çalıştırın:
go run github.com/99designs/gqlgen init
Bu komut, projenizin kök dizininde aşağıdaki dosyaları oluşturacaktır:
gqlgen.yml:gqlgenyapılandırma dosyası. Giriş şemanızı, model bağlamalarını ve diğer ayarları tanımlar.graph/schema.graphqls: GraphQL şemanızın tanımlanacağı dosya. Başlangıçta örnek sorgu ve mutasyonları içerecektir.graph/resolver.go: GraphQL resolver fonksiyonlarınızın yer alacağı dosya. Burası iş mantığınızı yazacağınız yerdir.graph/generated.go:gqlgentarafından şemanızdan otomatik olarak oluşturulan kod. Bu dosyayı elle düzenlememelisiniz.server.go: GraphQL sunucunuzu başlatan ana dosya.
Bu adımlarla, GraphQL API’nizi geliştirmeye başlamak için sağlam bir temel oluşturmuş olursunuz. gqlgen‘in otomatik kod oluşturma yeteneği, özellikle büyük ve karmaşık GraphQL şemalarıyla çalışırken zaman kazandırır. Artık schema.graphqls dosyamızı dosya yükleme için gerekli mutasyonları içerecek şekilde düzenleyebiliriz.
GraphQL Şemasını Tanımlama: Dosya Yükleme Mutasyonu Nasıl Oluşturulur?
GraphQL şeması, API’nizin sunduğu tüm veri türlerini, sorguları (query) ve mutasyonları (mutation) tanımlayan merkezi bir sözleşmedir. Dosya yükleme işlevini API’mize eklemek için özel bir mutasyon tanımlamamız ve Upload scalar türünü kullanmamız gerekecek. Bu bölüm, schema.graphqls dosyasını nasıl düzenleyeceğimizi adım adım gösterecektir.
schema.graphqls Dosyasının Düzenlenmesi
gqlgen init komutuyla oluşturulan graph/schema.graphqls dosyasını açın. İçeriği muhtemelen şöyle görünecektir:
# GraphQL schema example
#
# https://gqlgen.com/getting-started/
type Todo {
id: ID!
text: String!
done: Boolean!
}
type Query {
todos: [Todo!]!
}
type Mutation {
createTodo(text: String!): Todo!
}
Bu örnek şemayı dosya yükleme mutasyonumuzu içerecek şekilde değiştirmemiz gerekiyor. Öncelikle, GraphQL’in dosya yükleme için özel olarak tasarlanmış Upload scalar türünü tanımlamalıyız. Bu scalar, multipart/form-data isteğindeki dosya bölümünü temsil eder. Ardından, bir dosya yükleme mutasyonu ve yüklenen dosya hakkında bilgi döndürecek bir File türü oluşturacağız.
graph/schema.graphqls dosyanızı aşağıdaki gibi güncelleyin:
# Dosya yüklemeleri için özel scalar türü
scalar Upload
# Yüklenen dosya hakkında bilgi döndürecek tip
type File {
id: ID!
filename: String!
mimetype: String!
encoding: String!
url: String! # Dosyanın depolandığı URL
size: Int! # Dosya boyutu (byte cinsinden)
}
# Sorgular (Queries) - Şimdilik boş bırakabiliriz veya ekleyebiliriz
type Query {
hello: String!
}
# Mutasyonlar (Mutations) - Dosya yükleme işlevimizi buraya ekleyeceğiz
type Mutation {
# Tek bir dosya yüklemek için mutasyon
uploadFile(file: Upload!): File!
# Birden fazla dosya yüklemek için (isteğe bağlı)
uploadMultipleFiles(files: [Upload!]!): [File!]!
}
Bu şema tanımında:
scalar Upload: GraphQL’e dosya yükleme yeteneği kazandırır.gqlgenbu scalar’ı otomatik olarak işleyecektir.type File: Yüklenen bir dosya hakkında istemciye geri döndürülecek bilgileri tanımlar.id,filename,mimetype,encoding,urlvesizegibi alanlar içerir.urlalanı, dosyanın Object Storage’da depolandığı genel erişilebilir URL’yi tutacaktır.type Mutation: API’mizin veri üzerinde değişiklik yapma yeteneğini tanımlar.uploadFile(file: Upload!): File!: Tek bir dosya yüklemek için bir mutasyon.fileargümanı zorunlu birUploadtüründedir ve mutasyon başarılı olursa birFilenesnesi döndürür.uploadMultipleFiles(files: [Upload!]!): [File!]!: İsteğe bağlı olarak, birden fazla dosyayı aynı anda yüklemek için bir mutasyon.filesargümanı birUploaddizisi alır ve birFiledizisi döndürür.
Şema Değişikliklerini Uygulama
schema.graphqls dosyasını güncelledikten sonra, gqlgen‘in bu değişiklikleri Go koduna yansıtması için yeniden çalıştırmamız gerekir:
go run github.com/99designs/gqlgen generate
Bu komut, graph/generated.go dosyasını ve graph/models_gen.go dosyasını güncelleyecektir. models_gen.go dosyası, şemada tanımladığımız File türü için Go yapılarını (struct) içerecektir. Örneğin, File türü için şöyle bir yapı oluşturulacaktır:
// models_gen.go içinde oluşacak yapıya benzer
type File struct {
ID string json:"id"
Filename string json:"filename"
Mimetype string json:"mimetype"
Encoding string json:"encoding"
URL string json:"url"
Size int json:"size"
}
Artık GraphQL şemamız dosya yüklemeyi destekleyecek şekilde hazır. Bir sonraki adım, bu mutasyon için Go tarafında resolver fonksiyonlarını ve dosya işleme mantığını geliştirmektir. Bu, sunucumuzun gelen dosyaları nasıl alacağını, işleyeceğini ve Object Storage’a nasıl göndereceğini belirleyeceğimiz yerdir.
Go Sunucusu ve Resolver Fonksiyonlarını Geliştirme: Dosya İşleme Mantığı
Şemamızı tanımladıktan ve gqlgen ile gerekli Go kodlarını oluşturduktan sonra, sıra dosya yükleme mutasyonumuz için gerçek iş mantığını yazmaya geldi. Bu, resolver.go dosyasında gerçekleşecek ve gelen dosyayı işleyip Object Storage’a gönderme adımlarını içerecektir. Ayrıca, GraphQL sunucumuzu başlatan server.go dosyasında bazı ayarlamalar yapacağız.
server.go Dosyasının Yapılandırılması
gqlgen init komutuyla oluşturulan server.go dosyası, temel bir GraphQL sunucusu başlatır. Dosya yükleme desteği için, gqlgen‘in Upload scalar’ını doğru şekilde işlemesi için bazı middleware’lara ihtiyacımız olabilir. Ancak gqlgen‘in kendisi, multipart/form-data isteklerini ve Upload scalar’ını yerleşik olarak destekler, bu nedenle genellikle ek bir middleware’a gerek kalmaz.
server.go dosyanızın genel yapısı şöyle olmalıdır:
package main
import (
"log"
"net/http"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/kullaniciadi/go-graphql-upload/graph"
"github.com/kullaniciadi/go-graphql-upload/graph/generated"
"github.com/joho/godotenv" // Ortam değişkenleri için
)
const defaultPort = "8080"
func main() {
// .env dosyasından ortam değişkenlerini yükle
err := godotenv.Load()
if err != nil {
log.Println("Error loading .env file, assuming environment variables are set")
}
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
http.Handle("/query", srv)
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
Burada önemli olan, generated.Config{Resolvers: &graph.Resolver{}} kısmıdır. Bu, graph/resolver.go dosyasında tanımlayacağımız resolver’larımızın sunucuya bağlanmasını sağlar.
resolver.go Dosyasında Dosya Yükleme Mantığı
graph/resolver.go dosyası, GraphQL şemamızdaki her alan için gerçek iş mantığını içerir. gqlgen generate komutunu çalıştırdıktan sonra, bu dosyada UploadFile ve UploadMultipleFiles mutasyonları için placeholder fonksiyonlar göreceksiniz. Şimdi bu fonksiyonları, gelen dosyayı işleyecek ve Object Storage’a yükleyecek şekilde dolduralım.
Öncelikle, Resolver yapımıza Object Storage istemcisini eklememiz gerekiyor. Bu, S3 uyumlu bir servise (DigitalOcean Spaces gibi) bağlanmak için kullanılacak.
package graph
import (
"context"
"fmt"
"io"
"log"
"mime"
"path/filepath"
"os"
"github.com/99designs/gqlgen/graphql"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/google/uuid" // Benzersiz ID'ler için
"github.com/kullaniciadi/go-graphql-upload/graph/generated"
"github.com/kullaniciadi/go-graphql-upload/graph/model"
)
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
Uploader *s3manager.Uploader
BucketName string
Region string
Endpoint string
}
// Resolver yapımızın başlangıçta nasıl oluşturulacağını gösteren örnek.
// main.go'da çağrılacak.
func NewResolver() *Resolver {
// AWS SDK oturumunu başlat
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(os.Getenv("AWS_REGION")),
Endpoint: aws.String(os.Getenv("AWS_ENDPOINT")), // DigitalOcean Spaces için gerekli
}))
// S3 Uploader oluştur
uploader := s3manager.NewUploader(sess)
return &Resolver{
Uploader: uploader,
BucketName: os.Getenv("S3_BUCKET_NAME"),
Region: os.Getenv("AWS_REGION"),
Endpoint: os.Getenv("AWS_ENDPOINT"),
}
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
func (r *queryResolver) Hello(ctx context.Context) (string, error) {
return "Merhaba, GraphQL Dosya Yükleme API'sine hoş geldiniz!", nil
}
func (r *mutationResolver) UploadFile(ctx context.Context, file graphql.Upload) (*model.File, error) {
log.Printf("Yükleme isteği alındı: %s, %s, %d bytes", file.Filename, file.ContentType, file.Size)
// Benzersiz bir dosya adı oluştur
fileExt := filepath.Ext(file.Filename)
uniqueFilename := fmt.Sprintf("%s%s", uuid.New().String(), fileExt)
objectKey := "uploads/" + uniqueFilename // Spaces içinde bir klasöre kaydet
// Dosyayı Spaces'a yükle
uploadInput := &s3manager.UploadInput{
Bucket: aws.String(r.BucketName),
Key: aws.String(objectKey),
Body: file.File, // graphql.Upload'ın io.Reader'ı
ContentType: aws.String(file.ContentType),
}
result, err := r.Uploader.Upload(uploadInput)
if err != nil {
log.Printf("Dosya yüklenirken hata oluştu: %v", err)
return nil, fmt.Errorf("dosya yüklenemedi: %w", err)
}
log.Printf("Dosya başarıyla yüklendi: %s", *result.Location)
// Yüklenen dosya bilgisini döndür
return &model.File{
ID: uuid.New().String(), // Veya Spaces'tan gelen bir ID kullanabilirsiniz
Filename: file.Filename,
Mimetype: file.ContentType,
Encoding: "binary", // GraphQL'in 'Upload' scalar'ı genellikle binary'dir
URL: *result.Location,
Size: int(file.Size),
}, nil
}
func (r *mutationResolver) UploadMultipleFiles(ctx context.Context, files []*graphql.Upload) ([]*model.File, error) {
var uploadedFiles []*model.File
for _, file := range files {
uploadedFile, err := r.UploadFile(ctx, *file) // Tek dosya yükleme fonksiyonunu tekrar kullan
if err != nil {
// Hata durumunda diğer dosyaların yüklemesini durdurmak isteyebilirsiniz
// veya hataları toplayıp tümünü döndürebilirsiniz.
log.Printf("Birden fazla dosya yüklenirken hata oluştu: %v", err)
return nil, fmt.Errorf("dosyalardan biri yüklenemedi: %w", err)
}
uploadedFiles = append(uploadedFiles, uploadedFile)
}
return uploadedFiles, nil
}
main.go dosyasını da NewResolver() fonksiyonunu çağıracak şekilde güncellemeniz gerekiyor:
// main.go dosyasında değişiklik
// ...
func main() {
// ... (godotenv.Load ve port ayarları) ...
// Resolver'ı başlat ve S3 Uploader'ı enjekte et
resolver := graph.NewResolver()
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))
// ... (http.Handle ve log ayarları) ...
}
Bu kodda:
ResolveryapısınaUploader,BucketName,RegionveEndpointeklendi. Bu, resolver fonksiyonlarının Object Storage ile etkileşime geçmesini sağlar.NewResolver()fonksiyonu, AWS SDK oturumunu ves3manager.Uploader‘ı başlatır. Bu fonksiyon,main.goiçinde çağrılarak resolver’a bağımlılık enjeksiyonu sağlar. Ortam değişkenlerinden (AWS_REGION,AWS_ENDPOINT,S3_BUCKET_NAME) gerekli bilgileri alır.UploadFilemutasyonunda:graphql.Uploadtüründen gelen dosya (file.File),io.Readerarayüzünü uygular, bu da onu doğrudan S3 uploader’ına beslememizi sağlar.uuid.New().String()ile benzersiz bir dosya adı oluşturulur ve dosyanın orijinal uzantısı korunur. Bu, adlandırma çakışmalarını önler.s3manager.Uploaderkullanılarak dosya Object Storage’a yüklenir.UploadInputnesnesine bucket adı, anahtar (dosya yolu), dosya içeriği ve içerik türü bilgileri eklenir.- Yükleme başarılı olursa, Object Storage’dan dönen konum (URL) ve diğer dosya bilgileri ile bir
model.Filenesnesi oluşturulur ve istemciye döndürülür.
UploadMultipleFilesmutasyonu, tek dosya yükleme fonksiyonunu her bir dosya için tekrar kullanarak birden fazla dosyanın yüklenmesini sağlar.
Bu sayede, GraphQL API’miz artık gelen dosya yükleme isteklerini işleyebilir, dosyaları benzersiz bir şekilde adlandırabilir ve güvenli bir şekilde Object Storage’a depolayabilir. Sonraki adım, bu entegrasyonu detaylandırmak ve Object Storage servisleriyle nasıl daha verimli çalışabileceğimizi görmektir.
Spaces (S3 Uyumlu Object Storage) ile Entegrasyon: Dosyaları Yükleme
Önceki bölümde, Go resolver’ımızı kullanarak dosya yükleme mantığını taslağını oluşturduk. Şimdi bu mantığı S3 uyumlu bir Object Storage hizmeti olan DigitalOcean Spaces ile tam olarak entegre edelim. AWS SDK for Go, S3 API’si ile uyumlu tüm hizmetlerle çalışabildiği için, Spaces ile etkileşim kurmak oldukça kolaydır. Temel olarak, SDK’yı Spaces’ın endpoint’ini gösterecek şekilde yapılandırmamız yeterlidir.
AWS SDK’sını Spaces İçin Yapılandırma
AWS SDK’sını DigitalOcean Spaces gibi S3 uyumlu bir hizmetle kullanmak için, oturum yapılandırmasında Endpoint parametresini belirtmeniz gerekir. Ayrıca, Spaces erişim anahtarlarınızı (Access Key ve Secret Key) ortam değişkenleri aracılığıyla sağlamanız önemlidir.
.env dosyanızda aşağıdaki değişkenleri tanımladığınızdan emin olun:
# .env
AWS_REGION=fra1 # DigitalOcean Spaces bölgeniz (örn: nyc3, fra1)
AWS_ENDPOINT=https://fra1.digitaloceanspaces.com # Spaces endpoint URL'si
S3_BUCKET_NAME=your-space-name # Oluşturduğunuz Spaces'ın adı
AWS_ACCESS_KEY_ID=YOUR_DO_SPACES_ACCESS_KEY # DigitalOcean Spaces Erişim Anahtarınız
AWS_SECRET_ACCESS_KEY=YOUR_DO_SPACES_SECRET_KEY # DigitalOcean Spaces Gizli Anahtarınız
graph/resolver.go dosyasındaki NewResolver() fonksiyonu, bu ortam değişkenlerini kullanarak bir AWS SDK oturumu başlatacaktır:
// graph/resolver.go içerisindeki NewResolver fonksiyonu
func NewResolver() *Resolver {
// AWS SDK oturumunu başlat
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(os.Getenv("AWS_REGION")),
Endpoint: aws.String(os.Getenv("AWS_ENDPOINT")), // Spaces için önemli!
// Kimlik bilgileri varsayılan olarak ortam değişkenlerinden (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) alınır.
}))
// S3 Uploader oluştur
uploader := s3manager.NewUploader(sess)
return &Resolver{
Uploader: uploader,
BucketName: os.Getenv("S3_BUCKET_NAME"),
Region: os.Getenv("AWS_REGION"),
Endpoint: os.Getenv("AWS_ENDPOINT"),
}
}
Bu yapılandırma ile uploader nesnesi artık DigitalOcean Spaces’a dosya yüklemek için hazır hale gelmiştir. Kimlik bilgileri (Access Key ID ve Secret Access Key) varsayılan olarak ortam değişkenlerinden (AWS_ACCESS_KEY_ID ve AWS_SECRET_ACCESS_KEY) alınır, bu da kod içinde hassas bilgileri saklamaktan kaçınmamızı sağlar.
Dosyaları Spaces’a Yükleme Mekanizması
UploadFile resolver fonksiyonumuzun kalbi, s3manager.Uploader‘ın Upload metodunu kullanmaktır. Bu metod, büyük dosyaları otomatik olarak parçalara ayırıp çok parçalı yükleme (multipart upload) yaparak performansı ve güvenilirliği artırır.
// graph/resolver.go içerisindeki UploadFile fonksiyonundan ilgili kısım
func (r *mutationResolver) UploadFile(ctx context.Context, file graphql.Upload) (*model.File, error) {
// ... (Benzersiz dosya adı oluşturma) ...
objectKey := "uploads/" + uniqueFilename // Spaces içinde bir 'uploads' klasörüne kaydetmek için
// Dosyayı Spaces'a yükle
uploadInput := &s3manager.UploadInput{
Bucket: aws.String(r.BucketName),
Key: aws.String(objectKey),
Body: file.File, // graphql.Upload'ın io.Reader'ı
ContentType: aws.String(file.ContentType),
ACL: aws.String("public-read"), // Dosyanın genel erişilebilir olmasını sağlar
}
result, err := r.Uploader.Upload(uploadInput)
if err != nil {
log.Printf("Dosya yüklenirken hata oluştu: %v", err)
return nil, fmt.Errorf("dosya yüklenemedi: %w", err)
}
log.Printf("Dosya başarıyla yüklendi: %s", *result.Location)
// Yüklenen dosya bilgisini döndür
return &model.File{
ID: uuid.New().String(),
Filename: file.Filename,
Mimetype: file.ContentType,
Encoding: "binary",
URL: *result.Location, // Yüklenen dosyanın URL'si
Size: int(file.Size),
}, nil
}
Burada eklediğimiz önemli bir detay, ACL: aws.String("public-read") satırıdır. Bu, yüklenen dosyanın herkes tarafından okunabilir olmasını sağlar. Eğer dosyaların genel erişilebilir olmasını istemiyorsanız, bu satırı kaldırabilir ve erişimi Spaces bucket politikaları veya ön imzalı URL’ler (presigned URLs) aracılığıyla yönetebilirsiniz. result.Location, yüklenen dosyanın tam URL’sini içerir, bu da istemciye dosyanın nerede bulunduğunu bildirmek için mükemmeldir. Bu entegrasyon adımları sayesinde, Go GraphQL API’niz artık DigitalOcean Spaces gibi S3 uyumlu herhangi bir Object Storage hizmetine güvenilir ve verimli bir şekilde dosya yükleyebilir. Bu, uygulamanızın depolama ihtiyaçları için sağlam bir temel oluşturur ve gelecekteki ölçeklenebilirlik gereksinimlerini karşılamanıza yardımcı olur. Artık API’miz, dosyaları alıp bulut depolamaya güvenle aktarabilir bir durumda.
İleri Düzey Konular: Büyük Dosya Yüklemeleri ve Güvenlik İpuçları
Dosya yükleme API’leri oluştururken, büyük dosyaların yönetimi ve güvenlik en önemli konulardan ikisidir. Bu bölümde, bu zorlukların üstesinden gelmek için bazı ileri düzey teknikleri ve en iyi uygulamaları inceleyeceğiz.
Büyük Dosya Yüklemeleri İçin Optimizasyonlar
Küçük resimler veya belgeler için doğrudan yükleme yöntemi yeterli olsa da, gigabaytlarca büyüklükteki dosyaları yüklerken performansı ve güvenilirliği artırmak için farklı stratejiler uygulamak gerekir.
- Ön İmzalı URL’ler (Presigned URLs) ile Doğrudan Yükleme:
Bu, büyük dosyaları yüklemek için en yaygın ve önerilen yöntemlerden biridir. Mantık şöyledir: İstemci, API’nize bir dosya yüklemek istediğini belirtir (genellikle dosya adı ve türü ile). API’niz, Spaces’tan belirli bir süre geçerli olacak, doğrudan o dosyayı yükleyebileceği bir “ön imzalı URL” (PUT isteği için) veya “ön imzalı form alanı” (POST isteği için) ister. API, bu URL’yi istemciye geri döndürür. İstemci daha sonra dosyayı doğrudan Spaces’a, API’nizin üzerinden geçmeden yükler. Bu yaklaşımın faydaları:
- Sunucu Yükünü Azaltma: Dosya içeriği API sunucunuz üzerinden akmadığı için, sunucunuzun CPU, bellek ve ağ bant genişliği üzerindeki yükü önemli ölçüde azalır.
- Daha Hızlı Yüklemeler: İstemci, dosyayı doğrudan Object Storage’a yüklediği için, API sunucunuzun coğrafi konumundan bağımsız olarak daha düşük gecikmeyle daha hızlı yükleme deneyimi yaşayabilir.
- Gelişmiş Güvenlik: API, yalnızca belirli bir dosya için belirli bir süre geçerli olan imzalı bir URL oluşturur. Bu, yetkisiz erişimi sınırlar.
Go ile ön imzalı URL oluşturma, AWS SDK’nın
s3.PresignRequestveyas3.PutObjectRequestmetodları kullanılarak yapılabilir. İstemciye döndürülen URL’yi kullanarak dosya yüklemesi tamamlandıktan sonra, istemci API’nize bir bildirim gönderebilir ve dosyanın Object Storage’a başarıyla yüklendiğini doğrulayabilir. - Çok Parçalı Yükleme (Multipart Upload) ve Chunking:
s3manager.Uploaderzaten bu mekanizmayı dahili olarak kullanır, ancak istemci tarafında da dosyaları parçalara ayırıp paralel olarak yüklemek, özellikle zayıf ağ bağlantılarında veya çok büyük dosyalarda faydalı olabilir. İstemci, dosyayı küçük “parçalara” (chunks) böler ve her bir parçayı ayrı ayrı yükler. API’niz veya doğrudan Object Storage, bu parçaları daha sonra birleştirir. Bu, başarısız olan parçaların yeniden denemesini kolaylaştırır ve yükleme işlemini daha dayanıklı hale getirir.
Güvenlik İpuçları
Dosya yükleme API’lerinde güvenlik her zaman öncelikli olmalıdır. Aşağıdaki ipuçları, uygulamanızın güvenliğini artırmanıza yardımcı olacaktır:
- Yetkilendirme ve Kimlik Doğrulama:
Her dosya yükleme isteğinin yetkili bir kullanıcıdan geldiğinden emin olun. JWT (JSON Web Tokens), OAuth veya API anahtarları gibi mekanizmalar kullanarak kullanıcıların kimliğini doğrulayın ve dosya yükleme mutasyonuna erişimlerini yetkilendirin. Örneğin, sadece oturum açmış kullanıcıların dosya yüklemesine izin verin.
- Dosya Tipi ve Boyutu Doğrulama:
Kullanıcıların yükleyebileceği dosya türlerini (MIME tipleri) ve maksimum dosya boyutunu sınırlayın. Bu, kötü amaçlı dosyaların (örneğin, çalıştırılabilir dosyalar) yüklenmesini önler ve sunucu kaynaklarının aşırı kullanımını engeller. Bu doğrulamayı hem istemci tarafında (kullanıcı deneyimi için) hem de sunucu tarafında (güvenlik için) yapın. Go’da
file.ContentTypevefile.Sizealanlarını kontrol edebilirsiniz. - Benzersiz Dosya Adları ve Yollar:
Yüklenen dosyalar için benzersiz adlar ve güvenli depolama yolları kullanın (örneğin,
uuid.New().String()ile). Bu, dizin geçişi (directory traversal) saldırılarını ve dosya adlandırma çakışmalarını önler. Ayrıca, kullanıcı tarafından sağlanan dosya adlarını doğrudan kullanmaktan kaçının veya bunları güvenli bir şekilde temizleyin. - Erişim Kontrolü (ACLs ve Bucket Politikaları):
Object Storage’daki dosyalarınız için uygun erişim kontrol listeleri (ACL’ler) veya bucket politikaları belirleyin. Varsayılan olarak tüm dosyaların herkese açık olmasını istemiyorsanız,
public-readACL’sini kullanmaktan kaçının. Bunun yerine, sadece belirli kullanıcıların veya uygulamanızın dosyaya erişmesine izin veren daha kısıtlayıcı politikalar uygulayın. - Virüs Taraması ve Zararlı İçerik Kontrolü:
Mümkünse, yüklenen dosyaları virüs ve zararlı yazılım taramalarından geçirin. Bu genellikle ayrı bir hizmet veya mikroservis aracılığıyla arka planda yapılır. Dosya taramadan geçtikten sonra, güvenli olduğu onaylanırsa erişime açılabilir.
Bu ileri düzey konular ve güvenlik önlemleri, Go GraphQL dosya yükleme API’nizin sadece işlevsel değil, aynı zamanda sağlam, ölçeklenebilir ve güvenli olmasını sağlayacaktır. Özellikle büyük ölçekli ve hassas veri içeren uygulamalarda bu yaklaşımlar kritik öneme sahiptir.
Sonuç: Go ve GraphQL ile Güçlü Dosya Yükleme API’leri
Bu makale boyunca, Go programlama dilinin gücünü ve GraphQL’in esnekliğini kullanarak S3 uyumlu Object Storage (DigitalOcean Spaces gibi) hizmetlerine dosya yükleyen sağlam bir API’nin nasıl oluşturulacağını adım adım inceledik. GraphQL’in tek bir endpoint üzerinden esnek veri işlemleri sunması, Go’nun eşzamanlılık ve performans yetenekleri ile birleştiğinde, dosya yükleme gibi I/O yoğun görevler için ideal bir çözüm ortaya koydu. Ayrıca, Object Storage’ın ölçeklenebilirlik, dayanıklılık ve maliyet etkinliği avantajlarını da detaylıca ele aldık.
Projenin başlangıcından itibaren, Go modülünü başlatma, gqlgen ile GraphQL şemasını tanımlama, dosya yükleme mutasyonlarını oluşturma ve bu mutasyonlar için Go resolver fonksiyonlarını geliştirme adımlarını takip ettik. Özellikle, AWS SDK for Go’yu kullanarak dosyaları Spaces’a güvenli ve verimli bir şekilde nasıl yükleyeceğimizi gösterdik. İleri düzey konular bölümünde ise, büyük dosya yüklemeleri için ön imzalı URL’ler gibi optimizasyon tekniklerine ve API’nizin güvenliğini sağlamak için kimlik doğrulama, dosya doğrulama ve erişim kontrolü gibi kritik ipuçlarına değindik. Bu bilgiler, uygulamanızın sadece mevcut ihtiyaçlarını karşılamakla kalmayıp, aynı zamanda gelecekteki büyüme ve güvenlik gereksinimlerine de hazır olmasını sağlayacaktır. Sonuç olarak, Go ve GraphQL kombinasyonu, modern, yüksek performanslı ve sürdürülebilir dosya yükleme çözümleri geliştirmek için mükemmel bir araç setidir.
Sıkça Sorulan Sorular
- GraphQL ile dosya yüklemek neden REST’ten daha iyi bir seçenek olabilir?
- GraphQL, dosya yükleme mutasyonunu diğer tüm API işlemlerinizle tek bir şema altında birleştirmenize olanak tanır. Bu, API tutarlılığını artırır ve istemcilerin yüklenen dosyanın metadata’sını (URL, boyut vb.) aynı istekte almasını sağlar, bu da istemci tarafında ek HTTP isteklerine olan ihtiyacı ortadan kaldırır. Ayrıca, GraphQL’in esnekliği, API’nizin değişen ihtiyaçlara daha kolay adapte olmasını sağlar.
- Hangi S3 uyumlu Object Storage servislerini kullanabilirim?
- Bu makalede DigitalOcean Spaces’ı örnek olarak kullandık, ancak Amazon S3, Google Cloud Storage (S3 uyumlu mod), Azure Blob Storage, Linode Object Storage, Wasabi ve MinIO (kendi sunucunuzda çalıştırılan) gibi birçok S3 uyumlu Object Storage hizmetini kullanabilirsiniz. AWS SDK for Go,
Endpointyapılandırması ile bu hizmetlerin çoğuyla uyumlu çalışır. - Büyük dosyaları yüklerken performansı nasıl artırabilirim?
- Büyük dosyalar için en etkili yöntem, “ön imzalı URL’ler” (Presigned URLs) kullanmaktır. Bu yöntemle istemci, dosyayı doğrudan Object Storage’a yükler ve API sunucunuzun üzerinden geçmez, bu da sunucu yükünü ve gecikmeyi azaltır. Ayrıca, çok parçalı yükleme (multipart upload) ve istemci tarafında dosya parçalama (chunking) teknikleri de büyük dosya yüklemelerinin güvenilirliğini ve hızını artırabilir.
- Yüklenen dosyaların güvenliğini nasıl sağlayabilirim?
- Güvenlik için birden fazla katmanlı bir yaklaşım benimsemek önemlidir:
- Tüm dosya yükleme isteklerinde kullanıcı kimlik doğrulaması ve yetkilendirmesi yapın.
- Yüklenen dosya türlerini (MIME tipleri) ve boyutlarını hem istemci hem de sunucu tarafında doğrulayın.
- Dosya adları için benzersiz tanımlayıcılar kullanın (UUID gibi) ve kullanıcı tarafından sağlanan adları doğrudan kullanmaktan kaçının.
- Object Storage’da uygun erişim kontrol listeleri (ACL’ler) veya bucket politikaları uygulayın.
- Mümkünse, yüklenen dosyaları virüs ve kötü amaçlı yazılım taramasından geçirin.
gqlgenyerine başka bir GraphQL kütüphanesi kullanabilir miyim?- Evet, Go için
gqlgendışındagraphql-go/graphqlgibi başka GraphQL kütüphaneleri de mevcuttur. Ancakgqlgen, şema odaklı yaklaşımı ve otomatik kod oluşturma yetenekleri sayesinde genellikle daha hızlı geliştirme ve daha az boilerplate kodu sunar. Seçiminiz, projenizin özel gereksinimlerine ve ekibinizin tercihlerine bağlı olabilir.