Component Architecture

Guillotina is built on a component architecture. The component architecture uses adapter and singleton software design patterns to help manage complexity. It allows users to register and lookup adapters and utility defined against interfaces.

Why

program to an interface, not an implementation
favor object composition over class inheritance
keep objects stupid

The component architecture is a powerful tool to help you build complex software that needs to be extensible. In software engineering, adapters and singletons are used often so it is a natural pattern to build on.

Almost any component/functionality in Guillotina can be overridden in an add-on application by overriding Guillotina’s components.

Basics

To query an adapter on content:

from guillotina.component import get_adapter
from guillotina.interfaces import IResource
from zope.interface import Interface
from guillotina import configure

class IMyAdapter(Interface):
    pass

@configure.adapter(for_=IResource, provides=IMyAdapter)
class MyAdapter:
    def __init__(self, ob):
        self.ob = ob

    def do_something(self):
        pass

adapter = get_adapter(ob, IMyAdapter)
adapter.do_something()

To query for a utility(which is what we call singletons), it’s similiar:

from guillotina.component import get_utility
from guillotina.interfaces import IPermission
permission = get_utility(IPermission, name='guillotina.AccessContent')

Details

To describe power of this approach, we’ll go through using adapters without the registration and lookup and then with the component architecture.

Adapters without CA

class Automobile:
    wheels = 4

    def __init__(self):
        pass


class Motorcycle(Automobile):
    wheels = 2


class SemiTruck(Automobile):
    wheels = 18


class Operate:

    def __init__(self, automobile: Automobile):
        self.automobile = automobile

    def drive(self):
        pass


class OperateMotocycle:

    def drive(self):
        pass


class OperateSemi:

    def drive(self):
        pass

Then, to use these adapters, you might do something like:

if isinstance(auto, SemiTruck):
    operate = OperateSemi(auto)
elif isinstance(auto, Motorcycle):
    operate = OperateMotocycle(auto)
else:
    operate = Operate(auto)
operate.drive()

Adapters with CA

from guillotina import configure
from zope.interface import Attribute, Interface, implementer


class IAutomobile(Interface):
    wheels = Attribute('number of wheels')


class IMotocycle(IAutomobile):
    pass


class ISemiTruck(IAutomobile):
    pass


@implementer(IAutomobile)
class Automobile:
    wheels = 4


@implementer(IMotocycle)
class Motorcycle(Automobile):
    wheels = 2


@implementer(ISemiTruck)
class SemiTruck(Automobile):
    wheels = 18


class IOperate(Interface):
    def drive():
        pass


@configure.adapter(for_=IAutomobile, provides=IOperate)
class Operate:

    def __init__(self, automobile: Automobile):
        self.automobile = automobile

    def drive(self):
        return 'driving automobile'


@configure.adapter(for_=IMotocycle, provides=IOperate)
class OperateMotocycle(Operate):

    def drive(self):
        return 'driving motocycle'


@configure.adapter(for_=ISemiTruck, provides=IOperate)
class OperateSemi(Operate):

    def drive(self):
        return 'driving semi'

Then, to use it:

from guillotina.component import get_adapter
semi = SemiTruck()
operate = get_adapter(semi, IOperate)
operate.drive()