Kodu Yeniden Düzenlemenin Sadece Temiz Kod Yazmak Olduğunu Sanıyordum. Yanılmışım.
Bir üniversite yoklama sistemi backend'ini yeniden düzenleme üzerine vaka çalışması; risk azaltma ve veritabanı bağlantı yönetimine odaklanıyor.

Backend kodunu yeniden düzenlemenin (refactoring) çoğunlukla kötü kodu "temiz" koda dönüştürmek olduğunu sanıyordum.
Yanılmışım.
En zor kısım, hiç kimsenin yeniden düzenleme yapıldığını fark etmemesini sağlamaktı.
Yakın zamanda bir üniversitenin QR kodlu yoklama sistemi için Express backend'ini yeniden yapılandırmak üzerinde çalıştım. Platform şu anda günde ortalama 300 yoklama işlemi yapan 2.000 aktif kullanıcıya sahip ve farklı fakültelerde hala test aşamalarından geçiyor.
Bu ölçekte bile riskler büyüktü: API beklenmeyen bir veri formatı döndürürse veya bir ders sırasında çökerse, öğrencilerin yoklama kayıtları tehlikeye girecekti.
Devraldığım Kaos
Kod tabanını incelemeye başladığımda, zamanla kendi kendine büyümüş bir mimari buldum. Bu, teknik borcun (technical debt) klasik bir örneğiydi:
- Devasa Route Dosyaları: 3.000'den fazla satırı aşan route dosyalarımız vardı.
- Kopyalanmış Yetkilendirme: JWT doğrulama ara yazılımı (middleware) birden fazla dosya arasında manuel olarak kopyalanmıştı.
- Veritabanı ORM Çorbası: Kod tabanı aynı anda
pg(ham sorgular),knex(sorgu oluşturucu) veprisma'yı (ORM) bir arada kullanıyordu. - Mantık Sızıntıları: Karmaşık iş mantığı, doğrudan Express rotalarının (routes) içine sıkı sıkıya bağlıydı.
Ancak en çok dikkat çeken şey mimari bir seçimdi.
Prisma Bağlantı Havuzu Sorunu
Route dosyalarını incelerken, bu kalıbın her yerde tekrarlandığını fark ettim:
// studentRoutes.js içinde
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient(); // ❌ Her dosyada yeni bir instance oluşturuluyor
router.post('/record', async (req, res) => {
// ...
});
// attendanceRoutes.js içinde
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient(); // ❌ Başka bir bağımsız bağlantı havuzuHer route dosyası kendi new PrismaClient()'ını oluşturuyordu. Bu, uygulamanın istemeden veritabanına birden fazla bağımsız bağlantı havuzu (connection pools) açtığı anlamına geliyordu. Bir dersin başlangıcındaki eşzamanlı yoklamalar sırasında bu yaklaşım bağlantıları israf ediyor ve veritabanı sınırlarına erken ulaşma riskini taşıyordu.
Çözüm basitti: Tek bir Prisma örneği (Singleton deseni) çıkarmak ve bunu uygulama genelinde yeniden kullanmak.
// prisma/client.js
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
module.exports = prisma; // ✅ Tek ve genel bir havuzBu küçük kod değişikliği, veritabanı bağlantı kullanımını daha öngörülebilir hale getirdi ve yoğun kullanım saatlerinde bağlantı sınırlarına ulaşma riskini azalttı.
Pratik İpucu: ORM istemcinizi asla ayrı route veya service dosyaları içinde başlatmayın. Tüm Node.js süreci boyunca tek bir bağlantı havuzunu korumak için her zaman global bir singleton kullanın.
"Temiz Mimari" İşin Kolay Kısmı
Bu yeniden düzenleme sırasında fark ettiğim bir diğer önemli şey: "temiz mimariyi" uygulamak aslında işin kolay kısmıydı. Gerçek zorluk bunu şu şartlar altında yapmaktı:
- Geriye dönük uyumluluğu korumak: Frontend (arayüz), tam olarak aynı JSON anahtarlarını bekliyor.
- Mobil uygulamayı bozmamak: Mobil uygulamalar anında güncellenmeye zorlanamaz.
- Yanıt formatlarını aynı tutmak: Alttaki veritabanı yapısı değişse bile, API çıktısı kusursuz kalmalıdır.
- Hassas akışlara testler eklemek: Test edilebilir şekilde inşa edilmemiş akışların nasıl test edileceğini bulmak.
- Geri alma yolları tasarlamak: Dağıtım beklenmeyen hatalar (regressions) ortaya çıkarırsa, geri dönmek için net bir plana sahip olmak.
Risk Seviyesine Göre Yeniden Düzenleme
Bunu yönetmek için, özellikleri bir "yeniden düzenleme risk seviyesine" göre sınıflandırmaya başladım.
Örneğin, attendance (yoklama) ve session (oturum) akışlarını yüksek riskli alanlar olarak değerlendirdim ve ilk önce entegrasyon testleri eklemeden onları değiştirmekten kaçındım. Çünkü bu akışlar gerçek öğrenci kayıtlarını etkiler; orada oluşan herhangi bir hata (bug) sadece bir log hatası değildir—yanlış akademik verilere dönüşür.
Daha ilginç geçişlerden biri, ham SQL işlem (transactional) akışlarını değiştirmekti.
Prisma Transaction'lara Geçiş
Aşağıdakine benzer eski ham SQL transaction'larımız vardı:
// Eski Knex/PG karışık transaction
const client = await pool.connect();
try {
await client.query('BEGIN');
// Karmaşık insert ve update işlemleri
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}Bunları prisma.$transaction'a taşıdım. Buradaki amaç sadece modern bir araç kullanmak değil, aynı zamanda API davranışının onu tüketen mevcut istemcilerle (clients) uyumlu kalmasını sağlarken tip güvenliğini (type safety) ve otomatik geri almaları (automatic rollbacks) garanti etmekti.
// Yeni Prisma Transaction
const result = await prisma.$transaction(async (tx) => {
const session = await tx.session.update({ /* ... */ });
const attendance = await tx.attendance.create({ /* ... */ });
return attendance;
});Farkındalık
Bu projenin derinliklerine indikçe bakış açım daha da değişti.
Backend mühendisliği "sadece API'ler inşa etmek" gibi hissettirmeyi bıraktı...
...ve daha çok: Üretim (production) kısıtlamaları altında risk yönetimi gibi hissettirmeye başladı.
Need help with your backend?
I build and optimize full-stack apps with Node.js, React, and PostgreSQL. If you have a slow API or need a robust architecture, let's talk.