You can also have the staging and production servers running on the
same machine. This is not ideal, especially if the production server
needs every megabyte of memory and every CPU cycle so that it can
cope with high request rates. But when a dedicated machine just for
staging purposes is prohibitively expensive, using the production
server for staging purposes is better than having no staging area at
all.
Another possibility is to have the staging environment on the
development machine.
So how does this three-tier scheme work?
-
Developers write the code on their machines (development tier) and
test that it works as expected. These machines should be set up with
an environment as similar to the production server as possible. A
manageable and simple approach is to have each developer running his
own local Apache server on his own machine. If the code relies on a
database, the ideal scenario is for each developer to have access to
a development database account and server, possibly even on their own
machines.
-
The pre-release manager installs the code on the staging tier machine
and stages it. Whereas developers can change their own
httpd.conf files on their own machines, the
pre-release manager will make the necessary changes on the staging
machine in accordance with the instructions provided by the
developers.
-
The release manager installs the code on the production tier
machine(s), tests it, and monitors for a while to ensure that things
work as expected.
Of course, on some projects, the developers, the pre-release
managers, and the release managers can actually be the same person.
On larger projects, where different people perform these roles and
many machines are involved, preparing upgrade packages with a
packaging tool such as RPM becomes even more important, since it
makes it far easier to keep every machine's
configuration and software in sync.
Now that we have described the theory behind the three-tier approach,
let us see how to have all the code independent of the machine and
base directory names.
Although the example shown below is simple, the real configuration
may be far more complex; however, the principles apply regardless of
complexity, and it is straightforward to build a simple initial
configuration into a configuration that is sufficient for more
complex environments.
Basically, what we need is the name of the machine, the port on which
the server is running (assuming that the port number is not hidden
with the help of a proxy server), the root directory of the web
server-specific files, the base directories of static objects and
Perl scripts, the appropriate relative and full URIs for these base
directories, and a support email address. This amounts to 10
variables.
We prepare a minimum of three Local::Config
packages, one per tier, each suited to a particular
tier's environment. As mentioned earlier, there can
be more than one machine per tier and even more than one web server
running on the same machine. In those cases, each web server will
have its own Local::Config package. The total
number of Local::Config packages will be equal to
the number of web servers.
Example 5-3. Local/Config.pm
package Local::Config;
use strict;
use constant SERVER_NAME => 'dev.example.com';
use constant SERVER_PORT => 8000;
use constant ROOT_DIR => '/home/userfoo/www';
use constant CGI_BASE_DIR => '/home/userfoo/www/perl';
use constant DOC_BASE_DIR => '/home/userfoo/www/docs';
use constant CGI_BASE_URI => 'https://dev.example.com:8000/perl';
use constant DOC_BASE_URI => 'https://dev.example.com:8000';
use constant CGI_RELATIVE_URI => '/perl';
use constant DOC_RELATIVE_URI => '';
use constant SUPPORT_EMAIL => '[email protected]';
1;
The constants have uppercase names, in accordance with Perl
convention.
The configuration shows that the name of the development machine is
dev.example.com, listening to port 8000. Web
server-specific files reside under the
/home/userfoo/www directory. Think of this as a
directory www that resides under user
userfoo's home directory,
/home/userfoo. A developer whose username is
userbar might use
/home/userbar/www as the development root
directory.
If there is another web server running on the same machine, create
another Local::Config with a different port number
and probably a different root directory.
Example 5-4. Local/Config.pm
package Local::Config;
use strict;
use constant DOMAIN_NAME => 'example.com';
use constant SERVER_NAME => 'dev.' . DOMAIN_NAME;
use constant SERVER_PORT => 8000;
use constant ROOT_DIR => '/home/userfoo/www';
use constant CGI_BASE_DIR => ROOT_DIR . '/perl';
use constant DOC_BASE_DIR => ROOT_DIR . '/docs';
use constant CGI_BASE_URI => 'https://' . SERVER_NAME . ':' . SERVER_PORT
. '/perl';
use constant DOC_BASE_URI => 'https://' . SERVER_NAME . ':' . SERVER_PORT;
use constant CGI_RELATIVE_URI => '/perl';
use constant DOC_RELATIVE_URI => '';
use constant SUPPORT_EMAIL => 'stas@' . DOMAIN_NAME;
1;
Reusing constants that were previously defined reduces the risk of
making a mistake. In the original file, several lines need to be
edited if the server name is changed, but in this new version only
one line requires editing, eliminating the risk of your forgetting to
change a line further down the file. All the use
constantstatements are executed at compile time, in the
order in which they are specified. The constant
pragma ensures that any attempt to change these variables in the code
leads to an error, so they can be relied on to be correct. (Note that
in certain contexts—e.g., when they're used as
hash keys—Perl can misunderstand the use of constants. The
solution is to either prepend & or append
( ), so ROOT_DIR would become
either &ROOT_DIR or ROOT_DIR(
).)
Example 5-7. Local/Config.pm
package Local::Config;
use strict;
use constant DOMAIN_NAME => 'example.com';
use constant SERVER_NAME => 'www.' . DOMAIN_NAME;
use constant SERVER_PORT => 8000;
use constant ROOT_DIR => '/home/';
use constant CGI_BASE_DIR => ROOT_DIR . '/perl';
use constant DOC_BASE_DIR => ROOT_DIR . '/docs';
use constant CGI_BASE_URI => 'https://' . SERVER_NAME . ':' . SERVER_PORT
. '/perl';
use constant DOC_BASE_URI => 'https://' . SERVER_NAME . ':' . SERVER_PORT;
use constant CGI_RELATIVE_URI => '/perl';
use constant DOC_RELATIVE_URI => '';
use constant SUPPORT_EMAIL => 'support@' . DOMAIN_NAME;
You can see that the setups of the staging and production machines
are almost identical. This is only in our example; in reality, they
can be very different.
The most important point is that the Local::Config
module from a machine on one tier must never be
moved to a machine on another tier, since it will break the code. If
locally built packages are used, the Local::Config
file can simply be excluded—this will help to reduce the risk
of inadvertently copying it.
From now on, when modules and scripts are moved between machines, you
shouldn't need to worry about having to change
variables to accomodate the different machines'
server names and directory layouts. All this is accounted for by the
Local::Config files.