Hlavní navigace

ZODB, ZEO aneb vytápěné terárium pro vaše hroznýše

9. 1. 2003
Doba čtení: 6 minut

Sdílet

Dostat objektového hroznýše do relačního terária není vždy jednoduché, a pokud se to povede, nevypadá to zrovna esteticky. Proto firma Digital Creation vyvinula jako součást projektu Zope plnokrevnou objektovou databázi. V minulém roce se objevila také její verze standalone, takže nic nebrání jejímu použití v nezávislých projektech.

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.Per­sistent. 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(ZOD­B.FileStorage­.FileStorage(‚sto­rage.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()

skoleni

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