Zope Component Architecture (ZCA) is a group of python libraries for building componentized software and is amongst the most misunderstood, underdocumented and underutilized pieces of Python code out there today. If you are interested in how ZCA came to be, you can check it out for yourself, but the goal of this article is to get started with the concepts of componentized architecture and code immediately.

Why should I care?

The benefits of utilizing ZCA when writing your software may not be readily apparent, especially if your background is not in writing larger software systems consisting of hundreds to thousand of classes and thousands to tens of thousands of tests.

Whether or not you are currently writing such systems, mindfully writing software with an eye on componitization will help ensure (but in no way guarantee, especially if done incorrectly) that your small project can grow into something more substantial over time. Codebases that must grow are also codebases that must change.

But I use a Dynamic Language!

Sure, Python is amongst the many other languages in the dynamic language category. It allows one to monkey patch everything, has no compile time type checks and typically “Interfaces” are rarely interacted with (at least that’s what you might think). That starts to matter a whole lot less once you start interacting with real world services and data. Let’s take a look at something we all do: persist data.

You have some base class that is used to represent data:

class Entity(object):
    def __init__(self, key, value):
        self.key = key
        self.value = value

And you are storing that data in a database somewhere:

class MemcacheWriter(object):
    def save(self, entity):
        self.connection.put(entity.key, entity.value)

writer = MemcacheWriter(get_memcache_connection())
writer.save(Entity('a','b'))

Then of course you need to be able to get your entities back out:

class MemcacheReader(object):
    def read(self, key):
        value = self.connection.get(key)
        return Entity(key, value)

reader = MemcacheReader(get_memcache_connection())
entity = reader.read('a')

Notice that, even with this very short example, we have implemented several tight couplings that will make it hard to test this software. What if we need to test this code without an active connection to Memcache?

Furthermore, it will be difficult to adapt this software to growing demands. How will we migrate from direct key storage to a sharded scheme utilizing consistent hashing? What if we later find out that consistent hashing wasn’t the silver bullet we thought it to be? Or if we just want to be able to store anything more complex than Entity?

I have seen many projects attempt to cope with the shortcomings of niave first implementations in many different ways. Oh, and it gets ugly. Real quick. I’ve been first party to many of those implementations, and am here to tell you there is a better way!

Enter: Component Architecture

First: there are no silver bullets in software engineering. Different solutions to different problems have different tradeoffs. Think the about your proposed solution and implement a prototype before jumping head first into anything. Write some tests. Make a pros and cons list.

We are going to look at ways we can utilize ZCA to allow our code to grow and mature with the goals and age of the products we are building.

An Interface Here, an Interface There

Let’s begin by defining an interface for our data. The basic interface to our Entity data type will have a key and a value. We’ll start by describing our data:

from zope.interface import Interface, Attribute

class IKey(Interface):
    pass

class IEntity(Interface):
    key = Attribute("""The key by which this IEntity can be addressed""")
    value = Attribute("""A bytearray of data accessible by the key""")

And then simply implement it:

from zope.interface import implements

class Key(str):
    implements(IKey)

class Entity(object):
    implements(IEntity)
    
    def __init__(self, key, value):
        self.key = key
        self.value = value

While at first this may look like extra boilerplate (albiet providing some valuable documentation), just hang in there and I’ll make it worth your while.

Now we can look at something a little more interesting. Serializing our entity to and from a database. We can first think about exactly what we want to do, and define an interface for that. Writing stuff (for any value of stuff) seems important, as does reading them, as does a place to do that to and from:

from zope.interface import Interface

class IWriter(Interface):
    def write(self):
        """Write an Entity to persistent storage"""

class IReader(Interface):
    def read(self):
        """Read a Key from persistent storage"""

class IDatabase(Interface):
    def put(self, key, value):
        """Write a value to the database accessible later by key"""
    
    def get(self, key):
        """Retrieve a value from the database"""

Note that the reader and writer interfaces have nothing to do with databases themselves. They simply define something that can be written, and something that can be read.

Ready to Use!

With those interfaces sorted out, before we write any implementations, we can begin using the interfaces in test cases. Of course, if you run the code, you’ll get some weird errors about not being able to find an adaptor for IEntity and IWriter, but we’ll get to that in a bit.

from zope.component import getAdapter

entity = Entity('thekey', 'thevalue')
getAdapter(entity, IWriter).write()

key = Key('thekey')
entity = getAdapter(key, IReader).read()

Quickly, you can begin to see the benefits of writing software in a componentized fashion. In code where we are actually interested in writing or reading data, it is not necessary to worry about the underlying implementation. All we really care about locally is that there is some way defined in our code base that can adapt an IEntity to an IWriter.

Of course, in the interest of brevity, I simplify things slightly. It is possible that you may want to write to a ‘database’ or to ‘log file’ which may mean different things to your application (one is persistent, the other ephemeral). ZCA supports that, and allows you to name the various adaptors available for use.

Need Concrete Implementations

Let’s write some implementations so Entity and Key can be actually useful. First, we’ll need access to a database. All that is really necessary for testing is a dictionary-backed in-memory storage:

from zope.component import getGlobalSiteManager()

storage = {}

class InMemoryDatabase(object):
    implements(IDatabase)
    
    def put(self, key, value):
        storage[key] = value

    def get(self, key):
        return storage[key]

site = getGlobalSiteManager()
site.registerUtility(InMemoryDatabase, IDatabase, 'db:inMem')

What we did above was define a class that stores keys to values in a python dictionary as you may have done many times before. It gets interesting because it is necessary to tell zope.component that InMemoryDatabase is an implementation of IDatabase. Below we’ll see how that’s actually put to use.

The way in which our application remembers which database to use is up to the configuration of your app. For now, just define a static variable to keep the config. Configuration is another large topic in ZCA, and one best approached separately (I’ll get to that in a later blog post, in which we can erradicate all this interaction/code smell with site and globalSiteManager).

DB_FACTORY = 'db:inMem'

class EntityDBWriter(object):
    implements(IWriter)
    adapts(IEntity)

    def __init__(self, entity):
        self.entity = entity

    def write(self):
        db = site.getUtility(IDatabase, DB_FACTORY)()
        db.put(self.entity.key, self.entity.value)

class EntityDBReader(object):
    implements(IReader)
    adapts(IKey)

    def __init__(self, key):
        self.key = key

    def read(self):
        db = site.getUtility(IDatabase, DB_FACTORY)()
        val = db.get(self.key)
        return Entity(self.key, val)

site.registerAdapter(EntityDBWriter)
site.registerAdapter(EntityDBReader)

Above is the code that will adapt any IEntity into concrete implementations of IWriter or IReader and actually serialize it to an IDatabase. If you are unfamiliar with the Adapter Pattern, basically it’s a way to get from one concrete object with a defined interface (in this case IEntity) to another concrete object with a different interface (for example IWriter).

You’ll now be able to run the code above (under the Ready To Use! header). Blam-o!

Conclusion

Now put all together (gist) it looks like what we’ve built is massively overengineered for the task at hand. And for trivial examples like this, that would be absolutely correct. It is. But one could imagine this trivial example growing up into a real life application. Where you might want to swap out the database implementation to try out different databases, or swap out the writer and readers to try different types of serialization.

When developing your application this way, what you’ll find is altering your code becomes much less hair-raising. Changing the structure of your components, inserting adapters, decorators and other mechanisms into the mix will be less stressful and easier to test. Your test cases should have better factoring by default. Even simple things such as interacting with a 3rd party web service can be made to be much easier to work with.

In a follow up post, I will go into detail about how the various components of our application can be tied together without interacting with the underlying fabric. We can remove the reliance on the “site manager” object and allow zope.configuration to tie the various components together for us. Stay tuned!