Bu makale, Aşama 1'de analiz edilen 10 kritik hatanın devamıdır. Aşama 2'de, B2B bayi yönetim sisteminin en kritik modülü — Akıllı Stok, Çoklu Fiyat Listesi ve Sepet Yönetimi — modülünde production ortamında ortaya çıkacak 10 yeni senior-seviye hata analiz edilmektedir. Senaryo: Kampanya dönemi, yüzlerce bayi aynı anda sınırlı stoktaki popüler ürüne hücum ediyor.
B2B sistemlerinde en büyük krizler üç anda yaşanır: (1) Black Friday/Kampanya anı — yüzlerce bayi aynı anda son stoku çekmeye çalışır, (2) Dinamik matris fiyatlandırma — bayi gruplarına göre farklı iskonto oranları hesaplanırken float yuvarlama hataları, (3) Sepette anlık fiyat/stok güncellenmesi — sepet onayı sırasında fiyat değişirse veya stok tükenirse. Bu makale, bu üç kriz anını tetikleyecek 10 hatayı detaylıca analiz eder.
| Kategori | Hata | Hata Numaraları | Risk |
|---|---|---|---|
| 🔒 Güvenlik | 3 | #4 (Input), #5 (Fiyat), #7 (Fiyat Kontrol) | Kritik |
| 🔑 Veri/Finansal Tutarlılık | 5 | #1 (Derived State), #2 (Mutasyon), #6 (Float), #8 (Race), #9 (State), #10 (Partial) | Kritik |
| ⚠️ Frontend Hata Y. | 2 | #1 (Derived), #3 (Idempotency) | Yüksek |
Aşağıda her bir hatanın detaylı analizi — sorunun tanımı, production'daki etkisi ve önerilen çözüm:
Sepet toplam fiyatı ayrı bir state'te (totalPrice) tutuluyor ve useEffect ile senkronize ediliyor. Bu React'in 'derived state' antipattern'idir. State güncellemeleri batched olarak yapıldığında ara toplamlar bir karelik gecikmeyle gelir, bayi yanlış toplam görür.
updateQuantity fonksiyonunda sepet öğesi doğrudan mutasyona uğruyor: item.quantity = newQty. Bu React'in immutable state kuralını ihlal eder. Ayrıca önceki state güvenceye alınmıyor (functional update pattern eksik).
Sipariş Onayla butonuna çift tıklanırsa iki ayrı sipariş oluşur. Hiçbir debounce, loading state veya idempotency key mekanizması yok. Ağ yavaş olduğunda kullanıcı tekrar tıklar.
Miktar input'unda sayısal doğrulama yok. NaN, negatif değer, ondalık sayı girilebilir. parseInt(e.target.value) NaN dönebilir ve bu backend'e gider.
client_side_price parametresi frontend'den güvenilerek alınıyor! Bir bayi browser dev tools ile fiyatı 0.01 TL yapabilir. Backend hiçbir fiyat doğrulaması yapmıyor.
İskonto hesabında float veri tipi kullanılıyor: 5000.00 * (1 - 0.0853) = 4573.499999999999. Binlerce siparişte bu kuruş sapmaları birikerek muhasebe krizine dönüşür.
Frontend'den gelen fiyat ile backend hesaplanan fiyat uyuşmuyor ama işlem sadece print ile logluyor ve sessizce devam ediyor. Bu bir fail-safe mekanizması değil, fail-silent antipattern.
Stok kontrolü (if product['stock'] < item.quantity) ile stok düşme (product['stock'] -= item.quantity) arasında 0.1 saniye bekleme var. İki istek aynı anda kontrol yaparsa ikisi de stoku yeterli görür. Son stok 2 kez satılır.
db_inventory sözlüğü global bir Python değişkeni ve doğrudan mutasyona uğruyor: product['stock'] -= item.quantity. Çoklu worker'da her worker'ın kendi belleği farklı. Ayrıca hiçbir lock mekanizması yok.
Sepette 3 ürün var. 1. ürünün stoğu düşülür, 2. üründe stok yetersiz hatası çıkar. Ama 1. ürünün stok düşümü geri alınmaz. Transaction yok, rollback yok. Stok havada asılı kalır.
Aşağıdaki düzeltilmiş React kodu, frontend hatalarını (#1, #2, #3, #4, #5) çözmektedir:
import React, { useState, useMemo, useCallback } from 'react';
// DÜZELTME 3: Idempotency key üretici
function generateIdempotencyKey() {
return `${Date.now()}-${crypto.randomUUID()}`;
}
export default function B2BOrderCart({ initialItems }) {
const [cartItems, setCartItems] = useState(initialItems);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);
// DÜZELTME 1: Derived state antipattern cozumu — useMemo
const totalPrice = useMemo(() => {
return cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0);
}, [cartItems]);
// DÜZELTME 2: Immutable update + functional update pattern
const updateQuantity = useCallback((productId, newQty) => {
// DÜZELTME 4: Kontrolsuuz girdi korumasi
const parsedQty = Math.max(1, Math.floor(Number(newQty) || 1));
setCartItems(prev =>
prev.map(item =>
item.id === productId
? { ...item, quantity: parsedQty }
: item
)
);
}, []);
// DÜZELTME 3: Idempotency key + submitting state (double-click korumasi)
const checkout = async () => {
if (submitting) return; // Cift tiklama korumasi
setSubmitting(true);
setError(null);
const idempotencyKey = generateIdempotencyKey();
try {
const response = await fetch('/api/v2/orders/confirm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Idempotency-Key': idempotencyKey // DÜZELTME 3
},
credentials: 'include',
body: JSON.stringify({
items: cartItems.map(item => ({
product_id: item.id,
quantity: item.quantity
// DÜZELTME 5: client_side_price GONDERILMIYOR!
}))
})
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.detail || 'Siparis basarisiz');
}
const result = await response.json();
alert(`Siparis alindi! No: ${result.order_id}`);
} catch (err) {
setError(err.message);
} finally {
setSubmitting(false);
}
};
return (
{error && {error}}
{cartItems.map(item => (
{item.name} - {item.price.toLocaleString('tr-TR')} TL
{/* DÜZELTME 4: min=1, step=1 ile kontrol */}
updateQuantity(item.id, e.target.value)}
/>
))}
Toplam: {totalPrice.toLocaleString('tr-TR')} TL
);
}useMemo ile derived state — ayrı totalPrice state'i kaldırıldısetCartItems(prev => prev.map(...))X-Idempotency-Key header + submitting state ile çift tıklama korumasımin=1, step=1, Math.max(1, Math.floor(Number(value))) ile girdi doğrulamaclient_side_price tamamen kaldırıldı — backend fiyatı kendisi hesaplarAşağıdaki düzeltilmiş FastAPI kodu, backend hatalarını (#5, #6, #7, #8, #9, #10) çözmektedir:
import asyncio
import uuid
from decimal import Decimal, ROUND_HALF_UP # DÜZELTME 6: Decimal
from fastapi import FastAPI, HTTPException, Header, Depends
from pydantic import BaseModel, validator
from typing import List
import redis.asyncio as aioredis
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text
app = FastAPI()
redis_client = aioredis.from_url("redis://localhost:6379/0", decode_responses=True)
# DÜZELTME 5: client_side_price KALDIRILDI
class CartItem(BaseModel):
product_id: int
quantity: int
# DÜZELTME 4: Backend validasyonu
@validator('quantity')
def validate_quantity(cls, v):
if v < 1:
raise ValueError('Miktar 1\'den az olamaz')
return v
# DÜZELTME 8: Redis distributed lock
async def acquire_lock(product_id: int, ttl: int = 10):
lock_key = f"stock_lock:{product_id}"
lock_id = str(uuid.uuid4())
acquired = await redis_client.set(lock_key, lock_id, nx=True, ex=ttl)
if not acquired:
raise HTTPException(409, "Urun isleniyor, tekrar deneyin")
return lock_key, lock_id
async def release_lock(lock_key: str, lock_id: str):
current = await redis_client.get(lock_key)
if current == lock_id:
await redis_client.delete(lock_key)
@app.post("/api/v2/orders/confirm")
async def confirm_order(
items: List[CartItem],
x_idempotency_key: str = Header(...), # DÜZELTME 3: Idempotency key
db: AsyncSession = Depends(get_db)
):
# DÜZELTME 3: Idempotency key kontrolu
idem_key = f"idem:{x_idempotency_key}"
existing = await redis_client.get(idem_key)
if existing:
return {"status": "duplicate", "order_id": existing}
# DÜZELTME 10: Tum siparis TEK transaction
locks = []
try:
async with db.begin():
for item in items:
# DÜZELTME 8: Lock al
lk, lid = await acquire_lock(item.product_id)
locks.append((lk, lid))
# DÜZELTME 9: Atomik stok dusumu (SELECT FOR UPDATE + atomic decrement)
result = await db.execute(
text("UPDATE products SET stock = stock - :qty WHERE id = :pid AND stock >= :qty RETURNING id, name, stock, base_price"),
{"qty": item.quantity, "pid": item.product_id}
)
row = result.fetchone()
if not row:
raise HTTPException(
409,
f"Stok yetersiz: Urun ID {item.product_id}"
)
# DÜZELTME 6: Decimal ile fiyat hesabi
base_price = Decimal(str(row.base_price))
discount_ratio = Decimal("0.0853")
calculated_price = (base_price * (Decimal("1") - discount_ratio)
.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
# DÜZELTME 7: Fiyat tutarlilik kontrolu
logger.info(
f"Price calculated for product {item.product_id}: "
f"base={base_price}, final={calculated_price}"
)
# Siparis ogresini olustur
order_item = OrderItem(
product_id=item.product_id,
quantity=item.quantity,
unit_price=calculated_price,
total_price=calculated_price * item.quantity
)
db.add(order_item)
# Siparisi olustur
order = Order(status="confirmed")
db.add(order)
await db.flush()
# DÜZELTME 3: Idempotency key kaydet (24 saat)
await redis_client.setex(idem_key, 86400, str(order.id))
return {"status": "confirmed", "order_id": order.id}
except HTTPException:
raise # Stok yetersiz hatasi — transaction rollback olur
except Exception as e:
logger.error(f"Order failed: {e}")
raise HTTPException(500, "Siparis olusturulamadi")
finally:
# DÜZELTME 8+10: Tum locklari release et
for lk, lid in locks:
await release_lock(lk, lid)client_side_price kaldırıldı — backend DB'den fiyat hesaplarDecimal sınıfı + ROUND_HALF_UP ile finansal hassasiyetHTTP 409 + structured loggingacquire_lock / release_lock — Redis SET NX ile distributed lockUPDATE ... SET stock = stock - :qty WHERE stock >= :qty RETURNING * — atomik stok düşümüasync with db.begin() — tek transaction, hata durumunda otomatik ROLLBACK + finally'de lock releaseX-Idempotency-Key header + Redis'te 24 saat saklama — çift sipariş engeliAşama 2 — Akıllı Stok, Çoklu Fiyat Listesi ve Sepet Yönetimi modülünde analiz edilen 10 kritik hata, bir B2B e-ticaret sisteminin en patlayıcı noktalarını temsil etmektedir. Race condition (over-selling) ve float precision hatası finansal olarak en yıkıcı sorunlardır — ilki şirketi stoksuz bırakır, ikincisi muhasebe krizi yaratır. Frontend'den fiyat alma güvenlik açığı ise doğrudan dolandırıcılığa imkan tanır.
Sunulan production-ready kod mimarisi — Redis distributed lock, Decimal finansal hesaplama, DB transaction + SELECT FOR UPDATE atomic stok düşümü, idempotency key, Pydantic validasyon, useMemo derived state ve immutable update pattern — bu sorunların tamamını çözmektedir. Aşama 3'te B2B e-Tahsilat ve Sanal Pos Entegrasyonu modülü ele alınacaktır: çift tahsilat, risk limit aşımı, 3D Secure redirect kaybı, refund race condition ve POS failover.