zope component architechture

22
Zope Component Architecture There should be a silver bullet! Let's try to look for it with ZCA at least :) Anatoly Bubenkov @bubenkoff Paylogic Groningen Office 19.08.2013

Upload: anatoly-bubenkov

Post on 23-Jan-2015

1.373 views

Category:

Technology


0 download

DESCRIPTION

There should be a silver bullet in architecture! Let's try to look for it with Zope Component Architecture.

TRANSCRIPT

Page 1: Zope component architechture

Zope Component Architecture

There should be a silver bullet! Let's try to look for it with ZCA at least :)

Anatoly Bubenkov@bubenkoffPaylogic Groningen Office19.08.2013

Page 2: Zope component architechture

I you’ve heard something bad about zope...Then probably you don’t know what it is :) So i’ll tell you:ZOPE stands for The Object Publising Environment, so TOPE, but T -> Z to be polite.Name zope itself is a fish, Ballerus ballerus, also known as zope or blue bream, is a fish native to Eurasia.

Some people say that python became popular in web development because of zope :)

Page 3: Zope component architechture

So what is zope?

Object-oriented web application server and frameworktwo main versions alive and actively developed:zope2 - mainly known because Plone CMF is based on itzope3, then BlueBream, then part of BlueBream became Zope Toolkit (ZTK).Many things from zope3 were backported to zope2, but zope3 is initially a full rewrite of zope2 using Component Architecture.And probably you’ve heard most of bad things about zope2.

Page 4: Zope component architechture

Component based architectureBasically the idea:

● objects(components) interact only through interfaces.● single interface - various implementations possible.

Page 5: Zope component architechture

But interaction only through interfaces can be tricky...class ISize(Interface):

“””Object size.”””size = Interface.Attribute(‘size’)

--------------------component---------------------------class IDocument(Interface):

“””Text document.”””text = Interface.Attribute(‘text’)

--------------------component---------------------------class IImage(Interface):

“””Image.”””image = Interface.Attribute(‘image’)

--------------------component---------------------------To get size from objects of various types, we need to implement ISize, but if we want to keep components separate, we can’t do it.

Page 6: Zope component architechture

So here comes an Adapter paradigmAdding adaptors, leads by-interface communication much more elegant, because you can control the interaction between objects through them without direct modification.

IDocument -> ISize adaptor

ISize

IDocument IImage

IImage -> ISize adaptor

Page 7: Zope component architechture

What do we have in python?

● PEP 246 (adaptation) and PEP 245 (interfaces) - killed by Guido in favor of __notimplemented__

● zope.interface● PyProtocols (not active since 2003)● PEAK-Rules (allows you to HACK

everything,so not a direct alternative, also seems not active already)

Page 8: Zope component architechture

zope.interface, here is the implementation

The key package of:● zope● twisted● pyramideven Shipped out of the box with OSX :)Pythonic (with C optimizations) implementation of component architechture.● python3 ready● pypy compliant

Page 9: Zope component architechture

zope.interface: InterfacesInterfaces are defined using Python class statements:>>> import zope.interface>>> class IFoo(zope.interface.Interface):... """Foo blah blah"""...... x = zope.interface.Attribute("""X blah blah""")...... def bar(q, r=None):... """bar blah blah"""

The interface is not a class, it’s an Interface, an instance of InterfaceClass:>>> type(IFoo)<class 'zope.interface.interface.InterfaceClass'>

We can ask for the interface’s documentation:>>> IFoo.__doc__'Foo blah blah'and its name:>>> IFoo.__name__'IFoo'Interfaces can inherit from each other just like normal python classes.

Page 10: Zope component architechture

zope.interface: Interface implementationThe most common way to declare interfaces is using the implements function in a class statement:>>> class Foo(object):... zope.interface.implements(IFoo)...... def __init__(self, x=None):... self.x = x...... def bar(self, q, r=None):... return q, r, self.x...... def __repr__(self):... return "Foo(%s)" % self.x

We can ask if interface is implemented by class:>>> IFoo.implementedBy(Foo)TrueInterface will be Provided by object, if it’s class implements it:>>> foo = Foo()>>> IFoo.providedBy(foo)TrueAnd of course interface is not provided by a class implementing it>>> IFoo.providedBy(Foo)FalseBut it’s also possible to dynamically provide interface for an object via zope.interface.directlyProvides

Page 11: Zope component architechture

zope.interface: InvariantsInvariants are validation expressions, where input is only depends on interface attributes.>>> def range_invariant(ob):... if ob.max < ob.min:... raise RangeError(ob)Given this invariant, we can use it in an interface definition:>>> class IRange(zope.interface.Interface):... min = zope.interface.Attribute("Lower bound")... max = zope.interface.Attribute("Upper bound")...... zope.interface.invariant(range_invariant)Interfaces have a method for checking their invariants:>>> class Range(object):... zope.interface.implements(IRange)...... def __init__(self, min, max):... self.min, self.max = min, max...... def __repr__(self):... return "Range(%s, %s)" % (self.min, self.max)

>>> IRange.validateInvariants(Range(1,2))>>> IRange.validateInvariants(Range(2,1))Traceback (most recent call last):...RangeError: Range(2, 1)

Page 12: Zope component architechture

zope.interface: AdaptersSingle AdaptersLet’s look at a simple example, using a single required specification:>>> from zope.interface.adapter import AdapterRegistry>>> import zope.interface

>>> class IR1(zope.interface.Interface):... pass>>> class IP1(zope.interface.Interface):... pass>>> class IP2(IP1):... pass

>>> registry = AdapterRegistry()

We’ll register an object that depends on IR1 and “provides” IP2:>>> registry.register([IR1], IP2, '', 12)

Given the registration, we can look it up again:>>> registry.lookup([IR1], IP2, '')12

Page 13: Zope component architechture

Finding out what, if anything, is registeredWe can ask if there is an adapter registered for a collection of interfaces. This is different than lookup, because it looks for an exact match:>>> print registry.registered([IR1], IP1)11

>>> print registry.registered([IR1], IP2)12

>>> print registry.registered([IR1], IP2, 'bob')Bob's 12

>>> print registry.registered([IR2], IP1)21

>>> print registry.registered([IR2], IP2)None

Page 14: Zope component architechture

The adapter registry supports the computation of adapters. In this case, we have to register adapter factories:>>> class IR(zope.interface.Interface): ... pass

>>> class X: ... zope.interface.implements(IR)

>>> class Y: ... zope.interface.implements(IP1) ... def __init__(self, context): ... self.context = context

>>> registry.register([IR], IP1, '', Y)In this case, we registered a class as the factory. Now we can call queryAdapter to get the adapted object:>>> x = X()>>> y = registry.queryAdapter(x, IP1)>>> y.__class__.__name__'Y'>>> y.context is xTrue

Page 15: Zope component architechture

Multi-adaptationYou can adapt multiple objects:>>> class Q:... zope.interface.implements(IQ)

As with single adapters, we register a factory, which is often a class:>>> class IM(zope.interface.Interface):... pass>>> class M:... zope.interface.implements(IM)... def __init__(self, x, q):... self.x, self.q = x, q>>> registry.register([IR, IQ], IM, '', M)

And then we can call queryMultiAdapter to compute an adapter:>>> q = Q()>>> m = registry.queryMultiAdapter((x, q), IM)>>> m.__class__.__name__'M'>>> m.x is x and m.q is qTrue

Page 16: Zope component architechture

Listing named adaptersAdapters are named. Sometimes, it’s useful to get all of the named adapters for given interfaces:>>> adapters = list(registry.lookupAll([IR1], IP1))>>> adapters.sort()>>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")]

This works for multi-adapters too:>>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob')>>> adapters = list(registry.lookupAll([IR2, IQ2], IP1))>>> adapters.sort()>>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')]

Page 17: Zope component architechture

zope.interface: SubscriptionsNormally, we want to look up an object that most-closely matches a specification. Sometimes, we want to get all of the objects that match some specification. We use subscriptions for this. We subscribe objects against specifications and then later find all of the subscribed objects:>>> registry.subscribe([IR1], IP2, 'sub12 1')>>> registry.subscriptions([IR1], IP2)['sub12 1']

Note that, unlike regular adapters, subscriptions are unnamed.You can have multiple subscribers for the same specification:>>> registry.subscribe([IR1], IP2, 'sub12 2')>>> registry.subscriptions([IR1], IP2)['sub12 1', 'sub12 2']

If subscribers are registered for the same required interfaces, they are returned in the order of definition.You can register subscribers for all specifications using None:>>> registry.subscribe([None], IP1, 'sub_1')>>> registry.subscriptions([IR2], IP1)['sub_1', 'sub12 1', 'sub12 2']

Page 18: Zope component architechture

zope.interface: Subscription adaptersWe normally register adapter factories, which then allow us to compute adapters, but with subscriptions, we get multiple adapters. Here’s an example of multiple-object subscribers:>>> registry.subscribe([IR, IQ], IM, M)>>> registry.subscribe([IR, IQ], IM, M2)

>>> subscribers = registry.subscribers((x, q), IM)>>> len(subscribers)2>>> class_names = [s.__class__.__name__ for s in subscribers]>>> class_names.sort()>>> class_names['M', 'M2']>>> [(s.x is x and s.q is q) for s in subscribers][True, True]

adapter factory subcribers can’t return None values:>>> def M3(x, y):... return None

>>> registry.subscribe([IR, IQ], IM, M3)>>> subscribers = registry.subscribers((x, q), IM)>>> len(subscribers)2

Page 19: Zope component architechture

zope.interface: Handlers

A handler is a subscriber factory that doesn’t produce any normal output. It returns None. A handler is unlike adapters in that it does all of its work when the factory is called.To register a handler, simply provide None as the provided interface:>>> def handler(event):... print 'handler', event

>>> registry.subscribe([IR1], None, handler)>>> registry.subscriptions([IR1], None) == [handler]True

Page 20: Zope component architechture

Where it can be used?

Basically in every ‘big and complex’ project. Providing a clear communication protocol for pieces of your big picture.Adaptors are great when you need a representation, for example a web-based view of an object.Handlers provide efficient event model.Interfaces give you contracts.

Page 21: Zope component architechture

..And why?

● Having system of many independent components interacting via robust protocol is much better than monolithic approach when everyone knows everyone

● No need to invent the wheel (having api.py in each of your package)

● Relying on python imports potentially causes problems with circular dependencies, with zope.interface you only need interfaces (can be separate) for communication.