Ana içeriğe geç
Ahmet Faruk UzunkayaYaratıcı geliştirici
← Projelere dön
Arka uçTamamlandıGüncellendi · 2 Ekim 2026

E-ticaret API'si (.NET 9)

JWT, Redis önbellek, MassTransit outbox ve IP başına hız sınırıyla bir .NET 9 e-ticaret API'si.

.NET 9 ve ASP.NET Core ile yazdığım bir e-ticaret API'si. Katalog, sepet, sipariş, adres, ödeme yöntemi ve yorum için uç noktaları var. Kimlik doğrulama JWT ile, katalog önbelleği Redis'te; MassTransit ile transactional outbox kullanıyor, siparişler Idempotency-Key destekliyor. Log için Serilog, metrikler için Prometheus var. Aynı sözleşmeyi backend-spring de uyguluyor.

.NET 9PostgreSQLRedisMassTransitSerilogDocker
Durum
Tamamlandı
Kategori
Arka uç
Güncelleme
2 Ekim 2026
Yığın
.NET 9PostgreSQLRedisMassTransitSerilogDocker
E-ticaret API'si (.NET 9)

Bu, .NET 9 ve ASP.NET Core ile yazdığım bir e-ticaret REST API'si. Katalog, sepet, favoriler, sipariş, adres, ödeme yöntemi, yorum, bildirim, güvenlik ayarları ve destek talepleri için uç noktaları var. Geliştirirken hiçbir migration adımıyla uğraşmadan SQLite ile çalışıyor; Docker'da ise PostgreSQL, Redis ve RabbitMQ ile birlikte ayağa kalkıyor.

Projenin bence en ilginç yanı, aynı HTTP sözleşmesini ayrı bir depodaki backend-spring'in de uygulaması. Vitrin uygulamasını yalnızca NEXT_PUBLIC_API_URL değerini değiştirerek ikisinden birine bağlayabiliyorum. Sözleşme docs/API_CONTRACT.md dosyasında yazılı. İki backend'e aynı istekleri gönderip çıktıları karşılaştıran küçük bir betikle (contract-smoke.mjs) de aralarındaki her farkı listeleyebiliyorum.

Katmanlar ve etrafındaki servisler

Kodu üç katmana ayırdım: Domain, Application ve Infrastructure. Domain'de entity'ler duruyor, Application'da servisler, DTO'lar, hata kodları ve entegrasyon olayları. Infrastructure'da ise EF Core, Redis önbelleği, MassTransit ve outbox, middleware'ler, JWT kurulumu, repository'ler ve controller'lar var. Kullandığım başlıca kütüphaneler EF Core (Npgsql, SQLite), StackExchange.Redis, MassTransit, BCrypt.Net, FluentValidation, Serilog, prometheus-net ve Swashbuckle.

Docker Compose yığınında API'nin yanında PostgreSQL 15, Redis 7 ve RabbitMQ 3 çalışıyor. Veritabanı sağlayıcısı ayardan seçiliyor; belirtilmezse bağlantı dizesinden çıkarılıyor. SQLite modunda şema başlangıçta doğrudan oluşturuluyor, PostgreSQL'de ise EF Core migration'ları çalışıyor. Katalog önbelleği Redis'te. Redis'e ulaşılamazsa istek veritabanından cevaplanıyor, yani Redis düşse de API durmuyor.

Bütün uçlar /api/ altında ve küçük harfli, tekil kaynak adları kullanıyor. Kataloğu oturum açmadan okuyabiliyorsun ama katalogda yazma işlemleri Admin rolü istiyor; adres ve ödeme yöntemleri üzerinde de yalnızca sahibi işlem yapabiliyor. Kart numaraları her zaman maskeli dönüyor, tam kart numarası ve CVV ise hiç saklanmıyor.

Bir sipariş isteği nelerden geçiyor

En çok kuralın toplandığı uç POST /api/order. İstekte Idempotency-Key başlığı varsa ve aynı kullanıcı bu anahtarla daha önce sipariş oluşturduysa yeni sipariş açılmıyor, eskisi 200 ile geri dönüyor. Sonra kalemler ürüne göre birleştiriliyor; adresin ve ödeme yönteminin kullanıcıya ait ve aktif olup olmadığına bakılıyor. Sunucudaki sepet doluysa istenen kalemlerin sepetle birebir eşleşmesi gerekiyor, eşleşmezse CART_MISMATCH dönüyor.

Asıl iş tek bir transaction içinde dönüyor. Önce ürünlerin aktif olup olmadığına ve stoğa bakılıyor, kargo hesaplanıyor ve ödeme simüle ediliyor. Ardından stok iyimser kilitle düşülüyor, sipariş Pending olarak kaydediliyor, sepet temizleniyor ve "Sipariş alındı" bildirimi oluşuyor. İki istek aynı stoğa aynı anda dokunursa biri 409 CONFLICT alıyor. Satış fiyatı round(unitPrice × (1 − discount/100), 2) ile hesaplanıyor, 150 TRY altındaki sepetlere de 34,99 TRY kargo ekleniyor; ikisi de ayarlardan değiştirilebiliyor.

Sipariş Pending → Processing → Shipped → Delivered yolunu izliyor; yanında Cancelled, ReturnRequested ve Returned durumları da var. Her durum değişikliğinde kullanıcıya bildirim gidiyor, iptal ve iadede de stok geri ekleniyor. Geliştirme ortamında siparişi bir sonraki duruma ilerleten demo bir lojistik ucu açık, production'da kapalı.

Sipariş oluşturma ve durum değişikliği birer entegrasyon olayı üretiyor. Bu olayları doğrudan yayınlamıyorum, önce OutboxMessages tablosuna yazıyorum. Arka planda çalışan OutboxProcessor işlenmemiş mesajları sırayla okuyup MassTransit ile RabbitMQ'ya (ya da InMemory transport'a) gönderiyor; her mesajın kaç kez denendiğini ve son hatasını da kaydediyor. Tüketici tarafında, biri sipariş oluşturma, biri durum değişikliği için iki consumer var.

Her yanıt aynı zarfta

Sağlık, metrik ve Swagger uçları dışında /api/** altındaki bütün yanıtlar aynı biçimde dönüyor:

{
  "success": false,
  "message": "…",
  "error": "…",
  "errorCode": "VALIDATION_ERROR",
  "errors": { "email": ["Invalid email format"] },
  "traceId": "…"
}

success ve message her zaman var, diğer alanlar boşsa hiç yazılmıyor. Hatalarda istemcinin okuyabileceği bir errorCode dönüyor (UNAUTHORIZED, PRODUCT_NOT_FOUND, RATE_LIMITED gibi). traceId ise X-Correlation-Id başlığının değeri: bir middleware gelen kimliği kullanıyor ya da yenisini üretip yanıta ekliyor, Serilog da istekleri bu kimlikle logluyor. Böylece istemcide görülen bir hatanın log kaydını bu kimlikle bulabiliyorum. JSON alanları camelCase, tarihler UTC, tutarlar iki ondalıklı TRY.

Sağlık kontrolleri hazır ve canlı uçlarıyla geliyor, HealthChecks UI da bunları bir arayüzde gösteriyor. HTTP metrikleri Prometheus biçiminde dışarıya açık; sipariş ve stok sayılarını JSON olarak veren ayrı bir metrik ucu da var. Depoda Prometheus scrape yapılandırması ve Grafana provisioning dosyaları da duruyor.

Kimlik ve sınırlar

Kimlik doğrulamada JWT kullanıyorum (HS256, varsayılan süre 24 saat), şifreler de BCrypt ile özetleniyor. E-postası ayarlardaki yönetici listesinde olan kullanıcılar Admin rolü alıyor. "Tüm cihazlardan çıkış" dediğinde, isteği yapan token dışında kullanıcının bütün token'ları geçersiz oluyor. Bunun için doğrulama sırasında token'ın iat değerini kullanıcının iptal zamanıyla karşılaştırıyorum.

İstek sayısı IP başına sabit pencereyle sınırlanıyor; varsayılan sınır dakikada 300 istek. Sınır aşılınca 429 ve Retry-After dönüyor, sağlık ve metrik uçları ise bu sınırdan muaf. Yanıtlara X-Frame-Options: DENY, Referrer-Policy, Permissions-Policy gibi güvenlik başlıkları ekleniyor; HSTS yalnızca HTTPS isteklerinde gönderiliyor.

İki adımlı doğrulama ise henüz yok; ilgili iki uç şimdilik yalnızca şifreyi doğrulayan taslaklar.