[Zope3-dev] defining interfaces

Steve Alexander steve@cat-box.net
Mon, 07 Apr 2003 21:44:16 +0200


Garrett Smith wrote:
> I want to make sure I have this right.
> 
> Under the auspices of "best practices", there are two approaches for
> managing interface definitions. The one a developer chooses is based on
> the need to manage a large number of interfaces.
> 
> Option 1 - Small, easily managed number of interfaces:
> 
> - Define all interfaces in a file "interfaces.py" located in the package
> directory where the default implementations of the interfaces are
> defined.
> 
> Example of Option 1:
> 
>   /mypackage
>   -------------------
>   interfaces.py
>   foo.py

There needs to be an __init__.py too.
An example of this pattern is the package src/zope/schema in Zope 3.
Other examples are src/zope/tal, src/zope/i18n, src/zope/component.

Note that both exceptions and interfaces are typically defined there.
And, sometimes interfaces of exceptions are defined there too.


> Option 2 - Large number of interfaces
> 
> - Define all interfaces in files that are located in an "interface"
> subpackage. 

No. They are in an "interfaces" sub-package. The name is the same as in 
your option 1.
Use of interfaces from both kinds of project from other python code 
looks pretty much the same.
I see that you used "interfaces" below, though.


> The interface files should have the same names as the files
> containing the default interface implementations.

Well, perhaps.
There are usually more implementation classes than there are interfaces. 
Usually, you'll want the interface module files to have the same name as 
the packages where implementation is defined.

> Example of Option 2:
> 
>   /mypackage
>   --------------------
>   interfaces/
>   foo.py
>   bar.py
 >
>   /mypackage/interfaces
>   --------------------------------
>   foo.py
>   bar.py

The following example is more typical, unless there are unusually many 
interfaces for both foo and bar.

   /mypackage
   ----------------------
   __init__.py
   interfaces/
   foo.py
   bar/

   /mypackage/bar
   ----------------------
   __init__.py
   baz.py

   /mypackage/interfaces
   ----------------------
   __init__.py
   bar.py

For a real example from a Zope3 checkout:

~/scratch/Zope3$ ls ./src/zope/app/event/
CVS          configure.zcml    meta.zcml       subs.py
__init__.py  globalservice.py  objectevent.py  tests

~/scratch/Zope3$ ls ./src/zope/app/interfaces/event.py
./src/zope/app/interfaces/event.py

There are lots of other .py files in src/zope/app/interfaces.


> Does this sound right?

It's on the right track.


> If, so, questions:
 >
> - If the only criterion for differentiating Options 1 and 2 is the
> number/complexity of interfaces, what is a good rule of thumb for
> developers to know when it's time to go from Option 1 to Option 2?
> 
>   - A specific number of interfaces? E.g. > 20, > 30?
>   - The addition of subpackages? E.g. mysubpackage1, mysubpackage2,
> etc.?

Probably you'd use the same reasons for breaking any python code up into 
several modules. That is, when it makes the code easier to follow.

There are 23 exceptions and interfaces defined in 
src/zope/schema/interfaces.py. It doesn't seem too long to me. Most of 
the definitions are very short. There are 255 non-blank lines in the module.

If you add a subpackage for implementation, it is probably a good idea 
to consider another module for the interfaces and exceptions of that 
subpackage, if it has any.


> - From the Z3 codeline, it looks like a "project" should only have one
> "interfaces" package (assuming Option 2) and that it should be defined
> at the highest level of the project. E.g.
> 
>   Preferred Hierarchy
>   -------------------------------
>   /mypackage/
>   /mypackage/foo/
>   /mypackage/bar/
>   /mypackage/interfaces/
>   /mypackage/interfaces/foo/
>   /mypackage/interfaces/bar/
> 
>   Discouraged Hierarchy
>   -------------------------------
>   /mypackage/
>   /mypackage/foo/
>   /mypackage/bar/
>   /mypackage/interfaces/
>   /mypackage/foo/interfaces/
>   /mypackage/bar/interfaces/

In this case, it looks like mypackage is a project, and also that 
mypackage/foo and mypackage/bar are projects.

That's fine, if it fits the projects. The packages under src/zodb/ are 
organised like that.

./src/zodb/interfaces.py
./src/zodb/btrees/
./src/zodb/btrees/interfaces.py
./src/zodb/code/
./src/zodb/code/interfaces.py
./src/zodb/storage/
./src/zodb/storage/interfaces.py
./src/zodb/zeo/
./src/zodb/zeo/interfaces.py
./src/zodb/zeo/zrpc/
./src/zodb/zeo/zrpc/interfaces.py

In the case of zope.app, zope.app is the project. The things inside 
zope.app aren't really designed to be reusable independently of the rest 
of zope.app. I think that's the case with the zodb package too, so 
perhaps the zodb package should look more like this:

./src/zodb/btrees/
./src/zodb/code/
./src/zodb/storage/
./src/zodb/zeo/
./src/zodb/interfaces/btrees.py
./src/zodb/interfaces/code.py
./src/zodb/interfaces/storage.py
./src/zodb/interfaces/zeo/__init__.py
./src/zodb/interfaces/zeo/zrpc.py

But I don't think it matters much either way around. I would suggest 
that projects don't generally contain other projects. But sometimes they do.


> If there's agreement on what the approach is, I'll write up something
> for the docs.

Thanks.


> Btw, if the above scheme is accurate, we should clearly define
> "project", since that will expressly determine the level at which
> interface definitions are organized.

Um... a body of code that is within some package that may be 
independently maintained, and that well-defined dependencies on other 
code and other projects.

So, zope/app/services wouldn't be a project because it has ill-defined 
dependencies on the other things inside zope/app. It is maintained as 
part of zope/app. However, the zodb package has dependences on the 
persistence and transaction packages, and that's about it. (Other than 
the standard libraries.)

I'm sure this definition can be improved on.

--
Steve Alexander