6 Pazaryerini Tek Panelden Yönetmek: Dışarıya Giden Her İşlem İçin Bir Onay Kapısı
Bir halı markası için geliştirdiğim çok kanallı e-ticaret sistemi; Trendyol, Hepsiburada, N11, Amazon, Çiçeksepeti ve Pazarama'yı, markanın kendi web sitesini, tedarikçinin stok servisini ve e-fatura sağlayıcısını tek panelde topluyor. Böyle bir sistemde asıl tehlike bir API'nin çökmesi değil. Asıl tehlike, sistemin çalışırken yanlış bir şey yapması: yanlış eşleşmiş bir varyantın stoğunu sıfırlamak, bir Excel hatası yüzünden 4.000 ürünün fiyatını yarıya düşürmek ya da aynı siparişi tedarikçiye iki kez açmak. Bunların hiçbiri hata mesajı vermez, sadece para kaybettirir. Bu yazıda bunun önüne nasıl geçtiğimi anlatıyorum.
Kural: dışarıya giden her işlem kapıdan geçer
Sistemin değişmez kurallarından biri şu: pazaryerine stok/fiyat göndermek, sipariş durumu bildirmek, fatura kesmek, iade yapmak, WhatsApp'tan etiket yollamak... dış dünyayı değiştiren her işlem tek bir ApprovalService üzerinden geçiyor. Her işlem türünün panelden ayarlanabilen bir modu var: onaylı ya da otomatik. Varsayılan çok tutucu: sadece stok düşüşü otomatik, geri kalan her şey bir insanın "Onayla" demesini bekliyor.
public async Task<OperationMode> GetModeAsync(OperationType type, CancellationToken ct)
{
var policy = await db.OperationPolicies.AsNoTracking()
.FirstOrDefaultAsync(p => p.Type == type, ct);
// Ayarı olmayan tür güvenli tarafta kalır: onay bekler.
return policy?.Mode ?? OperationMode.Approval;
}Bu küçük satırdaki ?? bence sistemin en önemli kararı. Yeni bir işlem türü eklendiğinde ve kimse onun ayarını girmeyi hatırlamadığında, varsayılan davranış "çalıştır" değil "bekle" oluyor. Güvenli varsayılan, unutkanlığı zararsız hâle getiriyor.
İki modda da talep ve sonucu kayda geçiyor. Otomatik çalışan bir işlem de bir onay talebi olarak yazılıyor, sadece IsAutomatic işaretiyle hemen yürütülüyor. Böylece "dün gece Trendyol'a ne gönderildi?" sorusunun cevabı her zaman aynı ekranda.
Bayat talepler: aynı işin eski hâlini onaylamamak
Onay kuyruğu kendi başına yeni bir sorun yaratıyor. Diyelim ki saat 10'da yeni fiyat listesi yüklendi ve Trendyol fiyat gönderimi onay bekliyor. 11'de liste düzeltilip tekrar yüklendi. Kuyrukta artık iki talep var ve birisi eskisini onaylarsa yanlış fiyatlar gider.
Bunun için her talebin opsiyonel bir kapsam anahtarı (ScopeKey) var, örneğin price:trendyol:0. Aynı anahtarla yeni bir talep açıldığında bekleyen eski talepler Superseded durumuna çekiliyor ve artık onaylanamıyor. Büyük gönderimler parçalara bölündüğünde (price:trendyol:0, :1, :2...) önek bazlı geçersiz kılma ile eski parçaların hepsi birden düşüyor. Onaylayan kişi her zaman en güncel hâli görüyor.
Stok yalnızca onaylanmış eşleştirmeden akar
Pazaryerindeki bir ürün ile katalogdaki bir varyantın aynı şey olduğuna kim karar veriyor? İlk aktarımda bu eşleştirme otomatik öneriliyor ama onaylanmadan stok akmıyor. Eşleşmesi onaylanmamış bir varyantın stoğuna sistem dokunmuyor. Bu, "otomatik eşleştirme yanlış bir benzerlik yakaladı ve başka bir ürünün stoğunu sıfırladı" senaryosunu tamamen ortadan kaldırıyor.
Fiyat tarafında da benzer bir tutuculuk var: her kanalın fiyatı kendi Excel listesinden geliyor. Listede olmayan bir ürünün pazaryerindeki mevcut fiyatına dokunulmuyor ve ürün o kanala ilk kez gönderilemiyor, gönderim ekranında hata olarak görünüyor. Örnek Trendyol listesiyle salt okunur bir deneme yaptığımda 4.830 varyantın 4.208'i eşleşti. Belirsiz kalan 40 varyant ise listede aynı isimle iki ayrı satır olmasından kaynaklanıyordu. Sistem bunları tahmin etmek yerine belirsiz olarak işaretledi. Doğru davranış da buydu.
Geliştirme ortamı canlıya bağlıyken nasıl güvende kalınır?
Projenin pratik bir gerçeği vardı: geliştirme ortamı, gerçek siparişleri ve ürünleri görebilmek için canlı veritabanına bağlıydı. Bu da yerelde bir butona yanlışlıkla basmanın gerçek bir pazaryerine yazmak anlamına gelebileceği demek.
Çözümü kod seviyesinde kurdum: Integrations:ReadOnly=true ayarı açıkken her kanal istemcisi, yazma işlemine girmeden önce ReadOnlyModeException fırlatıyor. Pazaryerine stok/fiyat göndermek, sipariş bildirimi, iptal, etiket, fatura yükleme, fatura kesme, iade, WhatsApp ve e-posta gönderimi; hepsi bu kontrolün arkasında. Zamanlanmış işler de geliştirmede kapalı. Bunu "dikkatli olurum" disiplinine bırakmak yerine istemcilerin içine koymak, yorgun bir gece yapılacak hatayı imkânsız hâle getiriyor.
Bir kanalın hatası diğerlerini durdurmaz
Altı pazaryeri, altı farklı hata karakteri demek. Biri gece bakıma giriyor, biri ara ara 5xx dönüyor, biri yoğun saatlerde yavaşlıyor. Sipariş çekimi ve stok senkronizasyonu kanal bazında izole çalışıyor: bir kanalın hatası loglanıp aktivite kaydına düşüyor ama turun geri kalanını kesmiyor. Geçici 5xx hatalarında sadece okuma istekleri yeniden deneniyor. Yazma isteklerini körü körüne tekrarlamak, ilk istek aslında başarılı olmuşsa çift işlem demek olurdu.
API'si olmayan tedarikçi: bot ama sıkı kurallarla
Stok tedarikçide duruyor ve tedarikçinin bayi sitesinin sipariş API'si yok. Sipariş, Playwright ile çalışan ayrı bir botla açılıyor. Gerçek para ve gerçek kargo söz konusu olduğu için botun kuralları da sıkı: sipariş numarası kaydıyla çift sipariş engeli, sepet boş değilse durma, aynı anda tek sipariş, adresi sipariş numarasıyla seçip doğrulama, onaydan hemen önce son kontrol. Bir şey ters giderse bot tahmin yürütmüyor. Ekran görüntüsü alıp işi "elle bakılacak" listesine düşürüyor. Tedarikçi bir gün API açarsa sadece bu katman değişecek, çünkü bot da bir arayüzün arkasında duruyor.
Sonuç
Çok kanallı entegrasyonda "her şeyi otomatikleştirmek" kolay. Zor olan, otomasyonun yanlış bir şey yapmasını imkânsız hâle getirmek. Güvenli varsayılanlı bir onay kapısı, bayat talepleri düşüren kapsam anahtarları, onaysız eşleştirmeye stok akıtmamak, kod seviyesinde salt okunur mod ve birbirini etkilemeyen kanallar: bu kararlar sistemi biraz daha "yavaş" ama çok daha güvenilir yaptı. Panel, web sitesi ve e-fatura tarafının detayları proje sayfasında.