Merhaba arkadaşlar, bugün sizlere özellikle orta ve büyük ölçekli projelerde hayat kurtaran, benim de artık vazgeçilmezim haline gelen bir yapıdan bahsedeceğim. Başlangıçta her şeyi controller'a doldurup "çalışıyor işte" dediğim günler geride kaldı. Proje büyüdükçe, bir fonksiyonda değişiklik yapmak için 500 satırlık bir dosyada gezmek, test yazmak imkansız hale geliyordu. İşte tam da bu noktada Controller > Service > Repository katmanlı mimarisi ve Dependency Injection imdadıma yetişti.
Neden Bu Yapıya Geçtim?
Aslında sebep basitti: spaghetti kod. Auth işlemleri, veritabanı sorguları, iş mantığı (business logic) hepsi aynı controller dosyasının içinde, birbirine girmiş vaziyetteydi. Bir bug'ı fixlemek, yeni bir özellik eklemek kabusa dönüşüyordu. Ayrıca, birim testi (unit test) yazmak neredeyse imkansızdı çünkü her şey birbirine sıkı sıkıya bağlıydı. "Tight Coupling" denen o illet işte.
Katmanlı mimari, bu karmaşayı ayrıştırmak ve her katmana tek bir sorumluluk vermek için birebir. Repository veritabanından sorumlu, Service iş kurallarından, Controller ise gelen HTTP isteğini alıp cevap dönmekten.
Katmanları Nasıl Ayırdım?
Hemen basit bir Kullanıcı Yönetimi örneği üzerinden gidelim. Diyelim ki bir kullanıcının profil bilgilerini getireceğiz.
İlk katman Repository. Tek işi veritabanı ile konuşmak.
Sonraki katman Service. İş mantığı burada. Repository'yi kullanır.
En üst katman Controller. HTTP istek/cevap işleri burada.
Dependency Injection (Bağımlılık Enjeksiyonu) Neden Altın Değerinde?
Yukarıdaki kodlara dikkat edin. UserService, UserRepository'yi constructor'ında parametre olarak alıyor. UserController da UserService'i parametre olarak alıyor. İşte bu, Dependency Injection (DI).
Faydaları:
1. Test Edilebilirlik: Unit test yazarken, gerçek veritabanı bağlantısı olan bir Repository yerine, taklit (mock) bir Repository objesini Service'in constructor'ına verebilirim. Aynı şekilde Controller'ı test ederken de mock Service kullanırım. Her şey birbirinden bağımsız test edilebilir!
2. Esneklik: Yarın öbür gün veritabanınızı MySQL'den PostgreSQL'e geçirmek isterseniz, sadece Repository katmanındaki sorguları değiştirirsiniz. Service ve Controller katmanları hiç etkilenmez.
3. Merkezi Yönetim: Bağımlılıklarınızı (Service, Repository vs.) merkezi bir yerden (bir Container) yönettiğinizde, yaşam döngülerini ve konfigürasyonlarını tek elden kontrol edersiniz.
Sonuç ve Düşüncelerim
Bu yapıya geçiş yapmak ilk başta biraz fazla dosya, biraz fazla soyutlama gibi gelebilir. Küçük projelerde gereksiz de olabilir. Ama projeniz büyümeye başladığı anda, özellikle ekip arkadaşlarınızla çalışırken, herkesin aynı yapıyı kullanması ve kodun nereye yazılacağının net olması inanılmaz bir disiplin ve rahatlık sağlıyor.
Siz projelerinizde nasıl bir yapı kullanıyorsunuz? Katmanlı mimari sizin için de vazgeçilmez mi yoksa daha farklı, daha hafif pattern'ler mi tercih ediyorsunuz? Laravel'de Eloquent ile Repository pattern kullanmak gereksiz mi sizce? Yorumlarda tartışalım!
Aslında sebep basitti: spaghetti kod. Auth işlemleri, veritabanı sorguları, iş mantığı (business logic) hepsi aynı controller dosyasının içinde, birbirine girmiş vaziyetteydi. Bir bug'ı fixlemek, yeni bir özellik eklemek kabusa dönüşüyordu. Ayrıca, birim testi (unit test) yazmak neredeyse imkansızdı çünkü her şey birbirine sıkı sıkıya bağlıydı. "Tight Coupling" denen o illet işte.
Katmanlı mimari, bu karmaşayı ayrıştırmak ve her katmana tek bir sorumluluk vermek için birebir. Repository veritabanından sorumlu, Service iş kurallarından, Controller ise gelen HTTP isteğini alıp cevap dönmekten.
Hemen basit bir Kullanıcı Yönetimi örneği üzerinden gidelim. Diyelim ki bir kullanıcının profil bilgilerini getireceğiz.
İlk katman Repository. Tek işi veritabanı ile konuşmak.
JavaScript:
// UserRepository.js
class UserRepository {
constructor(databaseConnection) {
this.db = databaseConnection;
}
async findById(userId) {
// Sadece ve sadece DB sorgusu!
return await this.db('users').where({ id: userId }).first();
}
async updateProfile(userId, updateData) {
return await this.db('users').where({ id: userId }).update(updateData);
}
}
Sonraki katman Service. İş mantığı burada. Repository'yi kullanır.
JavaScript:
// UserService.js
class UserService {
constructor(userRepository, emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
async getUserProfile(userId) {
const user = await this.userRepository.findById(userId);
if (!user) {
throw new Error('Kullanıcı bulunamadı!');
}
// Belki burada kredi skoru kontrolü, yetki kontrolü gibi iş kuralları olacak.
return user;
}
async updateUserProfile(userId, profileData) {
// İş kuralları: Mail değiştiyse doğrulama gönder.
const updatedUser = await this.userRepository.updateProfile(userId, profileData);
if (profileData.email) {
await this.emailService.sendVerificationEmail(updatedUser.email);
}
return updatedUser;
}
}
En üst katman Controller. HTTP istek/cevap işleri burada.
JavaScript:
// UserController.js
class UserController {
constructor(userService) {
this.userService = userService;
}
async getProfile(req, res) {
try {
const userId = req.params.id;
const userProfile = await this.userService.getUserProfile(userId);
res.json({ success: true, data: userProfile });
} catch (error) {
res.status(404).json({ success: false, message: error.message });
}
}
}
Yukarıdaki kodlara dikkat edin. UserService, UserRepository'yi constructor'ında parametre olarak alıyor. UserController da UserService'i parametre olarak alıyor. İşte bu, Dependency Injection (DI).
Faydaları:
1. Test Edilebilirlik: Unit test yazarken, gerçek veritabanı bağlantısı olan bir Repository yerine, taklit (mock) bir Repository objesini Service'in constructor'ına verebilirim. Aynı şekilde Controller'ı test ederken de mock Service kullanırım. Her şey birbirinden bağımsız test edilebilir!
2. Esneklik: Yarın öbür gün veritabanınızı MySQL'den PostgreSQL'e geçirmek isterseniz, sadece Repository katmanındaki sorguları değiştirirsiniz. Service ve Controller katmanları hiç etkilenmez.
3. Merkezi Yönetim: Bağımlılıklarınızı (Service, Repository vs.) merkezi bir yerden (bir Container) yönettiğinizde, yaşam döngülerini ve konfigürasyonlarını tek elden kontrol edersiniz.
Bu yapıya geçiş yapmak ilk başta biraz fazla dosya, biraz fazla soyutlama gibi gelebilir. Küçük projelerde gereksiz de olabilir. Ama projeniz büyümeye başladığı anda, özellikle ekip arkadaşlarınızla çalışırken, herkesin aynı yapıyı kullanması ve kodun nereye yazılacağının net olması inanılmaz bir disiplin ve rahatlık sağlıyor.
Siz projelerinizde nasıl bir yapı kullanıyorsunuz? Katmanlı mimari sizin için de vazgeçilmez mi yoksa daha farklı, daha hafif pattern'ler mi tercih ediyorsunuz? Laravel'de Eloquent ile Repository pattern kullanmak gereksiz mi sizce? Yorumlarda tartışalım!