Anladığım kadarıyla buradaki mesajlar forum mesajları gibi veya yorumlar gibi olacak. Her kullanıcının attığı mesaj sayısı da fazla olacağı için bunu kullanıcı koleksiyonunda saklamak istemiyorsun sanırım. Öyleyse burada query işlemi çoğalacak. Her mesaj için tutacağın bilgilere kimden atıldığını belirten bir from bilgisi ekleyebilirsin. Mongodb NoSQL bir veritabanı olduğu için relation işlemlerinin çok olduğu durumda verimli olmayabiliyor. Ama kurgunu buna göre yaptıysan ortadaki cost da kabul edilmiş demektir. Şimdi bir users kolleksiyonu bir de messages kolleksiyonu olduğunu varsayalım. User kolleksiyonunda kullanıcı bilgileri tutulmalı yani bu kolleksiyondaki bir döküman şu şekilde olabilir :
{
_id: 601c0506d1260afc543ce21b,
username: superman,
name: clark,
surname: kent,
email: clark.kent@gmail.com
}
Burada bahsettiğin sorunun yani başkalarının bu kullanıcıya ait mesajları editlemesinin önüne geçebilmemiz için kullanının unique(eşsiz) id’sine ihtiyacımız var. Burada mongonun verdiği id’yi kullanabilirsin. Bu object id NEREDEYSE unique olur her döküman için. Bu ayrımı yapmak için daha da özel bir id’ye ihtiyacın olursa npm uuid paketini araştırabilirsin. Şimdi bir de messages koleksiyonu olmalı demiştik. Oradaki dökümanlar da şu şekilde olsun :
{
_id: 601c05d2ebaed6d9fe9b9bb5,
from: 601c0506d1260afc543ce21b, // Bu kısmın yukarıdaki id ile aynı olduğuna dikkat edelim
to : 601c063dda47ae6b913b0192, // Eğer bu mesaj birisine atılmışsa buradaki id de o user id olmalı
message : “Ne kadar güçlü olduğunu öğrenmenin tek yolu sınırlarını zorlamaya devam etmen”,
}
Artık kullanıcı dökümanında gizli bir relation oluşturmuş olduk. Bundan sonraki süreçte eğer kullanıcıdan bağımsız olarak, bir kullanıcıya ait mesajları almak istersek önce kullanıcının id’sini bilmemiz gerekiyor sonrasında o kullanıcıya ait mesajları eşleşiyor mu diye kontrol ederek bulabiliriz. Id’sini de email yardımıyla veya her kullanıcı için unique belirlediğimiz id dışında username, email gibi bir alan yardımıyla bulabiliriz. Örnek kod :
const user = await User.find({email: “clark.kent@gmail.com”}).select(“_id”); // Kullanıcıyı emailinden bulup sadece id’sini alıyoruz.
const messages = await Message.find({from:user.id}).sort({createdAt: 1}) // Kullanıcının bütün mesajlarını createdAt tarihine göre sıralayarak almış olduk. Burada bir kullanıcının, başka bir kullanıcının mesajlarını görememesi konusu authorization/authentication konusu. Yani sen bu isteği atan kullanıcının, bu mesajlara erişebilecek kullanıcı olduğundan emin olma lojiğini express tarafında yapman gerekiyor. Kullanıcıyı authenticate ettikten sonra req.email, req.userID gibi bir alanda bu bilgileri saklayıp, endpointe cevap veren fonksiyonda yine bu kullanıcıya özel id veya email ile kontrol ettikten sonra o kullanıcıya ilgili mesajları döndürebilmelisin veya edit yaptığın PUT/POST requestte bu kontrolü sağlaman gerekiyor.
Bu bir yöntemdi. Bir diğer yöntem de relation yöntemi. Bu yöntemde de mesajlar kısmında daha az bilgi verip mesajların objectID’sini kullanıcıda saklamamız gerekiyor. Yani kullanıcı alanı şu şekilde olsun :
{
_id: 601c0506d1260afc543ce21b,
username: superman,
name: clark,
surname: kent,
email: clark.kent@gmail.com,
messages : [‘601c09f5e6d54bec2d6b4923’, ‘601c09fbe1e073b144f771d4’]
}
Burada messages kısmında mongo object id’leri saklıyoruz. Message kısmındaki iki döküman da şu şekilde olabilir :
{
_id: 601c09f5e6d54bec2d6b4923, //Bu kısım arrayde saklanan object id
message : “Ne kadar güçlü olduğunu öğrenmenin tek yolu sınırlarını zorlamaya devam etmen”,
}
{
_id: ‘601c09fbe1e073b144f771d4’]//Bu kısım arrayde saklanan object id
message : “You Will Be Different, Sometimes You’ll Feel Like An Outcast, But You’ll Never Be Alone.”,
}
Bu durumda da aslında bir relation hazırlamış oluyorsun. Böylece mongodb senin user tarafında verdiğin messages kısmındaki alanları (şemada belirtmeyi unutma şema örneğini de kod olarak yazacağım) birer referans olduğunu anlıyor ve populate gibi metodu kullanarak bu mesajlara doğrudan user üzerinden erişebiliyorsun. Ama bu noktada eğer bahsettiğin gibi mesajın hangi kullanıcı tarafından atıldığını bulman gerekirse query maliyetin daha da büyüyecek. Diyeceksin ki bu mesajın id’sini içeren kullanıcıları bana getir. Bu da bütün kullanıcılar arasında search yapmak anlamına geliyor.
Bu iki Yöntemi birleştirip hibrit bir yaklaşımla hem kullanıcı tarafında mesajın id’sini hem de mesaj tarafında mesajı atan kullanıcının id’sini tutabilirsin. O zaman da yaptığın işin maliyeti memorye yansır çünkü duplicate bir veri tutuyorsun ve bu duplicate veriyi NoSQL bir db’de tutuyorsun. Yani olduğu gibi verinin maliyetini artırmış olacaksın.
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/playground')
.then(() => console.log('Connected to MongoDB...'))
.catch(err => console.error('Could not connect to MongoDB...', err));
const User = mongoose.model('User', new mongoose.Schema({
name: String,
username: String,
email: String,
surname: String,
messages : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Message'
}]
}));
const Messages = mongoose.model('Message', new mongoose.Schema({
message: String,
from: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}));
Yukarıdaki kısımda bağlantımızı yaptık ve şemalarımızı oluşturduk. Ancak kullanıcı şemasını oluştururken messages isminde bir array verdiğimizi ve bu array’in objesinin elemanlarının tipinin bir object id olduğuna dikkat etmemiz gerekiyor. Aynı şekilde mesajda da from kısmı bir obje id’si olarak tutuluyor. Şimdi yavaş yavaş fonksiyonlarımıza gelelim :
async function createUser(name, username, email, surname) {
const messages = [];
const user = new User({
name,
username,
email,
surname,
messages
});
const result = await user.save();
console.log(result);
}
Burada bir kullanıcı oluşturma kodu hazırladık. Bu test amaçlı bir kullanıcı oluşturabileceğiz.
async function createMessage(message,from) {
const msg = new Messages({
message,
from
});
const result = await msg.save();
//Kullanıcının mesajlar listesine mesajı ekliyoruz bunu unutursak referansı otomatik eklemez ve kullanıcı tarafında sonra erişmemiz zor olacak.
const usr = await User.findByIdAndUpdate({_id : from}, {
//https://docs.mongodb.com/manual/reference/operator/update/ buradaki dökümana gidip desteklenen bütün güncelleme operatörlerini görebiliriz.
// $currentDate , $inc , $rename, $set $unset...
$push : {messages : msg}
}, {new : true}); // Burada new true diyerek yeni dökümanı döndürmesini sağlıyoruz. Eğer bunu eklemezsek değişmemiş dökümanı döndürür.
console.log(result);
}
Bir mesaj oluşturma fonksiyonu. Bu noktada da dikkat etmemiz gereken unsur şu : mesajı oluşturduktan sonra bu mesajı kullanıcının mesaj listesine push etmemiz gerekiyor ki çift taraflı erişebilelim.
async function listMessageFromUser(id) {
const userMessages = await User
.findById({_id: id}) // Kullanıcıya ait mesajları görmek istiyoruz.
.populate('messages')
.select('message');
console.log(userMessages);
}
async function listUserFromMessage(id) {
const msg = await Messages
.findById({_id: id}) // Kullanıcıya ait mesajları görmek istiyoruz.
.populate('from')
.select('username');
console.log(msg);
}
Burada kullandığımız populate fonksiyonları yardımıyla mongoose’a direkt olarak bu referansa sahip objeyi getir diyebiliyoruz. Yani kullanıcı altında messages kısmında tuttuğumuz referansları doğrudan obje şeklinde alabileceğiz. Aynı şekilde bu ilişki yardımıyla mesajın kullanıcısına da erişimimiz olacak. Şimdi sırasıyla çağıralım :
// 1) Create User
createUser("Clark","Superman","clark.kent@gmail.com","kent");

Kullanıcının oluşturulduğu yanıtını aldık. Şimdi bir sonraki mesaj oluşturma fonksiyonumuzu çağırabiliriz :
// 2) Add Message
createMessage("Ne kadar güçlü olduğunu öğrenmenin tek yolu sınırlarını zorlamaya devam etmen","601c0ea955c6b541944f5398");
createMessage("You Will Be Different, Sometimes You’ll Feel Like An Outcast, But You’ll Never Be Alone.","601c0ea955c6b541944f5398");

Bununla ilgili de olumlu dönüş aldık. MongoDB compass yardımıyla baktığımızda da iki tarafta da kayıt edildiğini görüyoruz. Renklerle kontrol ederek referansların doğru olduğunu da görebiliriz:

Şimdi de kullanıcının bütün mesajlarına referans yöntemiyle erişip bastıralım :
// 3) List Message
listMessageFromUser("601c0ea955c6b541944f5398");

Mesajlarımız istediğimiz gibi gelmiş oldu. Son olarak mesajdaki referans yardımıyla da kullanıcının bilgilerine erişelim :
// 4) Get User
listUserFromMessage("601c12304cf4cc4854a584b7")

Bu bilgilere de erişmiş olduk.
Burada son olarak dikkat etmemiz gereken konu ise transaction konusu. Bu mongodb’de olmadığı için mesaj oluştururken önce mesajı oluşturduk sonra başka bir query ile kullanıcıyı güncelledik. Bu zincirin bölünmediğinden emin olmak için two phase commit mantığı kullanabiliriz. Yoksa mesaj kaydolduktan sonra bağlantı kopabilir ve kullanıcı tarafına mesajı kaydetmekte sorun yaşayabiliriz.
Kodların tamamı da şu şekilde :
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/playground')
.then(() => console.log('Connected to MongoDB...'))
.catch(err => console.error('Could not connect to MongoDB...', err));
const User = mongoose.model('User', new mongoose.Schema({
name: String,
username: String,
email: String,
surname: String,
messages : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Message'
}]
}));
const Messages = mongoose.model('Message', new mongoose.Schema({
message: String,
from: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}));
async function createUser(name, username, email, surname) {
const messages = [];
const user = new User({
name,
username,
email,
surname,
messages
});
const result = await user.save();
console.log(result);
}
async function createMessage(message,from) {
const msg = new Messages({
message,
from
});
const result = await msg.save();
//Kullanıcının mesajlar listesine mesajı ekliyoruz bunu unutursak referansı otomatik eklemez ve kullanıcı tarafında sonra erişmemiz zor olacak.
const usr = await User.findByIdAndUpdate({_id : from}, {
//https://docs.mongodb.com/manual/reference/operator/update/ buradaki dökümana gidip desteklenen bütün güncelleme operatörlerini görebiliriz.
// $currentDate , $inc , $rename, $set $unset...
$push : {messages : msg}
}, {new : true}); // Burada new true diyerek yeni dökümanı döndürmesini sağlıyoruz. Eğer bunu eklemezsek değişmemiş dökümanı döndürür.
console.log(result);
}
async function listMessageFromUser(id) {
const userMessages = await User
.findById({_id: id}) // Kullanıcıya ait mesajları görmek istiyoruz.
.populate('messages')
.select('message');
console.log(userMessages);
}
async function listUserFromMessage(id) {
const msg = await Messages
.findById({_id: id}) // Kullanıcıya ait mesajları görmek istiyoruz.
.populate('from')
.select('username');
console.log(msg);
}
// 1) Create User
//createUser("Clark","Superman","clark.kent@gmail.com","kent"); // Clark Kent kullanıcısı oluşturuldu id'si 601c0ea955c6b541944f5398
//Şimdi mesaj ekleyelim 601c0ea955c6b541944f5398
// 2) Add Message
//createMessage("Ne kadar güçlü olduğunu öğrenmenin tek yolu sınırlarını zorlamaya devam etmen","601c0ea955c6b541944f5398");
//createMessage("You Will Be Different, Sometimes You’ll Feel Like An Outcast, But You’ll Never Be Alone.","601c0ea955c6b541944f5398");
// 3) List Message
//listMessageFromUser("601c0ea955c6b541944f5398");
// 4) Get User
listUserFromMessage("601c12304cf4cc4854a584b7") // Burada da mesajın id'sini girdiğimize dikkat etmemiz gerekiyor. Id yoluyla mesaja ulaşıp onun altında kullanıcı referansını populate ediyoruz.