http://www.perlmonks.org?node_id=601474

In my node How to build a Search Engine. I mentioned that I was maintaining a large content management system. Now I am working on revamping its internal domain management system. I realized this would make a good example as to why to use MVC, and how to make it beneficial.

I am rewriting the portion of my CMS that is responsible for setting up new domains (and in some cases, removing them). During construction of a new domain, some of the domain management system's (DMS) responsibilities include: creating system users and groups, updating the file system, creating a database, generating a virtual host config from Apache, and other virtual host stuff.

So what does the DMS have to with Model-View-Controller architecture? I want to be able to create, modify, and destroy domains from both a website (an administration console) and from the command line. Why both? The website can be used by any admin, so I don't always have to be the one to do it. The command line gives me batch provisioning capabilities that would be too much work to implement on the web for the few times I would use it.

By designing things with the MVC paradigm in mind, I can create both the web-base and command-line versions without any duplication of code!

Let's quickly review the three components in an MVC stack:

  • Model: While often associated only with data, the model should encapsulate all of your business logic and validation. In this case, the model will be a library responsible for configuring the OS for a new virtual host.
  • Controller: Responsible for controlling access to the model; authentication, authorization, work flow, etc.
  • View: Responsible for generating output to the user. The web-base interface will generate HTML, while the command-line version will generate Text.

Model:

This will be the one component shared between both the web-based and command-line versions of our DMS. First, we will have a module that represents a Domain's configuration. This may be saved in a DB, or config files; that isn't important now.

package Domain; sub load { ... } sub getAddress { ... } #etc. 1

And we will have a module responsible for installing and removing domains (virtual hosts).

package DomainManager; sub install { my ($this, $domain) = @_; # create user # create files # create db # set up apache # etc return @results; } sub uninstall { my ($this, $domain) = @_; # remove user # remove files # remove db # remove apache config # etc return @results; } 1

View:

What is @results? We could just return a list of lines of text, but we may want to format the output differently based on if it is an error or not. Also, on the web we may want errors red, and non-errors green, while on the command line (where we might not have/want color) we may want "[ERROR] ..." versus [ OK ] ...".

So, we will use an object to represent each line. Both install and uninstall will return a list of these objects.

package DomainManagerOutput; sub new { ... } sub isError { ... } sub getText { ... } 1

Controller:

Now that we have all of our business logic (both the data representing a domain and a manager to install and uninstall domains) in a central set of modules, we can use them both from a web application (CGI, mod_perl, etc.), and a command-line script.

#!/usr/bin/perl -T use strict; use warnings; use Domain; use DomainManager; # Do authentication, authorization, etc. my $domain = Domain->load(...); my @results = DomainManager->install($domain); print "Content-type: text/html\n\n"; print "<html><body>"; for (@results) { if ($_->isError) { print "<div style='color: red'>" . $_->getText . "</div>"; } else { print "<div style='color: green'>" . $_->getText . "</div>"; } } print "</body></html>"; ----------------------------------------------------------- #!/usr/bin/perl use strict; use warnings; use Domain; use DomainManager; # Parse command line, etc. my $domain = Domain->load(...); my @results = DomainManager->install($domain); for (@results) { if ($_->isError) { print "[ERROR]" . $_->getText . "\n"; } else { print "[ OK ]" . $_->getText . "\n"; } }

Now you can see why we didn't just return a list of text lines.

On a side note, we really should go a step further, and not put HTML inside our CGI script. That should be pulled out to a template file so it too can be reused (further adhering to the MVC paradigm). But, I want to keep the example concise.

Summary:

So, how often does one create both a command-line and a web-base interface to the same functionality? Maybe not too often. But there are many other reasons to use MVC. For instance, you application could be accessed, not only via a web browser, but from a cell phone or PDA, as a web service, or via a cron script. You never know where you application is going to go, so it is best to be prepared.

Also, good MVC design has other benefits, even when there is only one mode of access. These include a centralize system of styling all output (that could be maintained by a non-programmer website designer), centralization of common controller functionality (like authentication and authorization), and in general, better code reuse through proper abstraction.

On the other side of things, there is a point of diminishing returns. How well you adhere to MVC is something that you must decide on a per-project basis. For the simplest of sites, it may not be worth the extra time, to go all out. But even keeping the fundamental principles of MVC in mind, in even these simplest applications, can save time and minimize bugs, while maximizing extensibility.

Ted Young

($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)