Bácsi László(lackac) blogbejegyzésében olvastam először a CoucDB-ről, és annyira megtetszett a koncepciója, hogy gondoltam Én is teszek egy mélyebb ismerkedést vele - mintegy kikapcsólódásként két záróvizsga tétel között.
A következő bejegyzést két részre bontottam, az elkövetkezőben a CouchDB koncepcióját ismerheted meg működés közben, példákon keresztül. Míg a cikk második része megmutatja hogyan használhatjuk a CoucDB-t Rubyban.
A CouchDB több szempontból sem mindennapi adatbázis kezelő. Mindenekelőtt szakít a hagyományos relációs adatbázisok koncepciójával: az adatainkat séma-mentes formában tárolhatjuk benne. A táblázatok helyett dokumentumaink vannak, amelyek mezőnév/attribútum/tulajdonság és a hozzájuk tartozó adatok formájában tárolják az információkat. Mivel nincsenek előre meghatározott sémáink az egyes dokumentumok bármilyen adatpárt felvehetnek.
Másrészt a CouchDB-vel a HTTP protokollon keresztült kommonikálhatunk az adatbázisunkkal! Ráadásul a HTTP protokoll használata és az URL-k kiosztása a REST elveire épül: az adatbázisaink benne a dokumentumaikkal lesznek az erőforrásaink, a HTTP metódusok pedig a művelet célját jelölik majd. De, hogy a dolgok ne legyenek ennyire egyszerűek: az adatok kinyerése és felvitele JSON formátumban történik.
Ezúttal nem sok kedvem volt manuálisan, forrásból telepíteni a sok függőség miatt és mivel a kipróbáláson volt a hangsúly, így a MacPorts-on keresztül tettem fel a CouchDB-t. Ehhez először érdemes ellenőrizni, hogy naprakész-e a csomagok(portok) listája:
sudo port selfupdateÉn erről megfeledkeztem, és egy korábbi (0.7.2-es) verzió települt fel, ami persze köszönő viszonyban sem állt a dokumentációban állítottakkal, sok dolog másként működött, ugyanis az új verzió óta (0.8.1) renteget változás történt.
Majd telepítsük a couchdb csomagját:
sudo port install couchdbItt máris tarthatunk egy kis szünetet, ugyanis az Erlang csomag telepítése eltarthat egy ideig.
Sikeres telepítés után indítsuk el a CouchDB szervert a következő paranccsal:
sudo couchdb
Elsőként érdemes egy böngésző segítségével a CouchDB webes kezelőfelületét kipróbálni a http://localhost:5984/_utils/index.html címen. Itt létrehozhatunk új adatbázisokat és dokumentumokat, valamint feltölthetjük őket adatokkal. Érdemes a Firebug segítségével követni a felhasználói felület parancsait, hogy lássuk a háttérben milyen HTTP kéréseket küld és miképp adja meg az adatokat JSON formátumban.
Futon - a CouchDB web alapú interfésze
Mielőtt Rubyban kezdenénk el használni a CouchDB-t érdemes a HTTP vagy REST API-n átrágni magunkat, még akkor is, ha tisztában vagyunk ezeknek az elveivel, van ugyanis pár elem, ami nagyban meghatározza az elküldendő adatok formátumát. Ehhez próbáljunk parancssorból kommunikálni az adatbázissal a telnet vagy a curl segítségével.
Először hozzunk létre egy adatbázist a PUT metódussal:
curl -i -X PUT http://localhost:5984/blog/A curl i kapcsolójával kiírathatjuk a HTTP válasz fejlécét, az X el pedig a HTTP kérés metódusát adhatjuk meg.
Hasonló választ kell, hogy kapjunk:
HTTP/1.1 201 Created Content-Type: text/plain;charset=utf-8
Most hogy létrehoztuk az adatbázist, adjunk hozzá egy dokumentumot, és helyezzünk el benne különféle adatokat.
curl -i -X PUT http://localhost:5984/blog/couchdb_cikk \
-d '{ "author":"Csiszar Attila", "title":"CouchDB cikk"}'
Ezután kérdezzük le a GET metódussal:
Ezúttal a telnet-et használtam mivel a curl valamiért nem akaródzott a HTTP válasz törzsét kiolvasni.$ telnet localhost 5984
GET http://localhost:5984/blog/couchdb_cikk/ HTTP/1.0
HTTP/1.1 200 OK
Content-Type: text/plain;charset=utf-8
Etag: 2482599723
{"_id":"couchdb_cikk","_rev":"2482599723","author":"Csiszar Attila","title":"CouchDB cikk"}
Mint látjuk a CouchDB két speciális mezőt rendelt a dokumentumunkhoz: az idt, mint azonosítót amely felvette az erőforrásunk címét - ezt a kérésünkkel adtuk meg -, valamint hozzárendelt egy rev tulajdonságot is.
A _ jellel kezdődő tulajdonságok a CouchDB számára fentartott értékek, nem módosíthatjuk őket.Az azonosítóknak adatbázis szinten egyedinek kell lenniük, hiszen ezt használjuk az URL-kben is - gyakorlatilag bármilyen karaktersorozatot felvehetnek. Ha nem szeretnénk az egyedi azonosítók megadásával bajlódni, használhatjuk a POST metódust dokumentumok létrehozására, mivel ez egyedi azonosítót fog rendelni a dokumentumhoz. Mivel POST metódus esetén nem konkrét erőforrásra, hanem erőforrás-kollekcióra szoktunk hivatkozni, a címünknek ezúttal az adatbázis elérését kell megadnunk:
curl -i -X POST http://localhost:5984/blog/ \
-d '{ "author":"Csiszar Attila", "title":"Másik CouchDB cikk"}'
A válasszal visszakapjuk a létrehozott dokumentum (erőforrás) azonosítóját:
HTTP/1.1 201 Created
Content-Type: text/plain;charset=utf-8
{"ok":true,"id":"19BF2F4BF267E8CCF120A2B3EB444C19","rev":"3269339116"}
A másik fontos mező, a rev érték: ez jelöli a dokumentum aktuális változatát. Ez még fontos lesz a változtatások felvitelekor, ugyanis azt meg kell adnunk az adataink között, lássuk:
curl -i -X PUT http://localhost:5984/blog/couchdb_cikk \
-d '{ "_rev":"2482599723", "body": "Ez hosszu cikk lesz..." }'
Ha a megadott azonosító nem egyezik az adatbázisban lévő dokumentum aktuális értékével, akkor a változtatások nem kerülnek érvényre és egy 409 Conflict választ kapunk vissza. A rev értéke megjelenik az Etag HTTP fejlécben is, így remekül használhatjuk konfliktuskezelésre.
A fentiekből látható, hogy a CouchDB REST APIjában a dokumentumaink - tehát az adataink elvi tárolói - lesznek az erőforrásaink, az elérésükhöz(URL) az azonosítóikat használjuk fel, és a megfelelő HTTP metódusokkal tudunk rajtuk műveleteket végezni.
A CouchDB további érdekessége, hogy a dokumetumaink mellé lehetőségünk van mellékleteket csatolni, csakúgy, mint az e-mailek esetében. Ezt a speciális _attachments tulajdonsággal tehetjük meg és az adatoknak base64 enkódolásúaknak kell lenniük.
curl -i -X PUT http://localhost:5984/blog/couchdb_cikk \
-d '{ "_id":"couchdb_cikk", "_rev": "2812696990", "_attachments": { "csatolt.txt": { "content_type":"text\/plain", "data":"Q3NhdG9sdCBkb2t1bWVudHVt"} } }'
Érdekes működés, hogy bár az URL-ben szerepel a dokumentum azonosítója azt ugyanúgy meg kell adnunk az átadott JSON adatban is.
Az átadott JSON adat kicsit áttekinthetőbben:
{ "_id":"couchdb_cikk",
"_rev": "2812696990",
"_attachments":
{
"csatolt.txt":
{
"content_type":"text\/plain",
"data":"Q3NhdG9sdCBkb2t1bWVudHVt"
}
}
}
Ismét csak a telnet segítségével kérdezzük le a dokumentumot.
GET /blog/couchdb_cikk/ HTTP/1.0
{
"_id":"couchdb_cikk",
"_rev":"3172282319",
"_attachments":
{
"csatolt.txt":
{
"stub":true,
"content-type":"text\/plain",
"length":18
}
}
}
A csatolmányokat nem kapjuk vissza a dokumentum lekérésekor automatikusan - ezt jelzi a stub:true érték - azokat külön URL-ken, a nevükön keresztül érhetjük el a dokumentum alatt, például így: /blog/couchdb_cikk/csatolt.txt.
GET /blog/couchdb_cikk/csatolt.txt HTTP/1.0 HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 18 Csatolt dokumentum
Ezt az URL elérést felhasználhatjuk a dokumentumok módosítására, sőt feltöltésére is. Így nem kell a JSON formátummal és base64 enkódolással bajlódni, mindössze a Content-Type HTTP fejléccel kell jeleznünk az adatunk formátumát.
PUT /blog/couchdb_cikk/kep.jpg Content-Type:image/jpeg Content-Length:123 <JPEG adat>
A csatolmányok egyik érdekes felhasználása lehet, hogy képeket tároljunk adatbázisszinten, amelyet azután egyszerűen meg tudunk jeleníteni a weboldalainkon a HTTP-s elérésnek köszönhetően.
A CouchDB-ben a nézetek felelnek meg a relációs adatbázisok lekérdezéseinek. A nézetek JavaScript nyelven írt kódok.
A nézetek kipróbálására érdemes a fentebb már említett http://localhost:5984/_utils/index.html címet megkeresni, ha belépünk az adatbázisunkba jobb oldalt felül találunk egy Select View legördülő listát innen pedig válasszuk a Custom query... pontot - így kicsit interaktívabb módon ellenőrizhetjük a változtatásaink közvetlen eredményét.
Nézzünk egy egyszerűbb nézetet:
function(doc) {
map(null, doc)
}
Amint látható a nézetek egyszerű JavaScript funkciók, paraméterként pedig megkapják a bejárás során épp aktuális dokumentumot - doc változó. A funkción belül bármilyen logikát elhelyezhetünk, egy a lényeg, ha szeretnénk egy dokumentumot visszaadni a nézetben akkor az emit funkciót kell használnunk. Ennek első paramétereként megadhatjuk a dokumentum egy tulajdonságát, a nézet dokumentumait ez alapján fogja sorba rendezni, második paraméterként a dokumentum visszaadott tulajdonságait szűkíthetjük le. Lásd a következő példát:
function(doc) {
emit(doc.title, {"Cim":doc.title, "Datum":doc.date, "ID":doc.id })
}
Ebben a nézetben a címek alapján lesz rendezve az összes dokumentum, és csak a megadott tulajdonságok (Title, Date, Id) szerepelnek mellettük, a megadott nevekkel.
A nézeteket kétféleképpen "érhetjük" el a HTTP API-n keresztül. Egyrészt POST kérésként átadhatjuk a megírt nézet függvényt az /{dbnév}/_slow_view URL-nek.
curl -X POST \
-H 'Content-Type: text/javascript' \
-d 'function(doc){map(null, {"Title":doc.title, "Date":doc.date, "Id":doc.id }) }' \
http://localhost:5984/blog/_slow_view
A visszakapott válasz:
{
"total_rows":3,
"offset":0,
"rows":
[
{
"id":"19BF2F4BF267E8CCF120A2B3EB444C19",
"key":null,
"value":
{
"Title":"CouchDB cikk"
}
},
{
"id":"B7CFF70741ED178736D044F342B085EE",
"key":null,
"value":
{
"Title":"Masik CouchDB cikk"
}
}
]
}
A CouchDB terminológusában ezeket ideiglenes nézeteknek nevezzük, és inkább csak tesztelési célokra ajánlott használatuk - erre is utal a _\_slow\_view_ URL elnevezés. Ennél sokkal hatékonyabb, ha a nézeteinket is dokumentumokként, az adatbázisban tároljuk el: ekkor azok minden adatbázisban történt változás hatására - tehát például ha módosítunk egy dokumentumot - automatikusan lefutnak, az eredményeik pedig előre eltárolódnak! Ez sokkal gyorsabb működést eredményez.
A nézetek mint dokumentumok a következő formában tárolódnak:
{
"_id": "_design/posts",
"_rev": "3294989902",
"language": "javascript",
"views":
{
"all_by_title":
{
"map" : "function(doc){ emit(doc.title, {'Title':doc.title, 'Date':doc.date, 'Id':doc.id })}"
},
"all_by_author":
{
"map" : "function(doc){ emit(doc.author, doc)}"
}
}
}
A kulcs a nézet-dokumentumok elnevezésében adódik: mindegyiket a _design/ formával kell kezdenünk. A nézet-dokumentumok nemcsak egy, hanem tetszőleges számú nézetet tartalmazhatnak, elkülönítésükben az egyedi elnevezésük segít. A fenti példában például az első, all_by_title cím szerint, míg a második, all_by_author szerző szerint fogja visszadni a dokumentumjainkat.
A fenti nézet-dokumentumot egyszerűen hozhatjuk létre az adatbázisunkban:
curl -X PUT \
-d '{ "language": "javascript", "views":{ "all_by_title": { "map" : "function(doc){ emit(doc.title,{'Title':doc.title, 'Date':doc.date, 'Id':doc.id })}" }, "all_by_author":{ "map" : "function(doc){ emit(doc.author, doc)}" } }}' \
http://localhost:5984/blog/_design/posts
Válaszként pedig valami hasonlót kell kapnunk:
{"ok":true,"id":"_design\/posts","rev":"3294989902"}
A tárolt nézetek eredményét ezután egyszerűen egy GET kéréssel olvashatjuk ki, de erre már a _view/ útvonalat kell használnunk. Mivel eredetileg a _design/posts azonosítót adtuk a nézetet tároló dokumentumnak, a teljes elérést a _view/posts/all_by_title URL fogja szolgáltatni.
GET http://localhost:5984/blog/_view/posts/all_by_title HTTP/1.0
{
"id":"4fbdaedba5b66d4894191b8a52895c76",
"key":"CouchDB cikk",
"value":
{
"Title":"CouchDB cikk"
}
},
{
"id":"5126ff560acb43f444149b502fd1484b",
"key":"CouchDB cikk",
"value":
{
"Title":"CouchDB cikk"
}
}
A fenti csak néhány példa volt a CouchDB REST API-jának a használatára, a teljesség igénye nélkül próbáltam bemutatni a főbb működést. Részletesebb információkért érdemes átrágni magunkat a CouchDB teljes referenciáján, ha szeretnénk megismerni az összes lehetőséget.
Mindezt azonban érdemes volt összefoglani, mielőtt rátérnénk arra, hogy hogyan is használhatjuk a CouchDB-t Rubyban.
A bejegyzés folytatását: A CouchDB találkozása a Rubyval ezen a linken keresztül érheted el.CouchDB kipróbálása Firebugban
Érdekes projektek és sok-sok link a CouchDB-ről
Nyolc részes cikksorozat szintén a CouchDB használatáról Rubyban