CouchDB Belgelerini Modelleme İlkeleri


120

Bir süredir cevaplamaya çalıştığım ama çözemediğim bir sorum var:

CouchDB belgelerini nasıl tasarlar veya bölersiniz?

Örneğin bir Blog Yazısını ele alalım.

Bunu yapmanın yarı "ilişkisel" yolu, birkaç nesne oluşturmak olacaktır:

  • İleti
  • kullanıcı
  • Yorum Yap
  • Etiket
  • Pasaj

Bu çok mantıklı. Ama aynı şeyi modellemek için couchdb'yi (harika olduğu tüm nedenlerden dolayı) kullanmaya çalışıyorum ve bu son derece zor.

Dışarıdaki blog yazılarının çoğu, size bunun nasıl yapılacağına dair kolay bir örnek veriyor. Temelde aynı şekilde bölüyorlar, ancak her belgeye 'keyfi' özellikler ekleyebileceğinizi söylüyorlar ki bu kesinlikle güzel. Yani CouchDB'de buna benzer bir şeye sahip olacaksınız:

  • Gönderi (belgede etiketler ve snippet'ler "sözde" modellerle)
  • Yorum Yap
  • kullanıcı

Hatta bazı insanlar Yorum ve Kullanıcıyı oraya atabileceğinizi bile söylerdi, böylece şuna sahip olursunuz:


post {
    id: 123412804910820
    title: "My Post"
    body: "Lots of Content"
    html: "<p>Lots of Content</p>"
    author: {
        name: "Lance"
        age: "23"
    }
    tags: ["sample", "post"]
    comments {
        comment {
            id: 93930414809
            body: "Interesting Post"
        } 
        comment {
            id: 19018301989
            body: "I agree"
        }
    }
}

Bu çok hoş görünüyor ve anlaşılması kolay. Ayrıca, Kullanıcılar ve Etiketler ile aynı şekilde Yorum modellerine dahil etmek için tüm Gönderim belgelerinizden yalnızca Yorumları çıkaran görünümleri nasıl yazabileceğinizi de anlıyorum.

Ama sonra "neden tüm sitemi tek bir belgeye koymuyorsunuz?" Diye düşünüyorum:


site {
    domain: "www.blog.com"
    owner: "me"
    pages {
        page {
            title: "Blog"
            posts {
                post {
                    id: 123412804910820
                    title: "My Post"
                    body: "Lots of Content"
                    html: "<p>Lots of Content</p>"
                    author: {
                        name: "Lance"
                        age: "23"
                    }
                    tags: ["sample", "post"]
                    comments {
                        comment {
                            id: 93930414809
                            body: "Interesting Post"
                        } 
                        comment {
                            id: 19018301989
                            body: "I agree"
                        }
                    }
                }
                post {
                    id: 18091890192984
                    title: "Second Post"
                    ...
                }
            }
        }
    }
}

Bununla istediğinizi bulmak için kolayca görünümler oluşturabilirsiniz.

O halde sorduğum soru, belgeyi ne zaman daha küçük belgelere böleceğinizi veya belgeler arasında ne zaman "İLİŞKİ" kuracağınızı nasıl belirlersiniz?

Bence bu şekilde bölünmüş olsaydı, çok daha "Nesneye Yönelik" ve Değer Nesnelerine eşlemek daha kolay olurdu:


posts {
    post {
        id: 123412804910820
        title: "My Post"
        body: "Lots of Content"
        html: "<p>Lots of Content</p>"
        author_id: "Lance1231"
        tags: ["sample", "post"]
    }
}
authors {
    author {
        id: "Lance1231"
        name: "Lance"
        age: "23"
    }
}
comments {
    comment {
        id: "comment1"
        body: "Interesting Post"
        post_id: 123412804910820
    } 
    comment {
        id: "comment2"
        body: "I agree"
        post_id: 123412804910820
    }
}

... ancak sonra bir İlişkisel Veritabanı gibi görünmeye başlar. Ve çoğu zaman "belgede-tüm-site" gibi görünen bir şeyi miras alırım, bu yüzden onu ilişkilerle modellemek daha zordur.

İlişkisel Veritabanları ile Belge Veritabanları'nın nasıl / ne zaman kullanılacağı hakkında birçok şey okudum, bu yüzden buradaki ana sorun bu değil. Daha çok merak ediyorum, CouchDB'de verileri modellerken uygulanacak iyi bir kural / ilkenin ne olduğu.

Diğer bir örnek de XML dosyaları / verileridir. Bazı XML verilerinde 10+ düzey derinlikte iç içe geçme var ve JSON'u ActiveRecord, CouchRest veya başka herhangi bir Nesne İlişkisel Eşleştirici'den işlemek istediğim aynı istemciyi (örneğin, Rails üzerinde Ajax veya Flex) kullanarak görselleştirmek istiyorum. Bazen, aşağıdaki gibi tüm site yapısını oluşturan devasa XML dosyaları alıyorum ve Rails uygulamamda kullanmak için bunları Değer Nesneleri ile eşlemem gerekiyor, böylece verileri serileştirmenin / seriyi kaldırmanın başka bir yolunu yazmam gerekmiyor :


<pages>
    <page>
        <subPages>
            <subPage>
                <images>
                    <image>
                        <url/>
                    </image>
                </images>
            </subPage>
        </subPages>
    </page>
</pages>

Yani genel CouchDB soruları:

  1. Belgelerinizi (ilişkiler vb.) Bölmek için hangi kuralları / ilkeleri kullanıyorsunuz?
  2. Tüm siteyi tek bir belgeye koymak doğru mu?
  3. Öyleyse, keyfi derinlik seviyelerine sahip belgeleri serileştirmeyi / seriyi kaldırmayı nasıl halledersiniz (yukarıdaki büyük json örneği veya xml örneği gibi)?
  4. Yoksa onları VO'lara dönüştürmüyor musunuz, sadece "bunlar Object-Relational Map'e çok iç içe, bu yüzden onlara ham XML / JSON yöntemlerini kullanarak erişeceğim" mi diye mi karar veriyorsunuz?

Yardımınız için çok teşekkürler, verilerinizi CouchDB ile nasıl böleceğim konusunu "bundan sonra böyle yapmalıyım" demek benim için zor oldu. Yakında oraya varmayı umuyorum.

Aşağıdaki siteleri / projeleri inceledim.

  1. CouchDB'de Hiyerarşik Veriler
  2. CouchDB Wiki
  3. Sofa - CouchDB Uygulaması
  4. CouchDB Kesin Kılavuz
  5. PeepCode CouchDB Ekran Kaydı
  6. CouchRest
  7. CouchDB README

... ama bu soruyu hala yanıtlamadılar.


2
vay, buraya bütün bir makale
yazdın

8
hey, bu iyi bir soru
elmarco

Yanıtlar:


26

Buna şimdiden bazı harika yanıtlar verilmişti, ancak viatropos tarafından açıklanan orijinal durumla çalışma seçeneklerinin karışımına daha yeni CouchDB özellikleri eklemek istedim.

Belgeleri ayırmanın kilit noktası, çatışmaların olabileceği yerlerdir (daha önce belirtildiği gibi). Tamamen ilgisiz güncellemeler için tek bir revizyon yolu elde edeceğinizden (örneğin, tüm site belgesine bir revizyon ekleyen yorum ekleme) tek bir belgede büyük ölçüde "karışık" belgeleri bir arada tutmamalısınız. Çeşitli, daha küçük belgeler arasındaki ilişkileri veya bağlantıları yönetmek ilk başta kafa karıştırıcı olabilir, ancak CouchDB, farklı parçaları tek yanıtlarda birleştirmek için çeşitli seçenekler sunar.

İlki, görünüm harmanlamadır. Bir eşleme / küçültme sorgusunun sonuçlarına anahtar / değer çiftleri gönderdiğinizde, anahtarlar UTF-8 harmanlamasına göre sıralanır ("a", "b" den önce gelir). Ayrıca haritadan çıkış karmaşık tuşları / JSON diziler olarak azaltabilir: ["a", "b", "c"]. Bunu yapmak, dizi anahtarlarından oluşturulmuş bir tür "ağaç" eklemenize izin verir. Yukarıdaki örneğinizi kullanarak, post_id'yi, ardından referans verdiğimiz şeyin türünü ve ardından kimliğini (gerekirse) çıkarabiliriz. Daha sonra, başvurulan belgenin kimliğini döndürülen değerdeki bir nesneye çıkarırsak, bu belgeleri haritaya dahil etmek / çıktıyı azaltmak için 'include_docs' sorgu parametresini kullanabiliriz:

{"rows":[
  {"key":["123412804910820", "post"], "value":null},
  {"key":["123412804910820", "author", "Lance1231"], "value":{"_id":"Lance1231"}},
  {"key":["123412804910820", "comment", "comment1"], "value":{"_id":"comment1"}},
  {"key":["123412804910820", "comment", "comment2"], "value":{"_id":"comment2"}}
]}

Aynı görünümü '? İnclude_docs = true' ile istemek, 'değer' nesnesinde başvurulan '_id' öğesini kullanacak bir 'doc' anahtarı ekleyecektir veya bu 'değer' nesnesinde yoksa, kullanacaktır. satırın çıktığı belgenin "_id" si (bu durumda "post" belgesi). Lütfen bu sonuçların yaymanın yapıldığı kaynak belgeye referans veren bir 'id' alanı içereceğini unutmayın. Yer ve okunabilirlik için dışarıda bıraktım.

Daha sonra sonuçları tek bir gönderinin verilerine göre filtrelemek için "start_key" ve "end_key" parametrelerini kullanabiliriz:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
Hatta belirli bir tür için listeyi özellikle çıkarın:
? start_key = ["123412804910820", "yorum"] & end_key = ["123412804910820", "yorum", {}]
Bu sorgu parametresi kombinasyonları mümkündür çünkü boş bir nesne (" {}") her zaman harmanlamanın altındadır ve null veya "" her zaman en üsttedir.

Bu durumlarda CouchDB'nin ikinci yararlı eki _list işlevidir. Bu, yukarıdaki sonuçları bir tür şablonlama sistemi aracılığıyla çalıştırmanıza (HTML, XML, CSV veya başka bir şey istiyorsanız) veya bir gönderinin tüm içeriğini (dahil olmak üzere) talep edebilmek istiyorsanız birleşik bir JSON yapısı oluşturmanıza olanak tanır. yazar ve yorum verileri) tek bir istekle ve istemci tarafı / UI kodunuzun ihtiyaç duyduğu şeyle eşleşen tek bir JSON belgesi olarak döndürülür. Bunu yapmak, gönderinin birleşik çıktı belgesini şu şekilde talep etmenize olanak tanır:

/ db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true
_List işleviniz (bu durumda "birleştirilmiş" olarak adlandırılır), haritayı görüntüle / azaltın (bu durumda "gönderiler" olarak adlandırılır) sonuçlarını alır ve bunları, sizin içerik türünüzdeki HTTP yanıtını geri gönderecek bir JavaScript işlevi aracılığıyla çalıştırır. ihtiyaç (JSON, HTML, vb.).

Bunları birleştirerek, belgelerinizi güncellemeler, çakışmalar ve çoğaltma için yararlı ve "güvenli" bulduğunuz düzeyde bölebilir ve ardından istendiğinde gerektiğinde yeniden bir araya getirebilirsiniz.

Umarım yardımcı olur.


2
Bunun Lance'e yardım edip etmediğinden emin değilim, ama bir şeyi biliyorum; bana kesinlikle çok yardımcı oldu! Bu harika!
Mark

17

Bunun eski bir soru olduğunu biliyorum, ama aynı soruna en iyi yaklaşımı bulmaya çalışırken karşılaştım. Christopher Lenz , CouchDB'de "birleşmeleri" modelleme yöntemleri hakkında güzel bir blog yazısı yazdı . Çıkardıklarımdan biri şuydu: "İlgili verilerin birbiriyle çelişmeden eklenmesine izin vermenin tek yolu, ilgili verileri ayrı belgelere koymaktır." Yani, basitlik uğruna, "denormalizasyona" eğilmek istersiniz. Ancak belirli durumlarda birbiriyle çelişen yazılar nedeniyle doğal bir engele çarpacaksınız.

Gönderiler ve Yorumlar örneğinizde, tek bir gönderi ve tüm yorumları tek bir belgede yaşıyorsa, aynı anda iki kişinin yorum göndermeye çalışması (yani belgenin aynı revizyonuna karşı) bir çatışmaya neden olur. Bu, "sitenizin tamamı tek bir belgede" senaryosunda daha da kötüleşir.

Bu yüzden, temel kuralın "canınızı yakana kadar normalden farklılaştırılması" olacağını düşünüyorum, ancak "inciteceği" nokta, bir belgenin aynı revizyonuna karşı birden fazla düzenlemenin gönderilme olasılığının yüksek olduğu yerdir.


İlginç yanıt. Bunu akılda tutarak, makul derecede yüksek trafikli herhangi bir sitenin tek bir belgede tek bir blog yazısı için tüm yorumlara sahip olup olmayacağı sorgulanmalıdır. Bunu doğru okursam, bu, ardı ardına hızlı bir şekilde yorum ekleyen her insanınız olduğunda, çatışmaları çözmeniz gerekebileceği anlamına gelir. Tabii ki, bunu tetiklemek için arka arkaya ne kadar hızlı olmaları gerektiğini bilmiyorum.
pc1oad1etter

1
Yorumların Couch'daki belgenin bir parçası olması durumunda, eşzamanlı yorum gönderileri çakışabilir çünkü sürüm oluşturma kapsamınız tüm yorumlarıyla birlikte "postadır". Nesnelerinizin her birinin belge koleksiyonları olması durumunda, bunlar yalnızca gönderiye geri bağlantıları olan ve çarpışma endişesi olmayan iki yeni 'yorum' belgesi olur. Ayrıca, "nesne yönelimli" belge tasarımında görünüm oluşturmanın basit olduğunu da belirtmek isterim - örneğin bir gönderinin anahtarını iletirsiniz, sonra o gönderi için bazı yöntemlere göre sıralanmış tüm yorumları yayınlarsınız.
Riyad Kalla

16

Kitap akılda belgeleriniz güncellenmiş olabilir sıklığını tutarken, "acıyor" kadar denormalize, eğer doğru geri çağırmak, diyor.

  1. Belgelerinizi (ilişkiler vb.) Bölmek için hangi kuralları / ilkeleri kullanıyorsunuz?

Genel bir kural olarak, söz konusu öğeyle ilgili bir sayfayı görüntülemek için gereken tüm verileri ekliyorum. Başka bir deyişle, birine vereceğiniz gerçek dünyadaki bir kağıt parçasına yazdıracağınız her şey. Örneğin, bir hisse senedi fiyat belgesi, numaralara ek olarak şirketin adını, borsasını, para birimini içerecektir; bir sözleşme belgesi, karşı tarafların adlarını ve adreslerini, tarihler ve imzalayanlarla ilgili tüm bilgileri içerecektir. Ancak farklı tarihlerdeki hisse senedi fiyatları ayrı belgeler oluşturur, ayrı sözleşmeler ayrı belgeler oluşturur.

  1. Tüm siteyi tek bir belgeye koymak doğru mu?

Hayır, bu aptalca olurdu çünkü:

  • her güncellemede tüm siteyi (belgeyi) okumanız ve yazmanız gerekir ve bu çok verimsizdir;
  • herhangi bir önbelleğe alma işleminden yararlanamazsınız.

3
Benimle biraz konuya girdiğin için teşekkürler. "Söz konusu öğeyle ilgili bir sayfayı görüntülemek için gereken tüm verileri dahil et" fikrini aldım, ancak bunu uygulamak hala çok zor. Bir "sayfa", Yorumlar sayfası, Kullanıcılar sayfası, Gönderiler sayfası veya Yorumlar ve Gönderiler sayfası vb. Olabilir. O halde bunları esas olarak nasıl bölerdiniz? Ayrıca Sözleşmenizin Kullanıcılar ile görüntülenmesini de sağlayabilirsiniz. Onları ayrı tutmak mantıklı olan 'form benzeri' belgeleri alıyorum.
Lance Pollard

6

Bence Jake'in cevabı, kapsam belirleme kararını vermenize yardımcı olabilecek CouchDB ile çalışmanın en önemli yönlerinden birini çiviliyor: çatışmalar.

Gönderinin kendisinin bir dizi özelliği olarak yorumlarınız varsa ve içinde bir sürü büyük 'post' belge bulunan bir 'post' DB'niz varsa, Jake ve diğerlerinin doğru bir şekilde belirttiği gibi, üzerinde bir senaryo hayal edebilirsiniz. iki kullanıcının posta belgesine aynı anda düzenlemeler gönderdiği ve bu belge için bir çakışma ve sürüm çakışmasına neden olan gerçekten popüler bir blog yazısı.

YAN: Bu makalenin işaret ettiği gibi , bu dokümanı her talep ettiğinizde / güncellediğinizde, dokümanı bütünüyle almanız / ayarlamanız gerektiğini, dolayısıyla sitenin tamamını veya çok sayıda gönderiyi temsil eden büyük bir dokümanın etrafından dolaşmanız gerektiğini de göz önünde bulundurun. üzerine yapılan yorumlardan kaçınmak isteyebileceğiniz bir sorun olabilir.

Gönderilerin yorumlardan ayrı olarak modellenmesi ve iki kişinin bir hikaye hakkında yorum yapması durumunda, bunlar basitçe o DB'deki iki "yorum" dokümanı haline gelir ve hiçbir çatışma sorunu yoktur; "açıklama" veritabanına iki yeni yorum eklemek için yalnızca iki PUT işlemi.

Ardından, bir gönderi için yorumları size geri veren görünümleri yazmak için, postID'yi iletir ve ardından bu ana gönderi kimliğine başvuran tüm yorumları mantıksal bir sırayla sıralanmış olarak yayınlarsınız. Belki de ana gönderiyi ve sonuçların nasıl sıralanmasını istediğinizi veya bu satırlar boyunca bir şeyi belirtmek için "yorumlar" görünümünün anahtarı olarak [postID, byUsername] gibi bir şey iletebilirsiniz.

MongoDB belgeleri biraz farklı şekilde ele alır, dizinlerin bir belgenin alt öğeleri üzerine kurulmasına izin verir, böylece MongoDB posta listesinde aynı soruyu görebilirsiniz ve birisi "yorumları sadece ana yazının bir özelliği yap" diyebilir.

Mongo'nun yazma kilitleme ve tek ana doğası nedeniyle, yorum ekleyen iki kişinin çelişen revizyon sorunu orada ortaya çıkmaz ve belirtildiği gibi içeriğin sorgulama yeteneği, sub- indeksleri.

Bununla birlikte, her iki DB'deki alt öğeleriniz çok büyük olacaksa (diyelim ki 10 bin yorum), bu ayrı öğeleri yapmanın her iki kampın da önerisi olduğuna inanıyorum; Bir belgenin ve alt unsurlarının ne kadar büyük olabileceğine dair bazı üst sınırlar olduğu için bunun Mongo için geçerli olduğunu kesinlikle gördüm.


Çok yararlı. Teşekkürler
Ray Suelzer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.