Farklı Veritabanı Yaklaşımlarının İncelenmesi: Couchbase ile NoSQL Demo Çalışması

Kardel Rüveyda ÇETİN
32 min readAug 4, 2024

--

Photo by Aaron Burden on Unsplash

Bu yazıda, ekibimizin üzerinde düşündüğü bir konuyu detaylandıracağız: PostgreSQL, Couchbase ve MongoDB veri tabanlarının hangisinin bizim quiz uygulamamız için en uygun olduğunu belirlemeye çalışıyoruz.

Hikayemiz şöyle başladı: Ekibimiz, kullanıcıların quizler oluşturabileceği, soruları yönetebileceği ve sonuçları takip edebileceği kapsamlı bir uygulama geliştirmeyi planladı. Ancak, hangi veri tabanını kullanacağımıza karar vermekte zorlanıyoruz. Bu yüzden, bu yazıyı yazarak her bir veri tabanının güçlü yönlerini ve olası zorluklarını anlamayı hedefliyoruz. Her şeyi sıfırdan yaptığımız için öğrenme süreçleri konusundan proje bizlere harika bir rehber oluyor.

Uygulamamız, kullanıcı bilgilerini, oluşturulan quizleri, her bir quizin içindeki soruları ve bu sorulara verilen cevapları yönetmeyi amaçlıyor. JSON örneğinde görülebileceği gibi, her veri parçası; kullanıcıların kişisel bilgilerini, quiz başlıklarını, soruların detaylarını ve seçeneklerini içeriyor.

Bu süreçte, PostgreSQL’in sağlam veri bütünlüğü ve geniş ölçeklenebilirlik olanaklarını, Couchbase’in yüksek performans ve esnek yapılarını ve MongoDB’nin esnek veri yönetimi özelliklerini karşılaştırmaya çalışıyoruz. PostgreSQL ve MongoDB özelinde daha önce deneyimlerim olmuştu. Fakat Couchbase tarafında daha önce bir çalışma gerçekleştirmemiştim. Bu yazı ile birlikte bu uygulamanın veri tabanı yapısını PostgreSQL de yapsaydık nasıl olurdu, MongDB de yapsaydık nasıl olurdu ve Couchbase özelinde yapsaydık nasıl olurdu onları incelemeye çalıştım. Ancak Couchbase tarafında elimi birazcık da kirletip .NET Core ile bağlantısnı sağlayarak bir Demo da oluşturdum.

Gün sonunda ekiple birlikte hangi veri tabanının bizim ihtiyaçlarımıza en iyi şekilde cevap vereceğini belirleyeceğiz. Umuyorum bu makale de bu seçimi yapmamızda kolaylık sağlayabilir. :)

Geliştirmeyi hedeflediğimiz uygulama, kullanıcıların detaylı bilgilerini ve oluşturdukları sunumları kapsamlı bir şekilde saklamayı amaçlamaktadır.

  • Kullanıcı Bilgileri: Her kullanıcı için benzersiz bir kimlik (id), ad (firstName), soyad (lastName), e-posta adresi (emailAddress), telefon numarası (phoneNumber), sistem kayıt tarihi (createdAt), son güncelleme tarihi (updatedAt), aktiflik durumu (isActive) ve Clerk ID (clerkId) saklanır. Clerk Id olarak tanımladığımız alan kullanıcı kimlik ve yetkilendirme tarafında Clerk’ten faydalanacağız. O nedenle buradan gelebilecek spesifik bir ID’yi de kendi veri tabanımızda tutmalıyız diye düşündük.
  • Sunumlar: Her sunum için bir kimlik (id), başlık (title), sıra numarası (order), oluşturulma ve güncellenme tarihleri (createdAt, updatedAt) ile aktiflik durumu (isActive) bulunur.
  • Slaytlar: Her sunumun içindeki slaytlar için kimlik (id), şablon kimliği (templateId), sıra numarası (order), oluşturulma ve güncellenme tarihleri (createdAt, updatedAt) ve aktiflik durumu (isActive) kayıt edilir.
  • Sorular: Slaytlar içinde bulunan sorular için kimlik (id), soru metni (text), puan (score), oluşturulma ve güncellenme tarihleri (createdAt, updatedAt) ve aktiflik durumu (isActive) tutulur.
  • Seçenekler: Her soruya bağlı seçenekler için kimlik (id), seçenek metni (optionText), sıra numarası (order), oluşturulma ve güncellenme tarihleri (createdAt, updatedAt) ve aktiflik durumu (isActive) saklanır.

Bu uygulamanın ana özellikleri, kullanıcıların detaylı bilgilerini yönetme, sunum oluşturma ve düzenleme, slayt ve soru içeriklerini kapsamlı bir şekilde yönetme, aktiflik durumlarını belirleme ve verilerin zaman damgasını tutma gibi işlevleri içermektedir. O halde haydi başlayalım!

Konu Başlıkları

  • İlişkisel Veri Tabanları(Yapılandırılmış Veri Yönetimi,SQL (Structured Query Language),ACID Özellikleri,PostgreSQL)
  • NoSQL Veri Tabanları ( Temel Özellikler ve Kullanım Alanları, NoSQL’in BASE Özellikleri, Couchbase, MongoDB
  • Peki üçünü kıyaslayacak olursam ne derim ?(Postgre,MongoDB,Couchbase)
  • Postgre kullansak yapımız nasıl olabilir?
  • MongoDB kullanırsak yapımız nasıl olabilir?
  • Couchbase’yı deneyelim!
  • Coucbase Capebella
  • Couchbase’yi denemek için .NET Core Tarafında Uygulama
  • Olumlu Yönlerim
  • Geliştirilebilecek Alanlarım
  • Kaynakça

Öncelikle biraz üzerinde çalışmak istediğimiz veri tabanlarının özelliklerini tartışalım derim. Veritabanı sistemleri genellikle iki ana kategoriye ayrılır: İlişkisel Veritabanı Yönetim Sistemleri (RDBMS) ve NoSQL veritabanları. RDBMS, verileri tablolar halinde organize eden ve SQL dili ile sorgulama yapılabilen sistemlerdir. NoSQL ise “not only SQL” veya “non-SQL” olarak adlandırılır ve genellikle esneklik ve ölçeklenebilirlik avantajları sunar.

İlişkisel Veri Tabanları

İlişkisel veri tabanları, verileri önceden tanımlanmış şemalarla tablolarda organize eden ve bu tablolar arasındaki ilişkileri yöneten yapılandırılmış veri yönetim sistemleridir. Bu sistemlerde, veriler tablolar (tables) olarak adlandırılan iki boyutlu yapılar içinde saklanır. Her tablo, satırlar (rows) ve sütunlar (columns) olarak düzenlenir.

Yapılandırılmış Veri Yönetimi

İlişkisel veri tabanları, verilerin nasıl saklanacağını, hangi türde veriler olduğunu ve veriler arasındaki ilişkileri tanımlayan şemalar kullanır. Bu şemalar, veritabanındaki her tablonun yapısını belirler ve veri tutarlılığını sağlar. Veri tabanında yapılan her değişiklik bu şemalara uygun olmalıdır.

SQL (Structured Query Language)

SQL, ilişkisel veri tabanları ile etkileşimde bulunmak için kullanılan standart sorgulama dilidir. SQL, veri tabanından veri almak (SELECT), veri eklemek (INSERT), veri güncellemek (UPDATE) ve veri silmek (DELETE) gibi işlemleri gerçekleştirir. SQL, aynı zamanda veri tabanı şemalarının oluşturulması ve yönetilmesi için de kullanılır.

ACID Özellikleri

İlişkisel veri tabanları, veri tutarlılığı ve bütünlüğünü sağlamak için ACID (Atomicity, Consistency, Isolation, Durability) özelliklerini destekler.

  • Atomicity (Atomiklik): Bir işlem ya tamamen gerçekleştirilir ya da hiç gerçekleştirilmez. Bu, kısmi işlemler nedeniyle veri tutarsızlıklarının önlenmesini sağlar.
  • Consistency (Tutarlılık): Her işlem, veri tabanını tutarlı bir duruma getirir. Veri tabanı, tanımlanan kurallara ve kısıtlamalara (constraints) uygun olmalıdır.
  • Isolation (Yalıtım): Bir işlem, diğer işlemlerden bağımsız olarak gerçekleştirilir. Bu, işlemler arasında veri tutarsızlıklarının önlenmesini sağlar.
  • Durability (Kalıcılık): Bir işlem tamamlandığında, sonuçları kalıcı olarak saklanır. Sistem arızaları veya hatalar durumunda bile veriler kaybolmaz.

PostgreSQL

PostgreSQL, açık kaynaklı ve güçlü bir ilişkisel veri tabanıdır. Yüksek performans, geniş ölçeklenebilirlik ve veri bütünlüğü sağlama konusundaki yetenekleri ile bilinir. PostgreSQL’in öne çıkan özellikleri şunlardır:

  • Geniş Veri Türü Desteği: PostgreSQL, birçok yerleşik veri türünü (integer, text, boolean vb.) ve kullanıcı tanımlı veri türlerini destekler. Ayrıca, JSON veri türünü destekleyerek hem ilişkisel hem de belge tabanlı veri yapılarını işleyebilir.
  • İleri Düzey Sorgulama Yetenekleri: PostgreSQL, karmaşık sorguları ve alt sorguları destekleyen gelişmiş sorgulama yeteneklerine sahiptir. Aynı zamanda, dizinleme (indexing), tam metin arama (full-text search) ve coğrafi veri işleme (geospatial data) gibi özellikleri de sunar.
  • Genişletilebilirlik: PostgreSQL, kullanıcı tanımlı işlevler (user-defined functions), prosedürler (stored procedures) ve uzantılar (extensions) ile genişletilebilir. Bu, veri tabanının fonksiyonelliğini artırır ve özelleştirilebilirlik sağlar.
  • Veri Bütünlüğü ve Kısıtlamalar: PostgreSQL, veri tutarlılığını sağlamak için birçok kısıtlama (constraints) türünü destekler. Bunlar arasında birincil anahtarlar (primary keys), yabancı anahtarlar (foreign keys), benzersiz kısıtlamalar (unique constraints) ve kontrol kısıtlamaları (check constraints) bulunur.
  • Yüksek Performans ve Ölçeklenebilirlik: PostgreSQL, yüksek hacimli veri işlemleri ve büyük veri tabanı uygulamaları için optimize edilmiştir. Paralel işleme (parallel processing) ve çok çekirdekli işlemcilerden (multi-core processors) yararlanarak performansı artırır.

İlişkisel veri tabanları, yapısal ve tutarlı veri yönetimi gerektiren uygulamalar için ideal bir seçim olabilir. PostgreSQL gibi güçlü bir ilişkisel veri tabanı, geniş veri türü desteği, ileri düzey sorgulama yetenekleri ve yüksek performansıyla birçok kurumsal ve endüstriyel uygulamada yaygın olarak kullanılmaktadır.

NoSQL Veri Tabanları

NoSQL veri tabanları, yapılandırılmamış veya yarı yapılandırılmış verileri esnek bir şekilde saklamak ve yönetmek için tasarlanmıştır. Geleneksel ilişkisel veri tabanlarının aksine, NoSQL veri tabanları şema gerektirmez ve çeşitli veri modellerini destekler. Bu esneklik, dinamik ve hızla değişen veri gereksinimlerine uyum sağlamayı kolaylaştırır.

Temel Özellikler ve Kullanım Alanları

  • Şemasız Veri Yapıları: NoSQL veri tabanları, önceden tanımlanmış bir şemaya ihtiyaç duymadan veri saklar. Bu, veri yapısının değişken ve dinamik olduğu durumlarda büyük bir avantaj sağlar.
  • Yatay Ölçeklenebilirlik: NoSQL veri tabanları, büyük veri setlerini yönetmek için yatay ölçeklenebilirlik sağlar. Yeni düğümler ekleyerek veri tabanını genişletmek mümkündür, bu da performansın ve kapasitenin artmasını sağlar.
  • Yüksek Performans: NoSQL veri tabanları, büyük veri setlerinde yüksek okuma ve yazma performansı sunar. Bu, özellikle düşük gecikme süresi ve yüksek işlem hacmi gerektiren uygulamalar için idealdir.
  • Çeşitli Veri Modelleri: NoSQL veri tabanları, belge tabanlı, anahtar-değer tabanlı, sütun tabanlı ve grafik tabanlı veri modelleri gibi çeşitli veri yapılarını destekler.

NoSQL’in BASE Özellikleri

NoSQL veri tabanları, ACID özelliklerinden ziyade BASE (Basically Available, Soft state, Eventual consistency) özelliklerine odaklanır:

  • Basically Available (Temel Olarak Mevcut): Sistem, ağ bölünmeleri veya sunucu hataları gibi durumlarda bile hizmet vermeye devam eder.
  • Soft state (Yumuşak Durum): Verilerin birden fazla kopyası olabilir ve bu kopyalar zamanla tutarlı hale gelir.
  • Eventual consistency (Sonunda Tutarlılık): Verilerin kopyaları başlangıçta tutarsız olabilir, ancak belirli bir süre sonra tüm kopyalar tutarlı hale gelir.

Couchbase

Couchbase, hem belge tabanlı (JSON) hem de anahtar-değer tabanlı veri yapılarını destekleyen yüksek performanslı bir NoSQL veri tabanıdır.Couchbase, NoSQL kategorisinde yer alan ve “memory-first” mimarisi ile çalışan bir veritabanı sistemidir. Bu mimari, verilerin önce hafızada işlenmesini sağlayarak disk tabanlı sistemlere kıyasla çok daha hızlı yanıt süreleri sunar. Couchbase, veri parçalarını CRC32 hash algoritması kullanarak 1024 (vBucket) içinde saklar ve bu bucketlar kümeye dağıtılmış olarak yönetilir.

Couchbase, klasik SQL dilini destekleyen N1QL sorgulama hizmeti ile diğer NoSQL veritabanlarından ayrılır. N1QL sayesinde kullanıcılar, Couchbase üzerinde SQL dilini kullanarak veri manipülasyonu yapabilirler. Bu özellik, RDBMS yöneticileri ve yazılım geliştiricileri için büyük bir avantajdır, çünkü yeni bir sorgulama dili öğrenme gereksinimini ortadan kaldırır.

Couchbase, varsayılan olarak asenkron mimari ile çalışır. İşlemler önce “memory buffer(hafıza tamponu)”nda yapılır ve burada yapıldığında müşteriye bir “commit” gönderilir. Diğer tüm işlemler iç seviyede asenkron olarak yapılır. Bu sayede, Couchbase’in okuma ve yazma hızları mükemmeldir. Ayrıca kullanıcı dostu bir web konsolu bulunur. Bu web konsolu hem yönetim hem de uygulama geliştirme için kullanılabilir. Web üzerinde yapılabilecek her şeyi komut satırı “CLI” kullanarak da yapmak mümkündür.

Couchbase’in bir diğer önemli özelliği XDCR, yani “Cross Data Center Replication” hizmetidir. Bu hizmet sayesinde iki farklı Couchbase kümesini tek yönlü veya çift yönlü olarak senkronize edebiliriz. Bu özellik, yazma ve okuma işlemleri için kullanılabilir ve veritabanı dünyasında önemli bir rol oynar. XDCR, yedeklilik sağlamak, bağımsız konumlarda verileri senkronize etmek ve farklı konumlarda yazma ve okuma işlemleri yapmak için kullanılır.

Couchbase’in sevilen özelliklerinden biri de N1QL sorgulama dilidir. RDBMS yöneticileri ve yazılım geliştiricileri genellikle SQL diline hakimdir. SQL, eski ve yaygın bir dildir, ancak NoSQL veritabanı sistemlerinin genellikle kendine özgü ve öğrenilmesi zor dilleri vardır. Couchbase, klasik SQL dilini destekler, bu da sorgulama işlemlerini daha kolay hale getirir. Örneğin, MongoDB’de uzun ve karmaşık yazılması gereken sorgular, Couchbase’de klasik SQL ile daha kısa ve anlaşılır şekilde yazılabilir. Bu sorgulama hizmeti, yazılım geliştiriciler ve veritabanı yöneticileri için büyük bir avantaj sağlar ve uygulamaların esnekliğini ve hızını artırır.

İlişkisel veritabanında “sunucu” olarak adlandırılan kavram, Couchbase’de “küme” olarak geçer. Bir küme, tek bir sunucudan veya birden fazla sunucudan oluşabilir, ancak bağımsız bir örnek kurulumu bulunmaz. RDBMS’de “veritabanı” olarak adlandırılan kavram, Couchbase’de “bucket” olarak bilinir. Şema, Couchbase’de “scope” olarak adlandırılırken, tablo “koleksiyon” olarak geçer. Satır ise Couchbase ve diğer NoSQL veritabanlarında “belge” olarak adlandırılır.

Couchbase, verileri parçalara ayırarak saklar. Veriler, CRC32 hash algoritması kullanılarak bölünür ve “vBucket” adı verilen sanal kovalar içinde saklanır. Toplamda 1024 vBucket vardır ve bu vBucket’lar kümeye dağıtılmış şekilde saklanır.

Kısaca N1QL hizmetinden bahsetmek gerekirse, N1QL, Couchbase üzerinde klasik SQL dilini (ANSI) kullanarak verileri manipüle edebileceğiniz bir hizmettir. Sorguların daha hızlı çalışması için indeksleme hizmeti de bulunur. N1QL sorguları için indeksler eklenebilir (RDBMS’deki btree indeksler gibi) ve anahtar-değer kullanmadan bile iyi performans elde edilebilir.

Veritabanı yöneticileri için yedekleme ve geri yükleme kritik bir özelliktir. Couchbase 7.0 ile birlikte gelen dahili backup(yedekleme hizmeti) sayesinde bu işlemler arayüz, CLI veya API üzerinden kolayca gerçekleştirilebilir. Couchbase’in güçlü dokümantasyon sitesi, her modülün açıklamasını ve örneklerini içerir, bu da kullanıcıların sistemi daha verimli kullanmasını sağlar.

Couchbase bir NoSQL veritabanı çözümü olmasına rağmen ACID işlemlerini destekler. Bu sayede, örneğin bir EFT işlemi sırasında hata meydana geldiğinde tüm işlemleri geri alabiliriz. Couchbase’in “masterless” mimarisi sayesinde tüm ortamlardan ve fiziksel konumlardan okuma ve yazma işlemleri gerçekleştirilebilir.

Couchbase’in “server group(sunucu grubu)” mimarisi, veritabanının birden fazla fiziksel ortamda yedekli olarak çalıştırılmasına olanak tanır. Bu sayede, bir ortam tamamen kaybedilse bile diğer konumdaki tüm veriler kullanılmaya devam edilebilir, bu da kesinti yaşamadan hizmet sunulmasını garanti eder.

XDCR (Cross Data Center Replication) özelliği, verilerin başka bir konuma yedeklenmesini sağlar. Bu özellik, raporlama ortamını üretim ortamından ayırmak veya iki farklı konumda hem yazma hem de okuma işlemleri için kullanılabilir. Bu da Couchbase’in esnek ve güçlü bir veritabanı çözümü olduğunu gösterir.

Couchbase’in API desteği, veritabanı işlemlerinin API’ler aracılığıyla gerçekleştirilmesine olanak tanır. API referansları doküman sayfasında mevcuttur ve CLI’yı kullanarak da işlemler yapılabilir.

Yazılım geliştiriciler, Couchbase SDK’ları ile uygulama geliştirebilirler. Couchbase, verileri parçalara ayırarak tüm düğümlere dağıtır ve bu verilerin kopyalarını başka sunucularda saklar. Bu özellik, sunucu kapanmaları durumunda veri kaybını önler. Arayüzde kopya sayısını ayarlamak mümkündür ve kritik veritabanı sistemleri için bu sayıyı artırmak önemlidir. Varsayılan olarak bir olan kopya sayısı, genellikle iki veya üçe kadar artırılabilir. Bu durumda, bir sunucu kaybedildiğinde başka bir sunucu devreye girer ve veri kaybı yaşanmaz. Couchbase’in “memory-first” mimarisi, verilerin büyük çoğunluğunun hafızada saklanmasını sağlar. Bu mimari, diskten veri okumaya kıyasla çok daha yüksek performans sunar. Verilerin hafızada olmaması durumunda, diskten hafızaya çıkarılması performansı olumsuz etkileyebilir. Bu nedenle, veritabanı sistemleriyle çalışırken yeterli hafızaya sahip olmak kritik öneme sahiptir ve esnek yapılandırma ve performans optimizasyonu için geniş bir konfigürasyon yelpazesi sunar. Her sunucu, veri taşıma miktarı ve hafıza kullanımı gibi parametrelerle yapılandırılabilir. Bu özellikler, büyük uygulamalarda yük dengesi ve ölçeklendirme gereksinimlerini karşılamak için önemlidir. Kullanıcı dostu arayüzü ve kapsamlı dokümantasyonu, yönetim ve geliştirme süreçlerini kolaylaştırır.

  • Belge Tabanlı Veri Modeli: Couchbase, verileri JSON formatında saklar ve bu format sayesinde karmaşık ve ilişkisel olmayan veri yapıları kolayca yönetilir.
  • Yatay Ölçeklenebilirlik ve Dağıtık Mimari: Couchbase, yeni düğümler eklenerek kolayca ölçeklenebilir ve verileri dağıtık bir mimari ile yönetir.
  • N1QL ve Mutasyon Operasyonları: Couchbase, SQL benzeri bir sorgulama dili olan N1QL kullanarak veri sorgulama ve manipülasyonu sağlar.
  • Performans: Couchbase, düşük gecikme süresi ve yüksek işlem hacmi gerektiren uygulamalar için optimize edilmiştir.
  • Veri Tutarlılığı: Couchbase, veri tutarlılığını sağlamak için çeşitli mekanizmalar sunar.
  • Full-Text Search ve Analiz: Couchbase, metin arama ve veri analizini destekler.
  • Güvenlik: Couchbase, rol tabanlı erişim kontrolü (RBAC) ve veri şifreleme özellikleri ile yüksek güvenlik sağlar.
  • Yönetim ve İzleme Araçları: Couchbase, veri tabanı yönetimi ve izleme için kapsamlı araçlar sunar.

Couchbase

MongoDB, belge tabanlı bir NoSQL veri tabanıdır ve verileri esnek, şemasız JSON benzeri belgeler (BSON) olarak saklar.

  • Esnek Veri Modelleme: MongoDB, şemasız bir yapıya sahip olduğundan, veri modelleri dinamik olarak değiştirilebilir.
  • Güçlü Sorgulama ve İndeksleme: MongoDB, gelişmiş sorgulama ve indeksleme yetenekleri sunar, bu da veri yönetimini kolaylaştırır.
  • Yatay Ölçeklenebilirlik: MongoDB, yatay ölçeklenebilirliği ile büyük veri setlerini yönetmek için idealdir.
  • Agregasyon Çerçevesi: MongoDB, veri işleme ve analiz için güçlü bir agregasyon çerçevesi sunar.
  • Replikasyon ve Yüksek Erişilebilirlik: MongoDB, verilerin birden fazla kopyasını saklayarak yüksek erişilebilirlik sağlar.
  • Güvenlik: MongoDB, rol tabanlı erişim kontrolü ve veri şifreleme özellikleri ile yüksek güvenlik sağlar.

MongoDB ve Couchbase, NoSQL veri tabanı pazarında güçlü seçenekler sunar, ancak kullanım senaryolarına bağlı olarak farklı avantajlar ve özellikler sağlarlar. MongoDB, belge tabanlı bir veri tabanı olarak esnek bir veri modellemesi sunar; şemasız JSON benzeri belgeler kullanarak veri yapılarını dinamik olarak değiştirebilirsiniz. Bu, geliştiricilere hızlı prototipleme ve uygulama geliştirme sürecinde büyük bir esneklik sağlar. MongoDB’nin güçlü sorgulama ve indeksleme yetenekleri, karmaşık veri sorgularını ve analizlerini destekler. Ayrıca, MongoDB’nin güçlü bir agregasyon çerçevesi vardır ve yatay ölçeklenebilirlik özellikleri ile büyük veri setlerini yönetmek için uygundur.

Öte yandan, Couchbase, hem belge tabanlı (JSON) hem de anahtar-değer tabanlı veri yapılarını destekleyen bir NoSQL veri tabanıdır. Couchbase, özellikle yüksek performans ve düşük gecikme süreleri gerektiren uygulamalar için optimize edilmiştir. Yatay ölçeklenebilirliği sayesinde veri tabanını genişletmek kolaydır ve dağıtık mimarisi, verileri birden fazla düğüm üzerinde yöneterek yüksek erişilebilirlik sağlar. Couchbase’in N1QL sorgulama dili, SQL benzeri bir sorgulama deneyimi sunarak geliştiricilerin verileri sorgulamasını ve manipüle etmesini basit hale getirir. Ayrıca, Couchbase, veri tutarlılığı, replikasyon ve veri şifreleme gibi güçlü güvenlik özelliklerine sahiptir.

Eğer projeniz esneklik ve güçlü sorgulama yetenekleri gerektiriyorsa, MongoDB mükemmel bir seçenek olabilir. Ancak, yüksek performans, düşük gecikme süreleri ve dağıtık veri yönetimi önemliyse, Couchbase daha uygun bir tercih olabilir gibi görünüyor.

Peki üçünü kıyaslayacak olursam ne derim ?

Bu uygulama için en uygun veri tabanı yönetim sistemi, veri yapısına ve gereksinimlere bağlı olarak değişebilir diye düşünüyorum. Uygulamamız kullanıcı bilgileri, sunumlar, slaytlar ve sorular gibi hiyerarşik ve karmaşık veri yapıları içeriyor. PostgreSQL, ACID uyumluluğu sayesinde veri bütünlüğünü garanti altına alırken, SQL desteği ile karmaşık sorguların kolayca yazılmasını sağlar ve kullanıcı bilgileri, sunumlar ve slaytlar gibi ilişkisel veriler için uygundur. Ancak, şema değişiklikleri zor ve zaman alıcı olabilir ve yatay ölçeklenebilirliği sınırlıdır, yani veri tabanını genişletmek için mevcut sunucuya ek kaynak eklemek yerine, veriyi birden fazla sunucuya dağıtmak zor olabilir.

MongoDB, şemasız yapısı sayesinde esnek veri modellemesi sağlar ve veri yapıları dinamik olarak değiştirilebilir. Yatay ölçeklenebilirliği yüksek olup, büyük veri setleri için uygundur. JSON benzeri belgeler ile karmaşık veri yapıları rahatça yönetilebilir. Ancak, veri tutarlılığı ve karmaşık işlemler için daha az güvenilir olabilir. MongoDB’nin eventual consistency (nihai tutarlılık) modelini kullanması, verilerin anında senkronize edilmediği anlamına gelir. Bu model, verilerin tüm kopyalarının zamanla tutarlı hale gelmesini garanti eder, ancak anında değil. Yani, bir veri güncellenirken, bu güncelleme tüm sistemin her yerine hemen ulaşmayabilir. Veritabanı birden fazla sunucu veya düğüm üzerinde çalıştığında, her düğümün veriyi güncellemesi ve senkronize etmesi zaman alabilir. Örneğin, bir e-ticaret sitesinde bir ürünün stok miktarını güncellediğinizde, bu güncelleme anında tüm kullanıcılara yansımayabilir. Bir kullanıcı ürünü satın alırken, stok bilgisi güncel olmayabilir ve bu kullanıcı hala eski stok bilgilerini görebilir. Yani, başka bir kullanıcı bu ürünü almışsa, güncellenmiş stok bilgisi henüz görünmeyebilir. Bu, verinin anında tutarlı olmasını sağlamaz. Ancak, sistem nihai olarak güncelleme işlemini tüm sunuculara yayar ve verinin zamanla tutarlı hale gelmesini sağlar. Bu modelin avantajı, yüksek performans ve ölçeklenebilirlik( şu anda bu özelliğe ihtiyacımız var mı? bunu tartışabiliriz ekiple) sunmasıdır çünkü verilerin anında senkronize edilmesi gerekmemektedir. Ancak, dezavantajı, anlık veri tutarlılığı gerektiren uygulamalar için sorun yaratabilir. Özellikle finansal işlemler veya stok yönetimi gibi kritik durumlarda, verinin her yerde eş zamanlı olarak güncellenmesi gereken senaryolarda daha az güvenilir olabilir.

Couchbase, hem belge tabanlı hem de anahtar-değer tabanlı veri yapılarını destekleyerek esnek veri modelleme sunar ve yüksek performans ile düşük gecikme süreleri sağlar. Dağıtık mimarisi sayesinde, verileri birden fazla düğüm üzerinde yöneterek yüksek erişilebilirlik ve yatay ölçeklenebilirlik sunar. SQL benzeri N1QL sorgulama dili, veri manipülasyonunu ve sorgulamayı kolaylaştırır. Ancak, Couchbase’in kurulumu ve yönetimi daha karmaşık olabilir; bu süreçler, genellikle daha fazla teknik bilgi ve kaynak gerektirir. Şema esnekliği, MongoDB kadar geniş olmasa da, performans ve veri erişiminde sunduğu avantajlarla tercih edilebilir. Nihai tutarlılık modelini kullanarak veriyi tüm düğümlerde zamanla senkronize eder, ancak bu, verinin anlık olarak tüm düğümlerde güncel olmayabileceği anlamına gelir. Bu yaklaşım, yüksek performans ve düşük gecikme süreleri sağlarken, verinin anlık tutarlılığını garanti etmez. Bu, özellikle yüksek performanslı ve büyük veri setlerine sahip uygulamalarda Couchbase’in güçlü yönlerinden biridir.

Postgre kullansak yapımız nasıl olabilir?

Eğer benzer bir uygulamayı PostgreSQL’de yapmayı düşünürsek aşağıdaki gibi bir veri tabanı şeması oluşturmamız gerekcek. Bu şema, UserInfo, Presentation, Slide, Question ve Option gibi varlıkları içerecektir ve her bir varlığın ilişkilerini ve temel özelliklerini kapsayacaktır.

1. UserInfo Tablosu

CREATE TABLE UserInfo (
Id SERIAL PRIMARY KEY,
FirstName VARCHAR(100) NOT NULL,
LastName VARCHAR(100) NOT NULL,
EmailAddress VARCHAR(255) NOT NULL,
PhoneNumber VARCHAR(20),
ClerkId VARCHAR(100),
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
IsActive BOOLEAN DEFAULT TRUE
);

2. Presentation Tablosu

CREATE TABLE Presentation (
Id SERIAL PRIMARY KEY,
Title VARCHAR(255) NOT NULL,
"Order" INTEGER,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
IsActive BOOLEAN DEFAULT TRUE,
UserInfoId INTEGER REFERENCES UserInfo(Id) ON DELETE CASCADE
);

3. Slide Tablosu

CREATE TABLE Slide (
Id SERIAL PRIMARY KEY,
TemplateId VARCHAR(100),
"Order" INTEGER,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
IsActive BOOLEAN DEFAULT TRUE,
PresentationId INTEGER REFERENCES Presentation(Id) ON DELETE CASCADE
);

4. Question Tablosu

CREATE TABLE Question (
Id SERIAL PRIMARY KEY,
Text TEXT NOT NULL,
Score VARCHAR(10),
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
IsActive BOOLEAN DEFAULT TRUE,
SlideId INTEGER REFERENCES Slide(Id) ON DELETE CASCADE
);

5. Option Tablosu

CREATE TABLE Option (
Id SERIAL PRIMARY KEY,
OptionText TEXT NOT NULL,
"Order" INTEGER,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UpdatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
IsActive BOOLEAN DEFAULT TRUE,
QuestionId INTEGER REFERENCES Question(Id) ON DELETE CASCADE
);
  • UserInfo Tablosu: Kullanıcı bilgilerini tutar.
  • Presentation Tablosu: Kullanıcının yaptığı sunumları tutar ve UserInfo tablosuna bir yabancı anahtar (UserInfoId) ile bağlanır.
  • Slide Tablosu: Sunum içindeki slaytları tutar ve Presentation tablosuna bir yabancı anahtar (PresentationId) ile bağlanır.
  • Question Tablosu: Slayt içindeki soruları tutar ve Slide tablosuna bir yabancı anahtar (SlideId) ile bağlanır.
  • Option Tablosu: Soru içindeki seçenekleri tutar ve Question tablosuna bir yabancı anahtar (QuestionId) ile bağlanır.

İlişkisel veritabanı tasarımı, özellikle büyük ve karmaşık veri yapıları ile çalışırken bazı zorluklar sunabilir. Öncelikle, şema yönetimi ve esneklik konusunda zorluklar yaşanabilir; ilişkisel veritabanlarında şema değişiklikleri zor ve zaman alıcı olabilir. Örneğin, bir sunuma yeni bir slayt türü eklemek veya bir soru türü değiştirmek, mevcut veritabanı yapısını bozmadan gerçekleştirilmelidir, bu da geliştirme sürecini karmaşıklaştırabilir. Ayrıca, veri tabanı şemasındaki değişiklikler, uygulama kodunda da değişiklik gerektirebilir, bu da entegrasyon ve test süreçlerini zorlaştırır. Performans açısından, ilişkisel veritabanları karmaşık sorgular ve veri bütünlüğü için çeşitli kısıtlamalar ve indeksler kullanır; bu da özellikle büyük veri setleri ve yüksek trafik durumlarında sorgu performansını etkileyebilir. Örneğin, çok sayıda slayt ve sorunun olduğu büyük sunumlar için sorguların optimize edilmesi gerekebilir. Yatay ölçeklenebilirlik konusunda ise, ilişkisel veritabanları genellikle sınırlıdır; bu, veritabanının veri artışıyla birlikte birden fazla sunucuya bölünerek genişletilmesini zorlaştırabilir. Bu tür genişleme işlemleri, veri tabanının performansını ve yönetimini etkileyebilir. Ayrıca, karmaşık ilişkiler ve veri yönetimi, örneğin kullanıcı bilgileri ile sunumlar arasındaki bağlantıları yönetmek, veri bütünlüğünü sağlamak için dikkatli bir şekilde yapılandırılmalıdır; aksi takdirde veri tutarlılığı sorunları yaşanabilir. İlişkisel veritabanları, veri bütünlüğü ve tutarlılığını sağlamak için çeşitli kısıtlamalar ve kurallar kullanır, bu da verilerin doğru bir şekilde yönetilmesini ve ilişkilerin optimize edilmesini zorlaştırabilir. Son olarak, bakım ve yönetim konularında da zorluklar yaşanabilir; ilişkisel veritabanlarının düzenli olarak bakımı, performans optimizasyonu ve veri bütünlüğü sağlaması gerekir, bu da ek teknik bilgi ve kaynak gerektirir. Farklı veri tabanı sistemleri arasında veri taşınabilirliği de genellikle daha karmaşıktır, bu da veri dönüşümleri ve uyumluluğu sağlamak için ek çabalar gerektirebilir.

MongoDB kullanırsak yapımız nasıl olabilir?

MongoDB, ilişkisel veritabanlarının aksine esnek bir veri modeli sunar ve verileri JSON benzeri BSON formatında saklar. Bu, genellikle veri yapılarının daha esnek ve dinamik olmasına izin verir. MongoDB’de, ilişkiler genellikle belgelerin iç içe yerleştirilmesi (embedding) veya referanslar kullanılarak yönetilir. Bu örnekte, belgelerin iç içe yerleştirilmesi yöntemi kullanılabilir.

{
"_id": ObjectId("60d5ec49f92dfb6d9c95e4a0"),
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john.doe@example.com",
"phoneNumber": "123-456-7890",
"clerkId": "clerk123",
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"presentations": [
{
"id": ObjectId("60d5ec49f92dfb6d9c95e4a1"),
"title": "Presentation 1",
"order": 0,
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"slides": [
{
"id": ObjectId("60d5ec49f92dfb6d9c95e4a2"),
"templateId": "template123",
"order": 0,
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"questions": [
{
"id": ObjectId("60d5ec49f92dfb6d9c95e4a3"),
"text": "What is your name?",
"score": "10",
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"options": [
{
"id": ObjectId("60d5ec49f92dfb6d9c95e4a4"),
"optionText": "John",
"order": 0,
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true
}
]
}
]
}
]
}
]
}
  • Embedding (İç İçe Yerleştirme) yöntemiyle, Presentation, Slide, Question ve Option belgeleri UserInfo belgesinin içine yerleştirilir. Bu, verilerin tek bir belge içinde saklanmasını sağlar ve özellikle tek bir kullanıcıya ait tüm bilgilerin birlikte getirileceği sorgular için performans avantajı sunar. İç içe yerleştirme yerine referanslar kullanılarak da ilişkiler oluşturulabilir. Bu yöntemde, her belge ayrı koleksiyonlarda saklanır ve diğer belgelerin ObjectId'lerini referans olarak içerir.
  • Alternatif Model — Referanslar Kullanarak: Eğer verileri çok fazla büyüyebilecek ve ayrı ayrı güncellenmesi gereken ilişkilerle yönetmek isterseniz, referansları kullanabilirsiniz. Bu durumda, her bir koleksiyon için ayrı belgeler oluşturulur ve referanslarla bağlanır.

UserInfo Belgesi (Referanslarla)

{
"_id": ObjectId("60d5ec49f92dfb6d9c95e4a0"),
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john.doe@example.com",
"phoneNumber": "123-456-7890",
"clerkId": "clerk123",
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"presentationIds": [ ObjectId("60d5ec49f92dfb6d9c95e4a1") ]
}

Presentation Belgesi

{
"_id": ObjectId("60d5ec49f92dfb6d9c95e4a1"),
"title": "Presentation 1",
"order": 0,
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"userId": ObjectId("60d5ec49f92dfb6d9c95e4a0"),
"slideIds": [ ObjectId("60d5ec49f92dfb6d9c95e4a2") ]
}

Slide Belgesi

{
"_id": ObjectId("60d5ec49f92dfb6d9c95e4a2"),
"templateId": "template123",
"order": 0,
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"presentationId": ObjectId("60d5ec49f92dfb6d9c95e4a1"),
"questionIds": [ ObjectId("60d5ec49f92dfb6d9c95e4a3") ]
}

Question Belgesi

{
"_id": ObjectId("60d5ec49f92dfb6d9c95e4a3"),
"text": "What is your name?",
"score": "10",
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"slideId": ObjectId("60d5ec49f92dfb6d9c95e4a2"),
"optionIds": [ ObjectId("60d5ec49f92dfb6d9c95e4a4") ]
}

Option Belgesi

{
"_id": ObjectId("60d5ec49f92dfb6d9c95e4a4"),
"optionText": "John",
"order": 0,
"createdAt": ISODate("2024-08-03T13:27:58.538Z"),
"updatedAt": ISODate("2024-08-03T13:27:58.538Z"),
"isActive": true,
"questionId": ObjectId("60d5ec49f92dfb6d9c95e4a3")
}

MongoDB’de veri yapısını iç içe yerleştirme (embedding) veya referanslar kullanarak modelleme seçenekleri, ilişkisel veritabanlarına benzer bazı özellikler sunabilir. İç içe yerleştirme yöntemi, tüm verileri tek bir belge içinde saklayarak, özellikle belirli bir kullanıcıya ait tüm bilgilerin aynı anda sorgulanmasını sağlar. Bu, performans avantajı sunarken, verilerin bütünlüğünü ve erişimini basit tutar. Ancak, bu yöntem verilerin büyüklüğü arttıkça ve güncellenmesi gereken içerik sayısı yükseldikçe yönetim zorlukları yaratabilir. Örneğin, bir kullanıcıya ait birçok sunum, slayt ve soru bulunduğunda, bu verilerin tek bir belge içinde saklanması, belge boyutunun hızla büyümesine neden olabilir.

Referanslar kullanarak veri modellemesi, iç içe yerleştirme yerine her veri parçasını ayrı belgelerde tutarak, bu belgeler arasında ilişkiler kurar. Bu yaklaşım, verilerin güncellenmesi gerektiğinde daha esnek bir çözüm sunar çünkü her belge bağımsız olarak güncellenebilir. Örneğin, bir sunumun slaytlarını ve sorularını ayrı ayrı güncellemek daha kolay olabilir. Ancak, referanslarla yapılan modellemede, verileri bir araya getirmek için daha fazla sorgu ve veri çekme işlemi yapılması gerekir. Bu, performansı etkileyebilir çünkü ilgili belgelerin çekilmesi ve birleştirilmesi zaman alabilir.

İç içe yerleştirme, küçük ve sık güncellenmeyen veri setleri için uygunken, referanslar daha büyük ve dinamik veri setleri için esneklik sağlar. Ancak, referanslar kullanıldığında, nihai tutarlılığı sağlamak daha karmaşık olabilir çünkü veriler arasındaki ilişkiler her zaman güncel olmayabilir.

Couchbase’yı deneyelim!

Couchbase, modern veri ihtiyaçlarına yönelik esnek ve güçlü bir NoSQL veri tabanı yönetim sistemidir. Couchbase, Couchbase, Inc. tarafından geliştirilmiştir ve 2011 yılında Memcached ve CouchDB projelerinin birleşimiyle ortaya çıkmıştır. Couchbase, yüksek performans, yatay ölçeklenebilirlik ve esneklik gibi özellikleri ile bilinir.

Couchbase, yüksek performansı ve esnek veri yönetim özellikleri ile dikkat çeker. Düşük gecikme süresi ve yüksek veri işleme kapasitesi sayesinde, yüksek performanslı uygulamalar için ideal bir seçimdir. Dağıtık mimarisi sayesinde kolayca yatay olarak ölçeklenebilir, bu da büyük veri yükleri ile başa çıkabilmesini sağlar. Couchbase, JSON belgeleri kullanarak veri saklama konusunda esneklik sunar, böylece farklı veri yapılarını rahatlıkla yönetebilirsiniz. Zengin sorgulama yetenekleri, SQL-benzeri N1QL dili ile desteklenir, ayrıca tam metin arama özelliği sayesinde metin bazlı aramalar yapabilirsiniz. Gerçek zamanlı analiz yeteneği, verileri anlık olarak inceleme imkanı tanırken, Couchbase Lite ve Sync Gateway desteği ile mobil ve IoT uygulamaları için güçlü çözümler sunar.

Belge (Document) Tabanlı Veri Modeli

Couchbase, belge tabanlı bir veri modeli kullanır ve bu modelin merkezinde JSON (JavaScript Object Notation) formatı yer alır. Belge tabanlı veri modeli, geleneksel tablo ve satır tabanlı veri modellerinden farklı olarak, verilerin esnek ve hiyerarşik yapıda saklanmasına olanak tanır. Bu model, hem karmaşık hem de ilişkisel olmayan veri yapılarının saklanması ve yönetilmesi için idealdir.

JSON Yapısı

JSON, anahtar-değer çiftlerinden oluşan, insan tarafından kolayca okunabilen ve veri paylaşımı için yaygın olarak kullanılan bir format olarak bilinir. Couchbase’te JSON belgeleri, verilerin temel birimi olarak kullanılır ve bu, Couchbase’in esnek veri modelinin merkezinde yer alır. JSON’un sunduğu esneklik, iç içe geçmiş nesneler ve diziler gibi karmaşık veri yapılarının kolayca saklanmasına olanak tanır. Bu yapı, önceden tanımlanmış şemalar gerektirmeden dinamik olarak değiştirilebilen veri yapıları oluşturmayı mümkün kılar. JSON’un basit ve okunabilir sözdizimi, geliştiricilere verileri hızlı bir şekilde anlama ve manipüle etme kolaylığı sağlar. Ayrıca, dil bağımsız bir format olması nedeniyle, birçok programlama dili ile sorunsuz bir şekilde kullanılabilir ve çeşitli uygulamalarla entegrasyon sağlanabilir. Bu özellikler, JSON’u veri yönetimi ve uygulama geliştirme süreçlerinde son derece esnek ve kullanışlı bir araç haline getirir.

{
"id": "user123",
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john.doe@example.com",
"phoneNumber": "123-456-7890",
"createdAt": "2024-08-02T23:09:00.959Z",
"updatedAt": "2024-08-02T23:09:00.959Z",
"isActive": true,
"presentations": [
{
"id": "presentation1",
"title": "Couchbase Introduction",
"order": 0,
"createdAt": "2024-08-02T23:09:00.959Z",
"updatedAt": "2024-08-02T23:09:00.959Z",
"isActive": true,
"slides": [
{
"id": "slide1",
"templateId": "template1",
"order": 0,
"createdAt": "2024-08-02T23:09:00.959Z",
"updatedAt": "2024-08-02T23:09:00.959Z",
"isActive": true,
"questions": [
{
"id": "question1",
"text": "What is Couchbase?",
"score": "5",
"createdAt": "2024-08-02T23:09:00.959Z",
"updatedAt": "2024-08-02T23:09:00.959Z",
"isActive": true,
"options": [
{
"id": "option1",
"optionText": "A NoSQL database",
"order": 0,
"createdAt": "2024-08-02T23:09:00.959Z",
"updatedAt": "2024-08-02T23:09:00.959Z",
"isActive": true
}
]
}
]
}
]
}
]
}

Verilen JSON yapısı, kullanıcı bilgilerini ve bu kullanıcının yaptığı sunumları saklayan bir veri modelini temsil ediyor. Bu senaryo, bir kullanıcı yönetim sistemi ve sunum takibi uygulaması için uygun olabilir. Bu JSON yapısı, bir eğitim veya konferans yönetim sistemi için oldukça uygun olabilir. Kullanıcılar bu sistemde kayıtlı olabilir ve her bir kullanıcı çeşitli sunumlar yapabilir. Sunumlar, belirli bir sırayla slaytlardan oluşur ve her slayt belirli sorular içerir. Bu soruların her biri çeşitli cevap seçenekleri ile sunulur. Sistem, kullanıcıların etkinliklerini izlemek ve değerlendirmek için bu bilgileri kullanabilir. Couchbase’in belge tabanlı modeli, şema değişiklikleri için veri tabanını yeniden yapılandırmayı gerektirmediğinden, değişen gereksinimlere hızlı bir şekilde uyum sağlar ve geliştirme sürecini hızlandırır. JSON formatı, uygulamalarda kullanılan nesne yapılarıyla uyumlu olduğundan, verilerin doğal ve kolay bir şekilde saklanmasını ve işlenmesini sağlar. Ayrıca, RESTful API’ler ve diğer modern veri alışveriş formatları ile uyumluluğu sayesinde entegrasyonu basitleştirir. Couchbase, JSON belgelerini bellek içi saklama ve işleme yetenekleriyle yüksek performans sunar. Bu özellikler, Couchbase’i içerik yönetim sistemleri, e-ticaret uygulamaları, mobil ve web uygulamaları ile gerçek zamanlı analiz ve IoT gibi birçok kullanım senaryosu için ideal kılar. JSON formatı, modern uygulamaların gereksinimlerine yanıt verecek esneklik ve performans sunarak, geliştiricilere büyük kolaylık ve hızlı uygulama geliştirme imkanı sağlar.

Coucbase Capebella

İlk olarak, Couchbase Capella’yı denemeye karar verdim ve platformun ücretsiz deneme sürecini başlattım. Bu süreçte, Couchbase Capella’nın bulut tabanlı özelliklerini keşfetmek için yeni bir cluster oluşturmayı seçtim. Deneme süresi genellikle 30 gün sürüyor, bu yüzden bu süreyi en iyi şekilde değerlendirmek istiyorum.

Cluster Oluşturma ve Yapılandırmaları

Cluster Oluşturma

  • İlk adımda, Couchbase Capella kontrol paneline giriş yaptım ve “Create Cluster” seçeneğine tıkladım. Burada birkaç temel yapılandırma seçeneği sunuluyor.

Provider ve Konum Seçimi

  • Sağ üst köşede bulunan “Provider” kısmından AWS’yi seçtim ve bölge olarak “US East (Ohio)”yı belirledim. Bu, verilerimin fiziksel olarak hangi bölgede saklanacağını belirliyor.

Node ve Servis Seçimi:

  • Bir tane node seçtim. Bu, veri tabanımın çalışacağı sunucu anlamına geliyor. Node sayısını ihtiyaca göre artırabilirim, ancak başlangıç için tek bir node yeterli.

Servisler:

  • Cluster’a dahil edilen servisleri seçtim: Query, Data, Index, ve Search. Bu servisler, veri sorgulama, veri saklama, indeksleme ve metin aramaları gibi işlemleri yönetiyor.

Sürüm ve CIDR:

  • Couchbase’in sürümünü 7.6.2 olarak seçtim. Ayrıca, veri tabanımın IP aralığını belirlemek için bir CIDR bloğu (10.2.0.0/23) girdim. Bu, veritabanımın erişilebilirlik ve güvenlik ayarlarını yapılandırıyor.

Veri Yükleme ve Yönetim

Data Tools ve Bucket Oluşturma

  • “Data Tools” bölümüne geçtim ve burada bir bucket oluşturmak için “travel-sample” adında bir bucket seçtim. Bucket, verilerin saklandığı temel yapıdır. Burada kendi ekibimin kullanımına uygun bir scope oluşturabilirim.

Scope ve Veri Yükleme

  • Scope, verilerimi organize etmek için bir alan sağlıyor. Kendi scope’umu oluşturdum ve JSON formatında verilerimi yükledim. JSON, verileri esnek ve okunabilir bir formatta saklamama olanak tanıyor. Verilerim, iç içe geçmiş nesneler ve diziler içeriyorsa, JSON bunları basit bir şekilde yönetmemi sağlıyor.

Veri Yönetimi

  • JSON formatındaki verilerimi bu scope içinde organize edebildim. Verileri ekleme, güncelleme ve silme işlemlerini Couchbase’in sunduğu araçlarla gerçekleştirebildim.

Sorgulama

  • N1QL dili ile JSON belgelerim üzerinde güçlü sorgular yapabildim. Bu, verilerim üzerinde hızlı ve etkili sorgulama yapmama olanak sağladı.

Arama ve Analiz:

  • Couchbase’in full-text search özelliğini kullanarak metin içi aramalar yapabildim. Bu özellik, veriler üzerinde anlık ve detaylı analizler yapmamı sağladı.

Kısacası, Couchbase Capella’nın sunduğu bu araçlar ve özelliklerle verileri yönetmek oldukça kolay ve esnek. Şu anda deneme sürecindeyim, ama bu platformun veritabanı ihtiyaçlarım için ne kadar uygun olduğunu görmek için süreci yakından takip ediyorum. İlerleyen günlerde ekibimle birlikte daha detaylı bir değerlendirme yapmayı planlıyorum ve bulgularımı sizinle paylaşacağım.

Couchbase’yi denemek için .NET Core Tarafında Uygulama

Öncelikli olarak Couchbase Capella tarafına yani veri tabanına kod tarafından erişim sağlayabilmek için gerekli olan bir konfigurasyon yapmam gerekiyordu. Bunu gerçekleştirmek için aşağıdaki gibi kullanıcı adı ve şifre bilgilerine göre bir database access oluşturdum.

Ve bu oluşturduğum veri tabanı yetkilendirmesine hem okuma hem de yazma yetkileri verdim.

NET Web API Projesi Oluşturma

Visual Studio veya dotnet komut satırı aracı kullanarak yeni bir .NET Web API projesi oluşturun.

dotnet new webapi -n MyCouchbaseApp
cd MyCouchbaseApp

Couchbase SDK’sını NuGet kullanarak projenize ekleyin. Bunun için dotnet add package komutunu kullanabilirsiniz.

dotnet add package Couchbase.NetClient

Couchbase yapılandırmasını appsettings.json dosyanıza ekleyin.

{
"Couchbase": {
"ConnectionString": "couchbase://localhost",
"Username": "your_username",
"Password": "your_password",
"Buckets": [
"",
""
]
}
}

Program.cs dosyanızda Couchbase bağlantısını yapılandırın:

using Couchbase;
using Couchbase.Extensions.DependencyInjection;
using DotnetCouchbaseExample.Filters;
using Microsoft.OpenApi.Models;

var couchbaseConfig = builder.Configuration.GetSection("Couchbase");
var connectionString = couchbaseConfig["ConnectionString"];
var username = couchbaseConfig["Username"];
var password = couchbaseConfig["Password"];
var buckets = couchbaseConfig.GetSection("Buckets").Get<string[]>();

if (buckets == null || buckets.Length == 0)
{
throw new InvalidOperationException("No buckets configured.");
}


builder.Services.AddCouchbase(options =>
{
options.ConnectionString = connectionString;
options.UserName = username;
options.Password = password;
options.Buckets = buckets;
});

Veri yapınız için model sınıflarını oluşturmanız gerekiyor.

namespace DotnetCouchbaseExample.Models
{
public class UserInfo
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
public string ClerkId { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
public DateTime UpdatedAt { get; set; } = DateTime.Now;
public bool IsActive { get; set; }
public Presentation[]? Presentations { get; set; }
}

public class Presentation
{
public string Id { get; set; }
public string Title { get; set; }
public int Order { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
public DateTime UpdatedAt { get; set; } = DateTime.Now;
public bool IsActive { get; set; } = false;
public Slide[]? Slides { get; set; }
}

public class Slide
{
public string Id { get; set; }
public int TemplateId { get; set; } = 1;
public int Order { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsActive { get; set; }
public Question[]? Questions { get; set; }
}

public class Question
{
public string Id { get; set; }
public string Text { get; set; }
public string Score { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
public DateTime UpdatedAt { get; set; } = DateTime.Now;
public bool IsActive { get; set; }
public Option[]? Options { get; set; }
}

public class Option
{
public string Id { get; set; }
public string OptionText { get; set; }
public int Order { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
public DateTime UpdatedAt { get; set; } = DateTime.Now;
public bool IsActive { get; set; } = false;
}

}

Couchbase veritabanıyla etkileşimde bulunmak için bir servis oluşturdum. Şöyle düşünün: Couchbase, verilerinizi sakladığınız bir tür veri kutusu ve bu kod, o kutudaki verilerle iş yapmanıza yardımcı olacak bir yardımcı araç gibi.

  • CouchbaseService sınıfı: Bu sınıf, veritabanıyla konuşmak için gerekli olan her şeyi içeriyor. İçinde, verileri eklemek, güncellemek, almak ve silmek için kullanılacak metodlar var.
  • IBucket ve ICouchbaseCollection: Bunlar, Couchbase'teki belirli bölgelere (buckets) ve koleksiyonlara erişmenizi sağlar. Burada, “travel-sample” adında bir bucket ve onun içinde “futuverse” adında bir scope (alt küme) içindeki “UserInfos” adlı koleksiyona erişiyoruz.

Metotlar:

  • InsertAsync: Bu metod, yeni bir veri eklemek için kullanılır. Veriyi belirlediğiniz bir ID ile veritabanına ekler.
  • UpsertAsync: Mevcut veriyi günceller ya da eğer veri yoksa yeni bir tane ekler. Yani, ya mevcut veriyi yeniler ya da tamamen yeni bir veri ekler.
  • GetAsync: Belirli bir ID'ye sahip veriyi alır. Veritabanından veriyi getirip içeriğini size döner.
  • RemoveAsync: Belirli bir ID'ye sahip veriyi siler. Yani, veritabanından o veriyi kaldırır.
using Couchbase.Extensions.DependencyInjection;
using Couchbase.KeyValue;
using Couchbase;

public class CouchbaseService : ICouchbaseService
{
private readonly IBucket _bucket;
private readonly ICouchbaseCollection _collection;

public CouchbaseService(IBucketProvider bucketProvider)
{
_bucket = bucketProvider.GetBucketAsync("travel-sample").GetAwaiter().GetResult();
var scope = _bucket.Scope("futuverse");
_collection = scope.Collection("UserInfos");
}

public async Task InsertAsync(string id, object document)
{
await _collection.InsertAsync(id, document);
}

public async Task UpsertAsync(string id, object document)
{
await _collection.UpsertAsync(id, document);
}

public async Task<IGetResult> GetAsync(string id)
{
return await _collection.GetAsync(id);
}

public async Task RemoveAsync(string id)
{
await _collection.RemoveAsync(id);
}
}

Aşağıdaki kod, bir ASP.NET Core Web API’de Couchbase ile etkileşim kuran bir UserInfoController sınıfını tanımlar. Bu sınıf, kullanıcı bilgilerini yönetmek için dört ana işlev sunar. CreateUser metodu, yeni bir kullanıcı bilgisi ekler ve kullanıcıya benzersiz bir ID atar. GetUserById metodu, verilen ID'ye sahip kullanıcıyı alır ve bulamazsa hata döner. UpdateUser metodu, mevcut bir kullanıcı bilgisini günceller, ancak kullanıcı bilgisi veya ID uyumsuzsa hata döner. Son olarak, DeleteUser metodu, belirli bir ID'ye sahip kullanıcıyı siler, eğer kullanıcı bulunamazsa hata döner. Bu metodlar, Couchbase veritabanıyla etkileşim kurarak veri ekleme, alma, güncelleme ve silme işlemlerini gerçekleştirir. Aslında burada User özelinde yapılan işlemler için ayrı bir Controller oluşturmuş oldum.

using DotnetCouchbaseExample.Models;
using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[ApiController]
public class UserInfoController : ControllerBase
{
private readonly ICouchbaseService _couchbaseService;

public UserInfoController(ICouchbaseService couchbaseService)
{
_couchbaseService = couchbaseService;
}

[HttpPost("add")]
public async Task<IActionResult> CreateUser([FromBody] UserInfo userInfo)
{
if (userInfo == null)
{
return BadRequest("User information is null.");
}

userInfo.Id = System.Guid.NewGuid().ToString();
await _couchbaseService.InsertAsync(userInfo.Id.ToString(), userInfo);
return Ok("User added successfully.");
}

[HttpGet("{id}")]
public async Task<IActionResult> GetUserById(string id)
{
var result = await _couchbaseService.GetAsync(id);

if (result.ContentAs<UserInfo>() == null)
{
return NotFound();
}

return Ok(result.ContentAs<UserInfo>());
}

[HttpPut("update/{id}")]
public async Task<IActionResult> UpdateUser(string id, [FromBody] UserInfo updatedUserInfo)
{
if (updatedUserInfo == null || id != updatedUserInfo.Id)
{
return BadRequest("User information is null or ID mismatch.");
}

var result = await _couchbaseService.GetAsync(id);
var existingUserInfo = result.ContentAs<UserInfo>();

if (existingUserInfo == null)
{
return NotFound("User not found.");
}

updatedUserInfo.Id = existingUserInfo.Id;
updatedUserInfo.Presentations = existingUserInfo.Presentations;

await _couchbaseService.UpsertAsync(id, updatedUserInfo);
return Ok("User updated successfully.");
}

[HttpDelete("delete/{id}")]
public async Task<IActionResult> DeleteUser(string id)
{
var result = await _couchbaseService.GetAsync(id);
if (result == null)
{
return NotFound("User not found.");
}

await _couchbaseService.RemoveAsync(id);
return Ok("User deleted successfully.");
}
}

PresentationController sınıfı, kullanıcı bilgileriyle ilişkili sunumları yönetir. CreatePresentation metodu, verilen kullanıcı ID'sine bağlı olarak yeni bir sunum ekler; sunum eklendikten sonra kullanıcı bilgileri güncellenir. GetPresentationById metodu, belirli bir kullanıcı ve sunum ID'si için sunumu alır ve bulamazsa hata döner. UpdatePresentation metodu, mevcut bir sunumu günceller; eğer sunum bulunamazsa veya kullanıcı bilgisi uyumsuzsa hata döner. DeletePresentation metodu, belirli bir sunumu siler ve bulunamazsa hata döner.

using DotnetCouchbaseExample.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

[Route("api/[controller]")]
[ApiController]
public class PresentationController : ControllerBase
{
private readonly ICouchbaseService _couchbaseService;

public PresentationController(ICouchbaseService couchbaseService)
{
_couchbaseService = couchbaseService;
}

[HttpPost]
public async Task<IActionResult> CreatePresentation([FromQuery] string userId, [FromBody] Presentation presentation)
{
if (string.IsNullOrEmpty(userId))
{
return BadRequest("UserId is required.");
}

var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

presentation.Id = System.Guid.NewGuid().ToString();
var userInfo = user.ContentAs<UserInfo>();
userInfo.Presentations = userInfo.Presentations?.Append(presentation).ToArray() ?? new[] { presentation };

await _couchbaseService.UpsertAsync(userInfo.Id, userInfo);

return CreatedAtAction(nameof(GetPresentationById), new { id = presentation.Id }, presentation);
}


[HttpGet("{userId}/{presentationId}")]
public async Task<IActionResult> GetPresentationById(string userId, string presentationId)
{
var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

var presentation = user.ContentAs<UserInfo>().Presentations.FirstOrDefault(p => p.Id == presentationId);
if (presentation == null)
{
return NotFound();
}

return Ok(presentation);
}


[HttpPut("{userId}/{presentationId}")]
public async Task<IActionResult> UpdatePresentation(string userId, string presentationId, [FromBody] Presentation updatedPresentation)
{
var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();

if (userInfo == null)
{
return NotFound("User not found.");
}

var presentations = userInfo.Presentations;
var index = Array.FindIndex(presentations, p => p.Id == presentationId);

if (index == -1)
{
return NotFound("Presentation not found.");
}

var currentPresentation = presentations[index];
updatedPresentation.Id = currentPresentation.Id;
updatedPresentation.Slides = currentPresentation.Slides;

presentations[index] = updatedPresentation;

userInfo.Presentations = presentations;

await _couchbaseService.UpsertAsync(userId, userInfo);

return NoContent();
}


[HttpDelete("{userId}/{presentationId}")]
public async Task<IActionResult> DeletePresentation(string userId, string presentationId)
{
var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

var userInfo = user.ContentAs<UserInfo>();
var presentationToRemove = userInfo.Presentations.FirstOrDefault(p => p.Id == presentationId);
if (presentationToRemove == null)
{
return NotFound();
}

userInfo.Presentations = userInfo.Presentations.Where(p => p.Id != presentationId).ToArray();

await _couchbaseService.UpsertAsync(userInfo.Id, userInfo);
return NoContent();
}


}

SlideController sınıfı, kullanıcıların sunumlarına bağlı olarak slaytları yönetir. CreateSlide metodu, belirli bir sunuma yeni bir slayt ekler; kullanıcı ve sunum doğrulamasını yapar, slaytı ekler ve güncellenmiş kullanıcı bilgilerini veritabanına kaydeder. GetSlideById metodu, belirli bir kullanıcı, sunum ve slayt ID'sine göre slaytı getirir; eğer kullanıcı veya slayt bulunamazsa hata döner. UpdateSlide metodu, mevcut bir slaytı günceller; slaytın mevcut olup olmadığını kontrol eder, güncellenmiş bilgileri yerleştirir ve güncellenmiş kullanıcı bilgilerini veritabanına kaydeder. DeleteSlide metodu, belirli bir slaytı siler; slayt bulunamazsa hata döner ve kullanıcı bilgilerinde güncelleme yaparak veritabanına kaydeder. Genel olarak, bu controller slaytların eklenmesi, güncellenmesi, getirilmesi ve silinmesini yönetir, bu işlemleri yaparken ilgili kullanıcı ve sunum verilerini kontrol eder.

using DotnetCouchbaseExample.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;

[Route("api/[controller]")]
[ApiController]
public class SlideController : ControllerBase
{
private readonly ICouchbaseService _couchbaseService;

public SlideController(ICouchbaseService couchbaseService)
{
_couchbaseService = couchbaseService;
}

[HttpPost]
public async Task<IActionResult> CreateSlide([FromQuery] string userId, [FromQuery] string presentationId, [FromBody] Slide slide)
{
if (string.IsNullOrEmpty(userId))
{
return BadRequest("UserId is required.");
}

if (string.IsNullOrEmpty(presentationId))
{
return BadRequest("PresentationId is required.");
}

var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();
if (userInfo == null)
{
return NotFound("User not found.");
}

var presentation = userInfo.Presentations.FirstOrDefault(p => p.Id == presentationId);
if (presentation == null)
{
return NotFound("Presentation not found.");
}

slide.Id = Guid.NewGuid().ToString();

if (presentation.Slides == null)
{
presentation.Slides = new Slide[] { slide };
}
else
{
presentation.Slides = presentation.Slides.Append(slide).ToArray();
}

await _couchbaseService.UpsertAsync(userId, userInfo);

return CreatedAtAction(nameof(GetSlideById), new { userId = userId, presentationId = presentationId, id = slide.Id }, slide);
}

[HttpGet("{userId}/{presentationId}/{id}")]
public async Task<IActionResult> GetSlideById(string userId, string presentationId, string id)
{
var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

var slide = user.ContentAs<UserInfo>().Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == id);

if (slide == null)
{
return NotFound();
}

return Ok(slide);
}

[HttpPut("{userId}/{presentationId}/{id}")]
public async Task<IActionResult> UpdateSlide(string userId, string presentationId, string id, [FromBody] Slide updatedSlide)
{
var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();
if (userInfo == null)
{
return NotFound("User not found.");
}

var presentation = userInfo.Presentations.FirstOrDefault(p => p.Id == presentationId);
if (presentation == null)
{
return NotFound("Presentation not found.");
}

var slides = presentation.Slides;
var index = Array.FindIndex(slides, s => s.Id == id);
if (index == -1)
{
return NotFound("Slide not found.");
}

var existingSlide = slides[index];
updatedSlide.Id = existingSlide.Id;
updatedSlide.Questions = existingSlide.Questions;

slides[index] = updatedSlide;
presentation.Slides = slides;

await _couchbaseService.UpsertAsync(userId, userInfo);

return NoContent();
}


[HttpDelete("{userId}/{presentationId}/{id}")]
public async Task<IActionResult> DeleteSlide(string userId, string presentationId, string id)
{
var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();
if (userInfo == null)
{
return NotFound("User not found.");
}

var presentation = userInfo.Presentations.FirstOrDefault(p => p.Id == presentationId);
if (presentation == null)
{
return NotFound("Presentation not found.");
}

var slides = presentation.Slides;
var slideIndex = Array.FindIndex(slides, s => s.Id == id);
if (slideIndex == -1)
{
return NotFound("Slide not found.");
}

var updatedSlides = slides.Where((_, index) => index != slideIndex).ToArray();
presentation.Slides = updatedSlides;

await _couchbaseService.UpsertAsync(userId, userInfo);

return NoContent();
}
}

QuestionController sınıfı, kullanıcıların sunumlarının slaytlarında yer alan soruları yönetir. CreateQuestion metodu, belirli bir kullanıcı, sunum ve slayta yeni bir soru ekler; gerekli ID'lerin doğruluğunu kontrol eder ve soruyu slaytın mevcut sorularına ekler. GetQuestionById metodu, belirli bir kullanıcı, sunum, slayt ve soru ID'sine göre soruyu getirir. UpdateQuestion metodu, mevcut bir soruyu günceller; soru var mı diye kontrol eder, güncellenmiş bilgileri yerleştirir ve güncellenmiş verileri veritabanına kaydeder. DeleteQuestion metodu, belirli bir soru ID'sine sahip soruyu siler; sorunun var olup olmadığını kontrol eder ve güncellenmiş verileri veritabanına kaydeder. Bu controller, soruların eklenmesi, getirilmesi, güncellenmesi ve silinmesi işlemlerini yönetir.

using DotnetCouchbaseExample.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;

[Route("api/[controller]")]
[ApiController]
public class QuestionController : ControllerBase
{
private readonly ICouchbaseService _couchbaseService;

public QuestionController(ICouchbaseService couchbaseService)
{
_couchbaseService = couchbaseService;
}

[HttpPost("create-question")]
public async Task<IActionResult> CreateQuestion([FromQuery] string userId, [FromQuery] string presentationId, [FromQuery] string slideId, [FromBody] Question question)
{
if (string.IsNullOrEmpty(userId))
{
return BadRequest("UserId is required.");
}

if (string.IsNullOrEmpty(presentationId))
{
return BadRequest("PresentationId is required.");
}

if (string.IsNullOrEmpty(slideId))
{
return BadRequest("SlideId is required.");
}

var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();
if (userInfo == null)
{
return NotFound("User not found.");
}

var slide = userInfo.Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId);

if (slide == null)
{
return NotFound("Slide not found.");
}

question.Id = Guid.NewGuid().ToString();
slide.Questions = slide.Questions?.Append(question).ToArray() ?? new[] { question };

await _couchbaseService.UpsertAsync(userId, userInfo);

return CreatedAtAction(nameof(GetQuestionById), new { userId = userId, presentationId = presentationId, slideId = slideId, id = question.Id }, question);
}

[HttpGet("{userId}/{presentationId}/{slideId}/{id}")]
public async Task<IActionResult> GetQuestionById(string userId, string presentationId, string slideId, string id)
{
var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

var question = user.ContentAs<UserInfo>().Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId)?
.Questions.FirstOrDefault(q => q.Id == id);

if (question == null)
{
return NotFound();
}

return Ok(question);
}

[HttpPut("{userId}/{presentationId}/{slideId}/{id}")]
public async Task<IActionResult> UpdateQuestion(string userId, string presentationId, string slideId, string id, [FromBody] Question updatedQuestion)
{
var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();
if (userInfo == null)
{
return NotFound("User not found.");
}

var slide = userInfo.Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId);

if (slide == null)
{
return NotFound("Slide not found.");
}

var questions = slide.Questions;
var index = Array.FindIndex(questions, q => q.Id == id);
if (index == -1)
{
return NotFound("Question not found.");
}

var existingQuestion = questions[index];
updatedQuestion.Id = existingQuestion.Id;
updatedQuestion.Options = existingQuestion.Options;

questions[index] = updatedQuestion;
slide.Questions = questions;

await _couchbaseService.UpsertAsync(userInfo.Id, userInfo);

return NoContent();
}

[HttpDelete("{userId}/{presentationId}/{slideId}/{id}")]
public async Task<IActionResult> DeleteQuestion(string userId, string presentationId, string slideId, string id)
{
var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

var slide = user.ContentAs<UserInfo>().Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId);

if (slide == null)
{
return NotFound();
}

slide.Questions = slide.Questions.Where(q => q.Id != id).ToArray();

await _couchbaseService.UpsertAsync(user.ContentAs<UserInfo>().Id, user.ContentAs<UserInfo>());

return NoContent();
}
}

OptionController sınıfı, kullanıcıların sunumlarının slaytlarındaki sorulara seçenekler eklemelerini, güncellemelerini ve silmelerini sağlar. CreateOption metodu, gerekli tüm ID'lerin sağlandığından emin olduktan sonra, belirli bir kullanıcı, sunum, slayt ve soru için yeni bir seçenek oluşturur ve bunu ilgili sorunun seçeneklerine ekler. GetOptionById metodu, belirtilen kullanıcı, sunum, slayt, soru ve seçenek ID'sine göre seçeneği getirir. UpdateOption metodu, mevcut bir seçeneği günceller; seçeneğin varlığını doğrular ve güncellenmiş verileri kaydeder. DeleteOption metodu, belirli bir seçenek ID'sine sahip seçeneği siler ve güncellenmiş verileri veritabanına kaydeder. Bu controller, seçeneklerin eklenmesi, getirilmesi, güncellenmesi ve silinmesi işlemlerini yönetir.

using DotnetCouchbaseExample.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;

[Route("api/[controller]")]
[ApiController]
public class OptionController : ControllerBase
{
private readonly ICouchbaseService _couchbaseService;

public OptionController(ICouchbaseService couchbaseService)
{
_couchbaseService = couchbaseService;
}

[HttpPost]
public async Task<IActionResult> CreateOption([FromQuery] string userId, [FromQuery] string presentationId, [FromQuery] string slideId, [FromQuery] string questionId, [FromBody] Option option)
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(presentationId) || string.IsNullOrEmpty(slideId) || string.IsNullOrEmpty(questionId))
{
return BadRequest("All IDs (userId, presentationId, slideId, questionId) are required.");
}

var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();
if (userInfo == null)
{
return NotFound("User not found.");
}

var question = userInfo.Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId)?
.Questions.FirstOrDefault(q => q.Id == questionId);

if (question == null)
{
return NotFound("Question not found.");
}

option.Id = Guid.NewGuid().ToString();
question.Options = question.Options?.Append(option).ToArray() ?? new[] { option };

await _couchbaseService.UpsertAsync(userId, userInfo);

return CreatedAtAction(nameof(GetOptionById), new { userId = userId, presentationId = presentationId, slideId = slideId, questionId = questionId, id = option.Id }, option);
}


[HttpGet("{userId}/{presentationId}/{slideId}/{questionId}/{id}")]
public async Task<IActionResult> GetOptionById(string userId, string presentationId, string slideId, string questionId, string id)
{
var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

var option = user.ContentAs<UserInfo>().Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId)?
.Questions.FirstOrDefault(q => q.Id == questionId)?
.Options.FirstOrDefault(o => o.Id == id);

if (option == null)
{
return NotFound();
}

return Ok(option);
}

[HttpPut("{userId}/{presentationId}/{slideId}/{questionId}/{id}")]
public async Task<IActionResult> UpdateOption(string userId, string presentationId, string slideId, string questionId, string id, [FromBody] Option updatedOption)
{
var userResult = await _couchbaseService.GetAsync(userId);
var userInfo = userResult.ContentAs<UserInfo>();

if (userInfo == null)
{
return NotFound("User not found.");
}

var question = userInfo.Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId)?
.Questions.FirstOrDefault(q => q.Id == questionId);

if (question == null)
{
return NotFound("Question not found.");
}

var options = question.Options;
var index = Array.FindIndex(options, o => o.Id == id);

if (index == -1)
{
return NotFound("Option not found.");
}

var currentOption = options[index];
currentOption.OptionText = updatedOption.OptionText ?? currentOption.OptionText;

options[index] = currentOption;
question.Options = options;

await _couchbaseService.UpsertAsync(userId, userInfo);

return NoContent();
}

[HttpDelete("{userId}/{presentationId}/{slideId}/{questionId}/{id}")]
public async Task<IActionResult> DeleteOption(string userId, string presentationId, string slideId, string questionId, string id)
{
var user = await _couchbaseService.GetAsync(userId);
if (user.ContentAs<UserInfo>() == null)
{
return NotFound();
}

var question = user.ContentAs<UserInfo>().Presentations
.FirstOrDefault(p => p.Id == presentationId)?
.Slides.FirstOrDefault(s => s.Id == slideId)?
.Questions.FirstOrDefault(q => q.Id == questionId);

if (question == null)
{
return NotFound();
}

question.Options = question.Options.Where(o => o.Id != id).ToArray();

await _couchbaseService.UpsertAsync(user.ContentAs<UserInfo>().Id, user.ContentAs<UserInfo>());

return NoContent();
}
}

Gün sonunda bu demo örneğinde iç içe geçmiş bir veriyi güncellemek için ayrı ayrı controller sınıflar yazdım. Çok içime sindi mi tartışılır… Demo diye kendi üzerime çok gelmiyorum ama ben burada bence neyi iyi yaptım? Neyi iyi yapamadım? Biraz düşünelim, tartışalım. :)

Olumlu Yönlerim

  • Sorumluluk Ayrımı: Her controller, belirli bir veri kümesiyle (örneğin, sorular ve seçenekler) ilgileniyor. Bu, kodun mantıksal olarak ayrılmasını ve anlaşılmasını kolaylaştırır. Ayrıca, bu yaklaşımda her controller kendi işlevini gerçekleştirir, bu da kodun bakımını ve genişletilmesini daha yönetilebilir kılar.
  • Geliştirilmiş Okunabilirlik: Her controller’ın spesifik bir işlevi olması, kodun okunabilirliğini artırır. İç içe geçmiş veri yapılarıyla çalışırken, farklı veri seviyeleri için ayrı controller’lar kullanmak, kodun anlaşılmasını ve bakımını kolaylaştırabilir.
  • Tekrar Kullanılabilirlik: Belirli işlevler için ayrı controller’lar oluşturmak, bu işlevlerin diğer projelerde veya uygulama bölümlerinde tekrar kullanılmasını kolaylaştırabilir.

Geliştirilebilecek Alanlarım

  1. Veri Tutarlılığı ve Entegrasyon: İç içe geçmiş JSON verileriyle çalışırken, veri güncellemeleri ve ilişkili verilerin tutarlılığını sağlamak bazen karmaşık olabilir. Controller’lar arasında veri tutarlılığını sağlamak için bir servis katmanı oluşturmak, güncelleme işlemlerini merkezileştirebilir ve tutarlılığı artırabilir.
  2. Kod Tekrarı: Her controller’da benzer kontrol ve veri doğrulama kodları bulunuyor. Ortak kod parçalarını bir hizmet katmanına veya yardımcı sınıflara taşımak, kod tekrarını azaltabilir ve bakımını kolaylaştırabilir.
  3. Hata Yönetimi: Hata yönetimini konsolide etmek ve kullanıcıya anlamlı hata mesajları sağlamak, genel kullanıcı deneyimini artırabilir. Her controller’daki hata yönetimini standartlaştırmak, tutarlılığı artırabilir.
  4. Testler: İyi bir test stratejisi, veri tutarlılığını ve işlevsel doğruluğu sağlamada kritik rol oynar. Her controller için ayrı testler yazmak, farklı veri seviyelerinde doğru işleyişi garanti edebilir.

Yazdığınım yaklaşımlar, kodun modülerliğini ve okunabilirliğini artırma açısından iyi bir temel sağlar. Ancak, iç içe geçmiş veri yapılarıyla çalışırken, veri tutarlılığını sağlamak ve kod tekrarını azaltmak gibi alanlarda geliştirmeler yapmam bence faydalı olabilir. Servis katmanı kullanımı ve kapsamlı testler gibi iyileştirmeler, kodumu daha sağlam ve sürdürülebilir hale getirebilir. Genel olarak, yaklaşımınız bir demo için yeterli ve doğru yönde olabilir, ancak proje kapsamında yaparken belirli iyileştirmeler ile daha da güçlendirilerek uygulama temelleri atılırsa daha hoş olabilir.

Benim bugünlük anlatacaklarım bu kadar!

Bir sonraki yazıda görüşmek üzere, sevgiler :)

Kaynakça

--

--

Kardel Rüveyda ÇETİN

Expert Software Engineer @DogusTeknoloji | Microsoft MVP | Mathematical Engineer | Speaker | Blogger | Founder&Organizer @thecoderverse