Oproti relační databázi má objektová databáze obrovskou výhodu, pokud se dodrží jistá pravidla, zajistí databáze ukládání a nahrávání potřebných dat automaticky. Současné objektové aplikace je navíc možné přizpůsobit pro objektovou databázi s minimálními změnami. To si také ukážeme na jednoduchém příkladu.
A teď přátelé něco úplně jiného.
Instalace
Aktuální verzi balíku najdete na zope.org. Pro její instalaci budete potřebovat python verze 2.1.2 nebo vyšší. Vlastní instalace je triviální:
-python setup.py build
-python setup.py test
-python setup.py install
Jednoduchý příklad použití
Vytvoříme jednoduchý obchod pro prodej kamenů. Pokud si pamatujete scénu s kamenováním ze Života Briana, přijde vám po vánocích určitě vhod. Ti, co neviděli, nechť běží do videopůjčovny.
class Stone: def __init__(self,name,count,price): self.name = name self.count = count self.price = price def sell(self,count): if((self.count - count) < 0): return -1 else: self.count = self.count - count return 1 def buy(self,count): self.count = self.count + count class Person: def __init__(self,name,budget): self.budget = budget self.name = name self.stones = {} def buy(self,item): if(item == -1 or (self.budget - (item.count * item.price)) <0): return -1 self.budget = self.budget - (item.count * item.price) if(self.stones.has_key(item.name)): self.stones[item.name].buy(item.count) else: self.stones[item.name] = item def sell(self,name,count): if(self.stones[name].sell(count)): price = 1.2 * (count * self.stones[name].price) self.budget = self.budget + price return Stone(name,count,self.stones[name].price*1.2) else: return -1 def __str__(self): return str(self.name) + ':' + str(self.stones.keys()) + ':' + str(self.budget) class Shop: def __init__(self): self.seller = Person('Trhovec',200) self.seller.buy(Stone('Placak',50,1)) self.seller.buy(Stone('Spicak',50,1)) self.customer = Person('Brian',30) def makeContract(self): self.customer.buy(self.seller.sell('Placak',5)) print 'Seller: ', self.seller, 'Customer: ',self.customer shop= Shop() shop.makeContract() shop.makeContract()
Použití objektové databáze ZODB
Náš příklad převedený do ZODB vypadá následovně. Změny jsou pro jednoduchost zvýrazněny.
importsys, Persistence, ZODB, ZODB.FileStorage class Stone(Persistence.Persistent): def __init__(self,name,count,price): self.name = name self.count = count self.price = price def sell(self,count): if((self.count - count) < 0): return -1 else: self.count = self.count - count return 1 def buy(self,count): self.count = self.count + count class Person(Persistence.Persistent): def __init__(self,name,budget): self.budget = budget self.name = name self.stones = {} def buy(self,item): if(item == -1 or (self.budget - (item.count * item.price)) <0): return -1 self.budget = self.budget - (item.count * item.price) if(self.stones.has_key(item.name)): self.stones[item.name].buy(item.count) else: self.stones[item.name] = item self._p_changed = 1 def sell(self,name,count): if(self.stones[name].sell(count)): price = 1.2 * (count * self.stones[name].price) self.budget = self.budget + price self._p_changed = 1 return Stone(name,count,self.stones[name].price*1.2) else: return -1 def __str__(self): return str(self.name) + ':' + str(self.stones.keys()) + ':' + str(self.budget) class Shop(Persistence.Persistent): def __init__(self): self.seller = Person('Trhovec',200) self.seller.buy(Stone('Placak',50,1)) self.seller.buy(Stone('Spicak',50,1)) self.customer = Person('Brian',30) def makeContract(self): self.customer.buy(self.seller.sell('Placak',5)) print 'Seller: ', self.seller, 'Customer: ',self.customer db=ZODB.DB(ZODB.FileStorage.FileStorage('storage.fs')) connection= db.open() root= connection.root() if(root.has_key('shop')): shop = root['shop'] shop.makeContract() else: shop = Shop() root['shop'] = shop shop.makeContract() get_transaction().commit()
Na prvním řádku jsou knihovny nutné pro funkci ZODB. Každá třída uchovávaná v ZODB musí být persistentní, neboli musí být převoditelná na bajtový kód beze ztráty informace (více například standardní modul pickle) a musí uvědomit databázový systém o změně stavu a přístupu ke svým objektům. Toho jednoduše docílíme odvozením od třídy Persistence.Persistent. Všechny ve třídě použité objekty musí být také persistentní (což základní typy jsou) a navíc musí dodržovat následující pravidla:
- objekty, jejichž jméno začíná na _p_, jsou rezervované pro funkci ZODB
- pokud jméno objektu začíná na _v_, nebude uchováván v objektové databázi (je určeno například pro soubory, zámky vláken atd.)
- metody __getattr__ , __setattr__ je zakázáno implementovat (vyšší dívčí).
- metody __getstate__ a __setstate__ jsou volány, pokud je čten nebo zapisován stav objektu (možné použití si ukážeme)
Pokud v databázi použijeme objekt, který není persistentní (to je případ slovníku stones), můžeme informovat databázový stroj o změně stavu objektu pomocí proměnné _p_changed.
Řádek db=ZODB.DB(ZODB.FileStorage.FileStorage(‚storage.fs‘)) určuje, jakou metodu má databáze použít pro uchování dat. V současné době jsou dostupné:
- FileStorage, databáze je uložena v jednom souboru
- BerkeleyStorage, uložení je implementováno pomocí berkley databáze (používá se sleepycat)
- RelationalDatabase, umožňuje uchovat data v relační databázi (zatím pouze experimentální)
- ClientStorage, součást ZEO umožňuje vzdálené připojení (popsáno níže)
Dále se připojíme do databáze a vytvoříme root objekt. Při prvním spuštění uložíme naši aplikaci do tohoto slovníku. Příště stačí pouze obnovit aplikaci z objektu root.
Poslední řádek potvrdí všechny transakce, bez tohoto příkazu nejsou změny uloženy do databáze. A to je vše, můžete jít vesele kamenovat.
Změna architektury aplikace
Dříve nebo později je v sebelépe navržené aplikaci třeba provést úpravy. Pokud měníte pouze kód třídy, není nutné žádný dodatečný zásah, protože kód třídy není v databázi uchován. Pokud se změna týká atributů třídy, můžete použít metodu __setstate__, která je volána ve chvíli, kdy je objekt zaváděn z databáze do paměti. Například pokud budeme chtít přidat atribut účinnost pro objekt Stone, vytvoříme metodu __setstate__ následovně:
def __setstate__(self): if(not dir(self).index('effect')): self.effect = 1.2
ZOE – Zope Enterprise Object
Hroznýš je společenské zvíře, takže časem budete potřebovat jednotlivá terária spojovat. ZEO umožňuje nasadit objektovou databázi v distribuovaném prostředí. Na straně serveru vytváří tcpserver, který se připojí do lokální databáze. Na straně klienta je ClientStorage, která se tváří jako jakékoliv jiné úložiště a velmi agresivní keš. Vzhledem k tomu, že je ZEO určen pro aplikační server ZOPE, je optimalizován pro rychlé čtení objektů a pomalejší zápis. To může u některých aplikací způsobovat problémy. Použití ZEO je velmi jednoduché:
Na straně serveru:
pythonlib/python/ZEO/start.py -p 7777 /tmp/storage.fs
Na straně serveru:
from ZEO import ClientStorage from ZODB import DB storage = ClientStorage.ClientStorage( ('server_hostname', 7777) ) db = DB( storage ) connection = db.open()
Dále zůstává kód shodný.
Závěr
ZODB obsahuje další zajímavé vlastnosti, o kterých jsem se nezmínil. Například transakční systém podporující subtransakce a dvoufázový commit, versioning umožňující vytváření nezávislé verze databáze. Pokud vás tyto možnosti zajímají, podívejte se na reference. Osobně bych ZODB doporučil spíše do menších projektů, kde se pracuje se stovkami objektů (takové nasazení máme testované). Velmi zajímavá je rychlost vývoje s použitím ZODB, zvláště pokud o bytí a nebytí projektů rozhoduje čas. Pokud máte někdo zkušenosti nebo zprávy o masivních projektech, dejte mi vědět. Také by mě zajímalo, zda někdo používáte nějakou objektovou databázi pro javu. Doufám, že jsem zbytečně nemarnil váš drahocenný čas, pokud ano, tak si najděte nějaký pěkný placák ;-).
Refence
- Domovské stránky projektu ZODB
- Stránky projektu ZEO
- Velmi dobrý úvod do ZODB od Jima Fultona
- Základní FAQ o objektových databázích
- Enterprise objektová databáze (a nejen to) pro krále objektových jazyků smalltalk