Node.js ve MongoDB'de Repository Deseni: Veri Erişim Katmanınızı Güçlendirin

Yazılım geliştirme süreçlerinde, özellikle Node.js ve MongoDB gibi teknolojileri bir araya getirdiğimizde, uygulamamızın karmaşıklığı arttıkça veri erişim katmanının yönetimi kritik bir hale gelir. Doğrudan kontrolcüler veya servisler içinde veritabanı sorguları yazmak, başlangıçta hızlı bir çözüm gibi görünse de, zamanla kod tekrarına, test edilebilirlik sorunlarına ve bağımlılıkların artmasına yol açabilir. Benim geliştirme tecrübelerimde, bu tür durumların önüne geçmek ve daha sağlam, bakımı kolay uygulamalar inşa etmek için sıklıkla başvurduğum yöntemlerden biri Repository Deseni olmuştur.
Bu yazıda, Node.js tabanlı uygulamalarınızda MongoDB ile çalışırken Repository desenini nasıl etkin bir şekilde uygulayabileceğinizi adım adım ele alacağım. Amacımız, veri erişim katmanınızı iş mantığınızdan soyutlayarak kodunuzu daha modüler, test edilebilir ve gelecekteki değişikliklere daha esnek hale getirmek. Hazırsanız, bu güçlü mimari deseni daha yakından inceleyelim.
Repository Deseni Nedir ve Neden Önemlidir?
Repository Deseni (Depo Deseni), bir uygulamanın veri depolama mekanizması (veritabanı, dosya sistemi, dış API vb.) ile iş mantığı arasında bir soyutlama katmanı oluşturur. Bu desen, veri koleksiyonlarını (örneğin, tüm kullanıcılar veya ürünler) bellekteymiş gibi ele almamızı sağlayan bir arayüz tanımlar. İstemci kod (iş mantığı) bu soyut arayüz üzerinden veriyle etkileşime girer ve veri depolama katmanının detaylarından haberdar olmaz.
Repository Deseninin Temel Faydaları
Veri Depolama Bağımsızlığı: Uygulamanızın iş mantığı, kullandığınız veritabanının (MongoDB, PostgreSQL, MySQL vb.) veya ORM'nin (Mongoose, Sequelize) detaylarını bilmek zorunda kalmaz. Veritabanını değiştirmeniz gerektiğinde, sadece Repository uygulamasını güncellemeniz yeterli olur, iş mantığınız etkilenmez.
Test Edilebilirlik: Repository deseni, test yazmayı büyük ölçüde kolaylaştırır. İş mantığınızı test ederken gerçek veritabanı bağlantısı yerine, Repository'nin mock (sahte) bir uygulamasını kullanabilirsiniz. Böylece, veritabanı performansına veya durumuna bağlı olmayan hızlı ve güvenilir birim testleri yazabilirsiniz. Node.js uygulamalarında güvenilir test stratejileri için bu oldukça önemlidir.
Kod Tekrarının Azalması: Veri erişim mantığını tek bir yerde topladığınız için, aynı sorguların veya veri manipülasyonlarının farklı yerlerde tekrarlanmasını engellersiniz.
Daha Temiz İş Mantığı: İş mantığınız, veritabanı etkileşimlerinin karmaşıklığından arındırılarak sadece kendi sorumluluğuna odaklanır. Bu, kodun okunabilirliğini ve bakımını artırır. Temiz Mimari prensipleriyle uyumlu bir yaklaşımdır.
Domain Mantığı Kapsüllemesi: Veri manipülasyonlarıyla ilgili domain kurallarını (örneğin, bir kullanıcının sadece belirli alanlarını güncelleyebilme) Repository içinde kapsülleyebilirsiniz.

Node.js ve MongoDB Bağlamında Repository
Node.js'in esnek yapısı ve JavaScript'in prototip tabanlı doğası, Repository desenini uygulamak için oldukça uygun bir zemin sunar. MongoDB'nin belge tabanlı ve şemasız yapısı da (Mongoose gibi araçlarla şema uygulanabilir olsa da) Repository deseniyle iyi entegre olur. Genellikle, MongoDB ile Node.js geliştirirken Mongoose gibi bir ORM (Object-Relational Mapping) veya ODM (Object-Document Mapping) kütüphanesi kullanırız. Repository deseni, bu kütüphaneyi doğrudan iş mantığından soyutlayarak daha yüksek bir soyutlama katmanı sağlar.
Geleneksel Yaklaşımdan Farkı
Geleneksel bir Node.js/Express uygulamasında, veritabanı sorgularını doğrudan kontrolcüler veya servis dosyaları içinde yazmak yaygındır:
// Kötü pratik: Kontrolcü içinde doğrudan veritabanı etkileşimi
const User = require('../models/user');
exports.getUser = async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ message: 'Kullanıcı bulunamadı' });
}
res.json(user);
} catch (error) {
res.status(500).json({ message: error.message });
}
};Bu yaklaşım, küçük projelerde sorun yaratmayabilir, ancak projeniz büyüdükçe test yazmak, veritabanı teknolojisini değiştirmek veya iş mantığını güncellemek kabusa dönüşebilir. Repository deseni, bu karmaşıklığı kontrol altına almamıza yardımcı olur.
Pratik Uygulama: Node.js, Express ve MongoDB ile Repository
Şimdi adım adım Node.js, Express ve Mongoose kullanarak bir Repository deseni uygulayalım. Bir 'User' (Kullanıcı) modeli üzerinden örnekler vereceğim.
Adım 1: Proje Kurulumu
Yeni bir Node.js projesi oluşturun ve gerekli paketleri yükleyin:
mkdir nodejs-repository-example
cd nodejs-repository-example
npm init -y
npm install express mongoose dotenvProjenizde `.env` dosyası oluşturarak MongoDB bağlantı URI'nızı tanımlayın:
MONGO_URI=mongodb://localhost:27017/my_databaseAdım 2: MongoDB Bağlantısı
src/config/db.js dosyasında MongoDB bağlantısını kurun:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB başarıyla bağlandı.');
} catch (error) {
console.error('MongoDB bağlantı hatası:', error.message);
process.exit(1); // Uygulamayı sonlandır
}
};
module.exports = connectDB;Adım 3: Temel Model Tanımlama (Mongoose Schema)
src/models/User.js dosyasında Mongoose şemasını tanımlayın:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
});
module.exports = mongoose.model('User', UserSchema);Adım 4: Soyut Repository Arayüzü (Interface)
JavaScript'te doğrudan interface kavramı olmasa da, bu konsepti bir sınıf veya fonksiyon ile simüle edebiliriz. Bu, somut Repository'lerimizin uyması gereken bir kontrat görevi görecektir. src/repositories/interfaces/IRepository.js:
class IRepository {
constructor() {
if (new.target === IRepository) {
throw new Error("Abstract class 'IRepository' cannot be instantiated directly.");
}
}
async create(item) { throw new Error('Method not implemented.'); }
async find(query = {}) { throw new Error('Method not implemented.'); }
async findById(id) { throw new Error('Method not implemented.'); }
async update(id, item) { throw new Error('Method not implemented.'); }
async delete(id) { throw new Error('Method not implemented.'); }
}
module.exports = IRepository;Adım 5: Somut MongoDB Repository Uygulaması
Şimdi User modeli için MongoDB'ye özel Repository'imizi oluşturalım. src/repositories/UserRepository.js:
const User = require('../models/User');
const IRepository = require('./interfaces/IRepository');
class UserRepository extends IRepository {
constructor() {
super();
this.model = User;
}
async create(user) {
return this.model.create(user);
}
async find(query = {}) {
return this.model.find(query);
}
async findById(id) {
return this.model.findById(id);
}
async update(id, updatedUser) {
return this.model.findByIdAndUpdate(id, updatedUser, { new: true });
}
async delete(id) {
return this.model.findByIdAndDelete(id);
}
}
module.exports = UserRepository;Adım 6: Servis Katmanı
Repository'i doğrudan kontrolcüye bağlamak yerine, iş mantığını içeren bir servis katmanı oluşturalım. Bu servis, Repository ile etkileşime girecek ve kontrolcülere daha üst düzey işlevler sunacaktır. src/services/UserService.js:
class UserService {
constructor(userRepository) {
this.userRepository = userRepository; // Bağımlılık Enjeksiyonu
}
async createUser(userData) {
// İş mantığı, validasyon vb. buraya gelir
return this.userRepository.create(userData);
}
async getUsers(query) {
return this.userRepository.find(query);
}
async getUserById(id) {
return this.userRepository.findById(id);
}
async updateUser(id, userData) {
return this.userRepository.update(id, userData);
}
async deleteUser(id) {
return this.userRepository.delete(id);
}
}
module.exports = UserService;Burada, UserService sınıfının yapıcısına userRepository'i enjekte ettik. Bu, Bağımlılık Enjeksiyonu (Dependency Injection) prensibinin harika bir örneğidir ve test edilebilirliği artırır.

Adım 7: Kontrolcü ve Rota
Şimdi servis katmanını kullanacak kontrolcümüzü ve rotalarımızı oluşturalım. src/controllers/userController.js:
class UserController {
constructor(userService) {
this.userService = userService;
}
async createUser(req, res) {
try {
const user = await this.userService.createUser(req.body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ message: error.message });
}
}
async getUsers(req, res) {
try {
const users = await this.userService.getUsers(req.query);
res.json(users);
} catch (error) {
res.status(500).json({ message: error.message });
}
}
async getUserById(req, res) {
try {
const user = await this.userService.getUserById(req.params.id);
if (!user) return res.status(404).json({ message: 'Kullanıcı bulunamadı.' });
res.json(user);
} catch (error) {
res.status(500).json({ message: error.message });
}
}
async updateUser(req, res) {
try {
const user = await this.userService.updateUser(req.params.id, req.body);
if (!user) return res.status(404).json({ message: 'Kullanıcı bulunamadı.' });
res.json(user);
} catch (error) {
res.status(400).json({ message: error.message });
}
}
async deleteUser(req, res) {
try {
const user = await this.userService.deleteUser(req.params.id);
if (!user) return res.status(404).json({ message: 'Kullanıcı bulunamadı.' });
res.status(204).send(); // Başarılı silme, içerik yok
} catch (error) {
res.status(500).json({ message: error.message });
}
}
}
module.exports = UserController;src/routes/userRoutes.js:
const express = require('express');
const router = express.Router();
// Dependencies
const UserRepository = require('../repositories/UserRepository');
const UserService = require('../services/UserService');
const UserController = require('../controllers/userController');
// Instantiate dependencies
const userRepository = new UserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);
// Bind controller methods to routes
router.post('/', userController.createUser.bind(userController));
router.get('/', userController.getUsers.bind(userController));
router.get('/:id', userController.getUserById.bind(userController));
router.put('/:id', userController.updateUser.bind(userController));
router.delete('/:id', userController.deleteUser.bind(userController));
module.exports = router;Adım 8: Ana Uygulama Dosyası (app.js veya server.js)
require('dotenv').config();
const express = require('express');
const connectDB = require('./src/config/db');
const userRoutes = require('./src/routes/userRoutes');
const app = express();
// Connect to database
connectDB();
// Middleware
app.use(express.json());
// Routes
app.use('/api/users', userRoutes);
// Error handling middleware (example, could be more robust as in my previous post)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Sunucu Hatası!');
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Sunucu http://localhost:${PORT} adresinde çalışıyor`);
});Bu yapıda, UserController yalnızca UserService ile etkileşime girer, UserService ise UserRepository ile. UserRepository ise Mongoose aracılığıyla MongoDB ile konuşur. Her katman, kendi sorumluluğuna odaklanarak kodun temizliğini ve modülerliğini sağlar.
Repository Desenin Avantajları ve Zorlukları
Her tasarım deseni gibi Repository deseninin de kendine özgü avantajları ve bazı dikkat edilmesi gereken noktaları vardır:
Avantajlar
Daha İyi Test Edilebilirlik: En büyük avantajlardan biri, iş mantığınızı içeren servis katmanını veya kontrolcüleri test ederken veritabanı bağımlılığından kurtulmaktır. Repository'yi mock'layarak, hızlı ve izole birim testleri yazabilirsiniz.
Veritabanı Değişikliklerine Direnç: Uygulamanızın temel veri depolama teknolojisini (örneğin, MongoDB'den PostgreSQL'e veya hatta başka bir NoSQL veritabanına) değiştirmeniz gerektiğinde, sadece Repository uygulamasını yeniden yazmanız yeterli olur. İş mantığınızın büyük bir kısmı etkilenmez.
Merkezi Veri Erişim Mantığı: Tüm veri erişim işlemleri Repository'de toplandığı için, örneğin MongoDB'de indeksleme veya agregasyon gibi performans optimizasyonlarını veya özel sorguları tek bir yerden yönetebilirsiniz.
Bağımlılık Enjeksiyonu ile Uyum: Yukarıdaki örnekte gördüğünüz gibi, Repository deseni Bağımlılık Enjeksiyonu (DI) ile mükemmel bir şekilde entegre olur. Bu, uygulamanızın esnekliğini ve yapılandırılabilirliğini artırır.
Mikroservis Mimarileri İçin İdeal: Özellikle mikroservis mimarilerinde, her servisin kendi veri erişimini kapsüllemesi ve diğer servislerle sadece API'ler aracılığıyla konuşması önemlidir. Repository deseni, bu kapsüllemeyi kolaylaştırır.
Zorluklar
Ekstra Soyutlama Katmanı: Özellikle küçük ve basit uygulamalar için, Repository deseni başlangıçta gereksiz bir soyutlama ve kod artışına neden olabilir. Her zaman projenizin ihtiyaçlarına göre bir denge bulmak önemlidir.
Başlangıç Maliyeti: İlk kurulum ve yapılandırma, doğrudan veritabanı etkileşimine göre daha fazla zaman alabilir. Ancak bu maliyet, uzun vadede bakım ve esneklik açısından fazlasıyla karşılanır.
ORM/ODM Karmaşıklığı: Mongoose gibi bir ODM kullanırken, Repository içinde ODM'nin tüm özelliklerini (örneğin, populate, aggregate) soyutlamak bazen zorlayıcı olabilir. Bu durumda, Repository'nizin ne kadar esnek olması gerektiğine karar vermeniz gerekebilir.
İleri Konular ve Ek İpuçları
Unit of Work Deseni ile Entegrasyon: Birden fazla Repository üzerinden yapılan işlemleri tek bir veritabanı işlemi (transaction) olarak ele almak için Unit of Work desenini Repository ile birlikte kullanabilirsiniz. MongoDB Transactions ile bu yapıyı güçlendirebilirsiniz.
Generic Repository: Farklı modeller için temel CRUD (Create, Read, Update, Delete) operasyonlarını içeren generic bir Repository oluşturarak kod tekrarını daha da azaltabilirsiniz. Ancak bu, çok karmaşık sorgular için kısıtlayıcı olabilir.
Hata Yönetimi: Repository katmanındaki hataları (veritabanı bağlantı sorunları, veri geçerlilik hataları vb.) yakalamak ve iş mantığına uygun bir şekilde iletmek önemlidir. Node.js ve Express.js'te güçlü hata yönetimi prensiplerini Repository katmanında da uygulamak, uygulamanızın dayanıklılığını artırır.
Sonuç
Repository deseni, Node.js ve MongoDB ile geliştirme yaparken uygulamalarınızın veri erişim katmanını güçlendirmek, kod kalitesini artırmak ve gelecekteki değişikliklere karşı daha dirençli hale getirmek için vazgeçilmez bir araçtır. Bu deseni uygulayarak, iş mantığınızı veritabanı detaylarından soyutlar, test edilebilirliği artırır ve daha modüler bir mimari inşa edersiniz.
Başlangıçta ek bir soyutlama katmanı gibi görünse de, projenizin büyümesi ve karmaşıklaşmasıyla birlikte sağladığı faydalar bu ilk çabayı fazlasıyla haklı çıkaracaktır. Unutmayın, en iyi mimari kararlar, projenizin mevcut ihtiyaçları ve gelecekteki olası yönelimleri göz önünde bulundurularak verilir.
Eğer aklınıza takılan sorular olursa veya bu konular hakkında daha fazla bilgi almak isterseniz, bana ismailyagci371@gmail.com adresinden veya sosyal medya kanallarından ulaşabilirsiniz. Sağlıklı ve başarılı kodlamalar dilerim!
Orijinal yazı: https://ismailyagci.com/articles/nodejs-ve-mongodbde-repository-deseni-veri-erisim-katmaninizi-guclendirin
Yorumlar
Yorum Gönder