Difference between pages "Bash by Example, Part 2" and "Zope Component Architecture"

From Funtoo
(Difference between pages)
Jump to navigation Jump to search
 
 
Line 1: Line 1:
== More bash programming fundamentals ==
This page describes the Zope Component Architecture, or ZCA for short.


=== Accepting arguments ===
== ZCA: What is it? ==
Let's start with a brief tip on handling command-line arguments, and then look at bash's basic programming constructs.


In the sample program in the [[Bash by example, Part1|introductory article]], we used the environment variable "$1", which referred to the first command-line argument. Similarly, you can use "$2", "$3", etc. to refer to the second and third arguments passed to your script. Here's an example:
The Zope Component Architecture is a Framework that arose out of Zope 3 development, and is now integrated into Zope 2. It is used by various software projects, and doesn't even require Zope to run. You can use it natively with Python. It's part of Zope, but it's not Zope.
<source lang="bash">
#!/usr/bin/env bash


echo name of script is $0
=== Why Should I Use It? ===
echo first argument is $1
echo second argument is ${2}
echo seventeenth argument is ${17}
echo number of arguments is $#
</source>
The example is self explanatory except for three small details. First, "$0" will expand to the name of the script, as called from the command line, and "$#" will expand to the number of arguments passed to the script. The use of curly braces is optional for single-digit numbers but required for arguments above "$9". Play around with the above script, passing different kinds of command-line arguments to get the hang of how it works.


Sometimes, it's helpful to refer to all command-line arguments at once. For this purpose, bash features the "$@" variable, which expands to all command-line parameters separated by spaces. We'll see an example of its use when we take a look at "for" loops, a bit later in this article.  
You shouldn't. Well, I mean, don't use it unless you see the value in it, or if you are using a software project that uses it already. Due to the fact that it is a framework, it does impose a paradigm on how you develop Python code and you need to determine if that paradigm works for your particular situation, or if it doesn't.


=== Bash programming constructs ===
=== The Paradigm ===
If you've programmed in a procedural language like C, Pascal, Python, or Perl, then you're familiar with standard programming constructs like "if" statements, "for" loops, and the like. Bash has its own versions of most of these standard constructs. In the next several sections, I will introduce several bash constructs and demonstrate the differences between these constructs and others you are already familiar with from other programming languages. If you haven't programmed much before, don't worry. I include enough information and examples so that you can follow the text.


=== Conditional love ===
In one phrase, the paradigm implemented by the ZCA framework is that of a "component architecture", which in itself may not mean anything to you, but it is easy to understand.  
If you've ever programmed any file-related code in C, you know that it requires a significant amount of effort to see if a particular file is newer than another. That's because C doesn't have any built-in syntax for performing such a comparison; instead, two stat() calls and two stat structures must be used to perform the comparison by hand. In contrast, bash has standard file comparison operators built in, so determining if "'''/tmp/myfile''' is readable" is as easy as checking to see if "<span style="color:green">$myvar</span> is greater than 4".  


The following table lists the most frequently used bash comparison operators. You'll also find an example of how to use every option correctly. The example is meant to be placed immediately after the "if". For example:
If you are reading this, you have probably developed some Python software. Now imagine if you had to lead a team that is developing a complex Python software application. This type of project presents new challenges that you may not experience as an individual developer.  
<source lang="bash">
if [ -z "$myvar" ]
then
    echo "myvar is not defined"
fi
</source>
Sometimes, there are several different ways that a particular comparison can be made. For example, the following two snippets of code function identically:
<source lang="bash">
if [ "$myvar" -eq 3 ]
then
    echo "myvar equals 3"
fi


if [ "$myvar" = "3" ]
For one, different members of the team have different skill sets. You also want the team to be able to work in parallel rather than have some members of the team sit idle waiting for parts they depend upon to be completed. Ideally, you would want to split the application into logical parts and then clearly define who is doing what part of the software and what each team is delivering. But when the software is complete, it needs to work together as an integrated unit.
then
    echo "myvar equals 3"
fi
</source>
In the above two comparisons do exactly the same thing, but the first uses arithmetic comparison operators, while the second uses string comparison operators.  


=== String comparison caveats ===
Clearly, you will need to think about the software architecture. But it can also be helpful to have a framework your team can use that helps them to split the application into logical parts, develop these parts in parallel, and then allow these various parts to interact once they are done. This is what a component architecture is typically used for. It supports this style of development.
Most of the time, while you can omit the use of double quotes surrounding strings and string variables, it's not a good idea. Why? Because your code will work perfectly, unless an environment variable happens to have a space or a tab in it, in which case bash will get confused. Here's an example of a fouled-up comparison:
<source lang="bash">
if [ $myvar = "foo bar oni" ]
then
    echo "yes"
fi
</source>
In the above example, if myvar equals "foo", the code will work as expected and not print anything. However, if myvar equals "foo bar oni", the code will fail with the following error:
<source lang="bash">
[: too many arguments
</source>
In this case, the spaces in "$myvar" (which equals "foo bar oni") end up confusing bash. After bash expands "$myvar", it ends up with the following comparison:
<source lang="bash">
[ foo bar oni = "foo bar oni" ]
</source>
Because the environment variable wasn't placed inside double quotes, bash thinks that you stuffed too many arguments in-between the square brackets. You can easily eliminate this problem by surrounding the string arguments with double-quotes. Remember, if you get into the habit of surrounding all string arguments and environment variables with double-quotes, you'll eliminate many similar programming errors. Here's how the "foo bar oni" comparison should have been written:
<source lang="bash">
if [ "$myvar" = "foo bar oni" ]
then
    echo "yes"
fi
</source>
The above code will work as expected and will not create any unpleasant surprises.  


{{fancynote|If you want your environment variables to be expanded, you must enclose them in double quotes, rather than single quotes. Single quotes disable variable (as well as history) expansion.}}
=== Interfaces ===
=== Looping constructs: "for" ===
OK, we've covered conditionals, now it's time to explore bash looping constructs. We'll start with the standard "for" loop. Here's a basic example:
<source lang="bash">
#!/usr/bin/env bash


for x in one two three four
You decide to split your team into smaller groups, and each group will tackle one part of the software. One group's code will need to interact with another group's code, but ''many parts depend upon other parts that haven't been written yet''. To solve this chicken-and-egg problem, the groups need to define ''interfaces'' for how others will be able to utilize their code, even before it is written. These interfaces are the interaction points for one group to utilize software that other groups write.
do
    echo number $x
done


Output:
Now we have added an additional layer of complexity to the software development project -- the effort of defining these interfaces. But we expect the investment to reap major rewards in mere days as this model of development will allow each group to actively develop their part of the application without waiting on another group to complete theirs first. While this could be done without ZCA, essentially all of ZCA is designed to help facilitate this style of development.
number one
number two
number three
number four
</source>
What exactly happened? The "for x" part of our "for" loop defined a new environment variable (also called a loop control variable) called "$x", which was successively set to the values "one", "two", "three", and "four". After each assignment, the body of the loop (the code between the "do" ... "done") was executed once. In the body, we referred to the loop control variable "$x" using standard variable expansion syntax, like any other environment variable. Also notice that "for" loops always accept some kind of word list after the "in" statement. In this case we specified four English words, but the word list can also refer to file(s) on disk or even file wildcards. Look at the following example, which demonstrates how to use standard shell wildcards:
<source lang="bash">
#!/usr/bin/env bash


for myfile in /etc/r*
Now that you understand this, you will have a better appreciation for why ZCA is designed the way it is. It is trying to provide a framework for building larger projects that cannot be handled by a single developer. It does add some complexity to your project. This is intentional. You should only use ZCA if you expect that the additional complexity will be more than offset by improved productivity, maintainability and software quality that can arise from a component-based architecture. So, is it right for you? There is no one answer to this. The right answer will depend on the complexity of the project, your development team, and other factors.
do
    if [ -d "$myfile" ]
    then
      echo "$myfile (dir)"
    else
      echo "$myfile"
    fi
done


output:
At least now you know what ZCA is designed for, so you will be able to evaluate it fairly :)


/etc/rc.d (dir)
Martin Aspeli has written [http://www.martinaspeli.net/articles/of-babies-and-bathwater-or-why-i-love-the-zope-component-architecture a defense of ZCA].
/etc/resolv.conf
/etc/resolv.conf~
/etc/rpc
</source>
The above code looped over each file in '''/etc''' that began with an "r". To do this, bash first took our wildcard /etc/r* and expanded it, replacing it with the string '''/etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc''' before executing the loop. Once inside the loop, the "-d" conditional operator was used to perform two different actions, depending on whether myfile was a directory or not. If it was, a " (dir)" was appended to the output line.  


We can also use multiple wildcards and even environment variables in the word list:
== The Parts of ZCA ==
<source lang="bash">
for x in /etc/r??? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/*
do
    cp $x /mnt/mydira
done
</source>
Bash will perform wildcard and variable expansion in all the right places, and potentially create a very long word list.


While all of our wildcard expansion examples have used absolute paths, you can also use relative paths, as follows:
This section describes the "components" of ZCA itself. One of the most complicated things about ZCA is the new terminology that it uses for its various parts. Once you understand this terminology, ZCA is a lot more approachable. Also see [http://wiki.zope.org/zope3/ComponentArchitectureOverview]
<source lang="bash">
for x in ../* mystuff/*
do
    echo $x is a silly file
done
</source>
In the above example, bash performs wildcard expansion relative to the current working directory, just like when you use relative paths on the command line. Play around with wildcard expansion a bit. You'll notice that if you use absolute paths in your wildcard, bash will expand the wildcard to a list of absolute paths. Otherwise, bash will use relative paths in the subsequent word list. If you simply refer to files in the current working directory (for example, if you type <span style="color:green">for x in *</span>), the resultant list of files will not be prefixed with any path information. Remember that preceding path information can be stripped using the <span style="color:green">basename</span> executable, as follows:
<source lang="bash">
for x in /var/log/*
do
    echo `basename $x` is a file living in /var/log
done
</source>
Of course, it's often handy to perform loops that operate on a script's command-line arguments. Here's an example of how to use the "$@" variable, introduced at the beginning of this article:
<source lang="bash">
#!/usr/bin/env bash


for thing in "$@"
; Components: A component is Python-based code, usually a class, that implements an ''Interface'' (defined below.)
do
;Interfaces: Interfaces are special Python classes that are declared for the sole purpose of describing functionality implemented by another class or module. They are registered with a component registry, and others can query the registry for components that implement a particular interface. Objects can advertise that they implement a particular interface. From a code perspective, interfaces are the heart of ZCA.
    echo you typed ${thing}.
; Services: A service is generally a Python object that deals with components, and provides some functionality to your application. Think of components as "things", and services as parts of your code that take these "things" and perform useful actions with them.
done
;Component Registry: The Component Registry, also called a ''Site Manager'', is a directory of components, and acts as the central organizational hub of ZCA. It is a ''service'' (as defined above.) You register components with the component registry, and then others can query the registry for components that they need. There is a ''global'' component registry that is automatically created for you, and you can use the ZODB as a ''local'' component registry.
;Global Component Registry: The Global Component Registry a service that is automatically created when your ZCA-based application starts, and is populated with components based on your application's ZCML configuration files. Any part of your application can access the Global Component Registry. When your application shuts down, the Global Component Registry no longer exists. It will be re-initialized from ZCML when your application is started again.
;Local Components: Local components are components that are not instantiated as part of the Global Component Registry. They are typically stored in the traditional Zope storage infrastructure, the ZODB, which also means that they are ''persistent'', in that they will survive multiple application starts/stops. Because they exist in the ZODB, Zope will use ''acquisition'' (a traditional Zope way of finding things in the ZODB) to retrieve local components when your application queries the ZODB.
;ZCML: Pronounced "Z-camel", ZCML is an XML-based configuration file syntax that is used to register components with the global component registry. It is useful in that it provides a mechanism to add new components using configuration files rather than touching source code. It also provides a mechanism to easily extend ZCA code by overriding existing components with new implementations -- all without touching existing Python source code.
;Adapters: Adapters are Python classes that allow new interfaces to be used with existing objects. More specifically, an Adapter class will contain an <tt>adapts()</tt> call in the class body that specifies that it ''adapts'' an interface, which means that you can pass any object that implements that interface to the adapter class' <tt>__init__()</tt> method, such as <tt>a = myAdapter(myobject)</tt>. <tt>MyAdapter</tt> now takes care of acting as a front-end for <tt>myobject</tt> so that any interfaces implemented by <tt>myAdapter</tt> will interact properly with <tt>myobject</tt>. In a sense, the adapter "eats" the adapted object.
;Factories: A factory is something (technically, a ''Utility'', see below) that creates components and objects. It is like a higher-level version of a constructor. A Factory must be ''callable'' and implement the <tt>IFactory</tt> interface. Factories offer a convenient means to access component constructors via a component registry, rather than connecting to the constructor directly in Python code. It's important to note that ''ZCML creates factories for you automatically when you register components. This means that in many cases, you do not need to deal directly with Factory creation.''
;Utilities: A utility is a Python object that you want to add to the component registry. It could be any type of Python object that another part of your application may need. A utility must implement at least one interface. [http://bluebream.zope.org/doc/1.0/howto/localsitemanager.html#how-does-it-usually-look-like The Zope Documentation] has some guidance on when it's appropriate to create a utility.
;Multi-Adapters: Multi-adapters are adapters that have the ability to adapt one than more type of object.
;Subscription Adapters: Adapters can be registered as subscription adapters, and then ''all'' adapters that adapt a particular object can be retrieved by calling the <tt>zope.component.subscribers()</tt> function. This is useful when doing things like validation, where you want to get back a bunch of adapters that apply to a specific object. Subscription adapters can return a value.
;Handlers: Handlers are like subscription adapters, but they do not return a value to the caller. They go off, "handle" something, and then return.
;Overrides: If you register multiple components with the same arguments, the last component registered will replace any previously-registered components. This behavior is used to replace existing components with new ones that may implement new functionality.
;Invariants: Invariants are functions that you attach to your interfaces that throw exceptions when certain conditions aren't met. Invariants are invoked by calling the interface's <tt>validateInvariants</tt> method, passing the object to be validated as an argument.


output:
== ZCML ==


$ allargs hello there you silly
The official specification for ZCML is viewable in the [http://apidoc.zope.org/++apidoc++/ Zope 3 APIDoc site], although this may not accurately reflect the API in Zope 2.13. In terms of explanatory, conceptual documentation, there does not seem to be anything good available, and this section will attempt to fill that gap.
you typed hello.
you typed there.
you typed you.
you typed silly.
</source>
=== Shell arithmetic ===
Before looking at a second type of looping construct, it's a good idea to become familiar with performing shell arithmetic. Yes, it's true: You can perform simple integer math using shell constructs. Simply enclose the particular arithmetic expression between a "$((" and a "))", and bash will evaluate the expression. Here are some examples:
<source lang="bash">
$ echo $(( 100 / 3 ))
33
$ myvar="56"
$ echo $(( $myvar + 12 ))
68
$ echo $(( $myvar - $myvar ))
0
$ myvar=$(( $myvar + 1 ))
$ echo $myvar
57
</source>
Now that you're familiar performing mathematical operations, it's time to introduce two other bash looping constructs, "while" and "until".  


=== More looping constructs: "while" and "until" ===
For now, read the [http://codespeak.net/z3/five/manual.html Five Manual] and work through the <tt>[http://svn.zope.org/z3/Five/trunk/demo/?rev=46891#dirlist demos]</tt> directory.
A "while" statement will execute as long as a particular condition is true, and has the following format:
<source lang="bash">
while [ condition ]
do
    statements
done
</source>
"While" statements are typically used to loop a certain number of times, as in the following example, which will loop exactly 10 times:
<source lang="bash">
myvar=0
while [ $myvar -ne 10 ]
do
    echo $myvar
    myvar=$(( $myvar + 1 ))
done
</source>
You can see the use of arithmetic expansion to eventually cause the condition to be false, and the loop to terminate.  


"Until" statements provide the inverse functionality of "while" statements: They repeat as long as a particular condition is false. Here's an "until" loop that functions identically to the previous "while" loop:
<source lang="bash">
myvar=0
until [ $myvar -eq 10 ]
do
    echo $myvar
    myvar=$(( $myvar + 1 ))
done
</source>
=== Case statements ===
"Case" statements are another conditional construct that comes in handy. Here's an example snippet:
<source lang="bash">
case "${x##*.}" in
    gz)
          gzunpack ${SROOT}/${x}
          ;;
    bz2)
          bz2unpack ${SROOT}/${x}
          ;;
    *)
          echo "Archive format not recognized."
          exit
          ;;
esac
</source>
Above, bash first expands "${x##*.}". In the code, "$x" is the name of a file, and "${x##*.}" has the effect of stripping all text except that following the last period in the filename. Then, bash compares the resultant string against the values listed to the left of the ")"s. In this case, "${x##*.}" gets compared against "gz", then "bz2" and finally "*". If "${x##*.}" matches any of these strings or patterns, the lines immediately following the ")" are executed, up until the ";;", at which point bash continues executing lines after the terminating "esac". If no patterns or strings are matched, no lines of code are executed; however, in this particular code snippet, at least one block of code will execute, because the "*" pattern will catch everything that didn't match "gz" or "bz2".
=== Functions and namespaces ===
In bash, you can even define functions, similar to those in other procedural languages like Pascal and C. In bash, functions can even accept arguments, using a system very similar to the way scripts accept command-line arguments. Let's take a look at a sample function definition and then proceed from there:
<source lang="bash">
tarview() {
    echo -n "Displaying contents of $1 "
    if [ ${1##*.} = tar ]
    then
        echo "(uncompressed tar)"
        tar tvf $1
    elif [ ${1##*.} = gz ]
    then
        echo "(gzip-compressed tar)"
        tar tzvf $1
    elif [ ${1##*.} = bz2 ]
    then
        echo "(bzip2-compressed tar)"
        cat $1 | bzip2 -d | tar tvf -
    fi
}
</source>
{{fancynote|Another case: The above code could have been written using a "case" statement. Can you figure out how?}}
Above, we define a function called "tarview" that accepts one argument, a tarball of some kind. When the function is executed, it identifies what type of tarball the argument is (either uncompressed, gzip-compressed, or bzip2-compressed), prints out a one-line informative message, and then displays the contents of the tarball. This is how the above function should be called (whether from a script or from the command line, after it has been typed in, pasted in, or sourced):
<pre>
<pre>
$ tarview shorten.tar.gz
<?xml version="1.0" encoding="utf-8"?>
Displaying contents of shorten.tar.gz (gzip-compressed tar)
<configure
drwxr-xr-x ajr/abbot         0 1999-02-27 16:17 shorten-2.3a/
        xmlns="http://namespaces.zope.org/zope"
-rw-r--r-- ajr/abbot      1143 1997-09-04 04:06 shorten-2.3a/Makefile
         xmlns:browser="http://namespaces.zope.org/browser"
-rw-r--r-- ajr/abbot      1199 1996-02-04 12:24 shorten-2.3a/INSTALL
        xmlns:five="http://namespaces.zope.org/five">
-rw-r--r-- ajr/abbot      839 1996-05-29 00:19 shorten-2.3a/LICENSE
</configure>
....
</pre>
</pre>
As you can see, arguments can be referenced inside the function definition by using the same mechanism used to reference command-line arguments. In addition, the "$#" macro will be expanded to contain the number of arguments. The only thing that may not work completely as expected is the variable "$0", which will either expand to the string "bash" (if you run the function from the shell, interactively) or to the name of the script the function is called from.
{{fancynote|Use'em interactively: Don't forget that functions, like the one above, can be placed in your ~/.bashrc or ~/.bash_profile so that they are available for use whenever you are in bash.}}
=== Namespace ===
Often, you'll need to create environment variables inside a function. While possible, there's a technicality you should know about. In most compiled languages (such as C), when you create a variable inside a function, it's placed in a separate local namespace. So, if you define a function in C called myfunction, and in it define a variable called "x", any global (outside the function) variable called "x" will not be affected by it, eliminating side effects.
While true in C, this isn't true in bash. In bash, whenever you create an environment variable inside a function, it's added to the global namespace. This means that it will overwrite any global variable outside the function, and will continue to exist even after the function exits:
<source lang="bash">
#!/usr/bin/env bash
myvar="hello"
myfunc() {
    myvar="one two three"
    for x in $myvar
    do
        echo $x
    done
}
myfunc
echo $myvar $x
</source>
When this script is run, it produces the output "one two three three", showing how "$myvar" defined in the function clobbered the global variable "$myvar", and how the loop control variable "$x" continued to exist even after the function exited (and also would have clobbered any global "$x", if one were defined).
In this simple example, the bug is easy to spot and to compensate for by using alternate variable names. However, this isn't the right approach; the best way to solve this problem is to prevent the possibility of clobbering global variables in the first place, by using the "local" command. When we use "local" to create variables inside a function, they will be kept in the local namespace and not clobber any global variables. Here's how to implement the above code so that no global variables are overwritten:
<source lang="bash">
#!/usr/bin/env bash
myvar="hello"
myfunc() {
    local x
    local myvar="one two three"
    for x in $myvar
    do
        echo $x
    done
}
myfunc
echo $myvar $x
</source>
This function will produce the output "hello" -- the global "$myvar" doesn't get overwritten, and "$x" doesn't continue to exist outside of myfunc. In the first line of the function, we create x, a local variable that is used later, while in the second example (local myvar="one two three"") we create a local myvar and assign it a value. The first form is handy for keeping loop control variables local, since we're not allowed to say "for local x in $myvar". This function doesn't clobber any global variables, and you are encouraged to design all your functions this way. The only time you should not use "local" is when you explicitly want to modify a global variable.
=== Wrapping it up ===
Now that we've covered the most essential bash functionality, it's time to look at how to develop an entire application based in bash. In my next installment, we'll do just that. See you then!
== Resources ==


*Read [[Bash by Example, Part 1]].
Check out this excellent [http://plone.org/products/dexterity/documentation/manual/five.grok Zope Component Architecture basics with five.grok] tutorial from Martin Aspeli.
*Read [[Bash by Example, Part 3]].
*Visit [http://www.gnu.org/software/bash/bash.html GNU's bash home page].


__NOTOC__
[[Category:Zope]]
[[Category:Linux Core Concepts]]
[[Category:Developer]]
[[Category:Articles]]
[[Category:Featured]]

Revision as of 15:53, February 14, 2012

This page describes the Zope Component Architecture, or ZCA for short.

ZCA: What is it?

The Zope Component Architecture is a Framework that arose out of Zope 3 development, and is now integrated into Zope 2. It is used by various software projects, and doesn't even require Zope to run. You can use it natively with Python. It's part of Zope, but it's not Zope.

Why Should I Use It?

You shouldn't. Well, I mean, don't use it unless you see the value in it, or if you are using a software project that uses it already. Due to the fact that it is a framework, it does impose a paradigm on how you develop Python code and you need to determine if that paradigm works for your particular situation, or if it doesn't.

The Paradigm

In one phrase, the paradigm implemented by the ZCA framework is that of a "component architecture", which in itself may not mean anything to you, but it is easy to understand.

If you are reading this, you have probably developed some Python software. Now imagine if you had to lead a team that is developing a complex Python software application. This type of project presents new challenges that you may not experience as an individual developer.

For one, different members of the team have different skill sets. You also want the team to be able to work in parallel rather than have some members of the team sit idle waiting for parts they depend upon to be completed. Ideally, you would want to split the application into logical parts and then clearly define who is doing what part of the software and what each team is delivering. But when the software is complete, it needs to work together as an integrated unit.

Clearly, you will need to think about the software architecture. But it can also be helpful to have a framework your team can use that helps them to split the application into logical parts, develop these parts in parallel, and then allow these various parts to interact once they are done. This is what a component architecture is typically used for. It supports this style of development.

Interfaces

You decide to split your team into smaller groups, and each group will tackle one part of the software. One group's code will need to interact with another group's code, but many parts depend upon other parts that haven't been written yet. To solve this chicken-and-egg problem, the groups need to define interfaces for how others will be able to utilize their code, even before it is written. These interfaces are the interaction points for one group to utilize software that other groups write.

Now we have added an additional layer of complexity to the software development project -- the effort of defining these interfaces. But we expect the investment to reap major rewards in mere days as this model of development will allow each group to actively develop their part of the application without waiting on another group to complete theirs first. While this could be done without ZCA, essentially all of ZCA is designed to help facilitate this style of development.

Now that you understand this, you will have a better appreciation for why ZCA is designed the way it is. It is trying to provide a framework for building larger projects that cannot be handled by a single developer. It does add some complexity to your project. This is intentional. You should only use ZCA if you expect that the additional complexity will be more than offset by improved productivity, maintainability and software quality that can arise from a component-based architecture. So, is it right for you? There is no one answer to this. The right answer will depend on the complexity of the project, your development team, and other factors.

At least now you know what ZCA is designed for, so you will be able to evaluate it fairly :)

Martin Aspeli has written a defense of ZCA.

The Parts of ZCA

This section describes the "components" of ZCA itself. One of the most complicated things about ZCA is the new terminology that it uses for its various parts. Once you understand this terminology, ZCA is a lot more approachable. Also see [1]

Components
A component is Python-based code, usually a class, that implements an Interface (defined below.)
Interfaces
Interfaces are special Python classes that are declared for the sole purpose of describing functionality implemented by another class or module. They are registered with a component registry, and others can query the registry for components that implement a particular interface. Objects can advertise that they implement a particular interface. From a code perspective, interfaces are the heart of ZCA.
Services
A service is generally a Python object that deals with components, and provides some functionality to your application. Think of components as "things", and services as parts of your code that take these "things" and perform useful actions with them.
Component Registry
The Component Registry, also called a Site Manager, is a directory of components, and acts as the central organizational hub of ZCA. It is a service (as defined above.) You register components with the component registry, and then others can query the registry for components that they need. There is a global component registry that is automatically created for you, and you can use the ZODB as a local component registry.
Global Component Registry
The Global Component Registry a service that is automatically created when your ZCA-based application starts, and is populated with components based on your application's ZCML configuration files. Any part of your application can access the Global Component Registry. When your application shuts down, the Global Component Registry no longer exists. It will be re-initialized from ZCML when your application is started again.
Local Components
Local components are components that are not instantiated as part of the Global Component Registry. They are typically stored in the traditional Zope storage infrastructure, the ZODB, which also means that they are persistent, in that they will survive multiple application starts/stops. Because they exist in the ZODB, Zope will use acquisition (a traditional Zope way of finding things in the ZODB) to retrieve local components when your application queries the ZODB.
ZCML
Pronounced "Z-camel", ZCML is an XML-based configuration file syntax that is used to register components with the global component registry. It is useful in that it provides a mechanism to add new components using configuration files rather than touching source code. It also provides a mechanism to easily extend ZCA code by overriding existing components with new implementations -- all without touching existing Python source code.
Adapters
Adapters are Python classes that allow new interfaces to be used with existing objects. More specifically, an Adapter class will contain an adapts() call in the class body that specifies that it adapts an interface, which means that you can pass any object that implements that interface to the adapter class' __init__() method, such as a = myAdapter(myobject). MyAdapter now takes care of acting as a front-end for myobject so that any interfaces implemented by myAdapter will interact properly with myobject. In a sense, the adapter "eats" the adapted object.
Factories
A factory is something (technically, a Utility, see below) that creates components and objects. It is like a higher-level version of a constructor. A Factory must be callable and implement the IFactory interface. Factories offer a convenient means to access component constructors via a component registry, rather than connecting to the constructor directly in Python code. It's important to note that ZCML creates factories for you automatically when you register components. This means that in many cases, you do not need to deal directly with Factory creation.
Utilities
A utility is a Python object that you want to add to the component registry. It could be any type of Python object that another part of your application may need. A utility must implement at least one interface. The Zope Documentation has some guidance on when it's appropriate to create a utility.
Multi-Adapters
Multi-adapters are adapters that have the ability to adapt one than more type of object.
Subscription Adapters
Adapters can be registered as subscription adapters, and then all adapters that adapt a particular object can be retrieved by calling the zope.component.subscribers() function. This is useful when doing things like validation, where you want to get back a bunch of adapters that apply to a specific object. Subscription adapters can return a value.
Handlers
Handlers are like subscription adapters, but they do not return a value to the caller. They go off, "handle" something, and then return.
Overrides
If you register multiple components with the same arguments, the last component registered will replace any previously-registered components. This behavior is used to replace existing components with new ones that may implement new functionality.
Invariants
Invariants are functions that you attach to your interfaces that throw exceptions when certain conditions aren't met. Invariants are invoked by calling the interface's validateInvariants method, passing the object to be validated as an argument.

ZCML

The official specification for ZCML is viewable in the Zope 3 APIDoc site, although this may not accurately reflect the API in Zope 2.13. In terms of explanatory, conceptual documentation, there does not seem to be anything good available, and this section will attempt to fill that gap.

For now, read the Five Manual and work through the demos directory.

<?xml version="1.0" encoding="utf-8"?>
<configure
        xmlns="http://namespaces.zope.org/zope"
        xmlns:browser="http://namespaces.zope.org/browser"
        xmlns:five="http://namespaces.zope.org/five">
</configure>

Check out this excellent Zope Component Architecture basics with five.grok tutorial from Martin Aspeli.