Metastorage example application implemented with Use Case mapping |
Author: Manuel Lemos (mlemos-at-acm.org) Keywords: use cases, use case mapping, UML, RUP, Rational Unified Process, Agile development process, PHP Web application development, Metastorage, Metanews Version control: @(#) $Id: example.documentation,v 1.15 2006/01/29 15:19:34 mlemos Exp $ ContentsMetastorage example application implemented with Use Case mapping
This document is an introduction on how to develop Web applications that use the code generated by Metastorage. It is meant to guide the Web developers through the necessary steps to build Web applications, using Metastorage as a means to reduce the project development cycle times. A methodology named use case mapping is also presented in this document. It is a methodology for developing consistent PHP Web applications, whether they use Metastorage generated code or not. Metastorage is a tool that generates several types of software components that can be incorporated Web applications. However, in the current version, Metastorage does not generate complete Web applications that can be executed right away. It is still necessary to develop more components that act as glue to implement complete Web applications. Such glue components can be Web page scripts, application logic components, and other types of components which provide functionality that is outside of the scope of Metastorage. A complete Web application can be built using many different methodologies. This document presents one methodology named "Use case mapping". It does not mean that this is necessarily the best Web development methodology, nor that you could not use another methodology that you may prefer. It is a methodology that I have been using for developing PHP Web applications with very satisfactory productivity results. I have been using it since 1999, when Object Oriented Programming support was added to PHP 3. Over time, it has been refined to address better the real world needs of sites of growing complexity. This methodology has been used extensively to develop busy sites like the PHPClasses repository. Therefore, it has proven to be suitable to develop enterprise grade Web applications. It does not impose excessively complex development procedures. So, it is also suitable for developing small Web applications. Use cases are basically the different types of situations that an application must handle to interact with the application actors. Use cases are often referred to as "screens" or "pages", like for instance: the login page, the search page, the articles pages, etc... Actors are usually the application users or external systems with which the application must interact. Use cases are not only about situations that generate output that an user can view when it occurs. An use case may need to handle a situation that only requires to interact with non-human external systems. For instance, sending an automated newsletter to subscribers listed in a database, or fetching weather forecast information from a remote system. Usually, a well structured software process requires some planning. Even when you are developing a small project for your own use, it is always good to give some thought before start implementing it. Many developers do not like planning, either because it is boring, or because they do not know enough about good methodologies to make proper software planning. The Rational Unified Process is a well-known software development process that recommends a plan-driven development of projects that are divided in iterations. Agile is another well-known software development process that also recommends iterative development, but in shorter cycles. The idea is to be able to develop software than can be released earlier and often. That would allow making eventual adjustments to the project requirements and subsequent planning, based on information realized later during the implementation process. Developers that do not like planning much, often prefer Agile software development process because it lets them start writing code sooner and see the results more often. Regardless of the software development process you prefer, by the time you start implementing your project, you must have already a good idea of which use cases you are going to implement in each project iteration. You also need to have a good idea of what each use case must do. It is always good to document your project use cases. This is is specially true when you are developing a project for a client. Even when the client does not understand enough about software development, if you show him a use case diagram and use case descriptions, he will most likely understand and trust that you are doing what he expects. When the client trusts you have the competence to do what he wants, he is more open to pay you more for your work, and will prefer ordering more work from you instead of your competitors. Therefore, the additional work that it takes to create proper project documentation may compensate financially. An use case diagram is an artifact of the UML language that can help you modeling use cases. UML is a visual language that can be used to create visual representations of different perspectives of a software system. An UML use case diagram can help representing the use cases that you want to implement. Use case diagrams are presented in an way that is easy to understand, even by people that is not familiar with software development processes, like clients and sponsors. However, use case diagrams are not sufficient to describe the relevant details of each use case. It is not absolutely necessary to describe use cases in detail before start writing the code that implements them. A small application written by a single developer may not need to be formally documented because the developer is capable to keep all the relevant details in mind. Regardless if a project is formally documented or not, there are several details about an use case that are very important. Understanding these details very well, helps you to write the necessary code to implement the use cases correctly. These are the most important use case details: There are many ways to implement Web applications use cases. This document focus in the use case mapping methodology. This methodology consists in implementing all the logic of an use case in two parts: a Web script and a business rules component. The purpose of the Web script is to receive the HTTP request and return a response. The role of the business rules component is to handle all the situations planned in the use case specification. A use case Web script is usually very simple. It performs the same tasks for each use case: Here is an example of an use case Web script: Load the application componentsdefine('APPLICATION_PATH', '..'); define('METABASE_PATH', APPLICATION_PATH.'/../../../metabase'); define('FORMS_PATH', APPLICATION_PATH.'/../../../forms'); require(METABASE_PATH.'/metabase_database.php'); require(METABASE_PATH.'/metabase_interface.php'); require(FORMS_PATH.'/forms.php'); require(APPLICATION_PATH.'/configuration/options.php'); require(APPLICATION_PATH.'/templates/page.php'); require(APPLICATION_PATH.'/templates/window.php'); require(APPLICATION_PATH.'/components/metanews/metanews.php'); require(APPLICATION_PATH.'/components/metanews/article.php'); require(APPLICATION_PATH.'/components/metanews/submitarticleform.php'); require(APPLICATION_PATH.'/usecases/metanews/submit_article.php'); Invoke the business rules component$options = new configuration_options_class; $options->application_path = APPLICATION_PATH; $options->initialize(); $case = new use_case_submit_article_class; $case->options = &$options; $success = ($case->initialize() && $case->process()); if($success) $case->output(); Process runtime errorsif(!$success) { require(APPLICATION_PATH.'/usecases/error.php'); $error_case = new use_case_error_class; $error_case->options = &$options; $error_case->error = $case->error; $error_case->debug = $case->debug; if($error_case->initialize() && $error_case->process()) $error_case->output(); } The business rules component implements most of the logic to handle each use case:
In the use case mapping methodology, each business rules component is implemented in practice using a single class. It should implement a common interface that has the following structure:
The three public functions initialize(), process() and output() constitute the use case class interface. Each of these three public functions of the use case class interface implement an important part of the use case logic: initialize()
process()
output()
In practice the use case classes have a skeleton like this: class use_case_skeleton_class { /* Public variables */ var $error = ''; /* Private variables */ var $verified_pre_conditions = false; var $prepared_output = ''; /* Private functions may go here */ /* Public functions */ Function initialize() { /* ... */ $this->verified_pre_conditions = $pre_conditions_are_met; /* ... */ if($an_error_occurred) { $this->error = 'some explanatory error message'; return false; } return true; } Function process() { if($this->verified_pre_conditions == false) return true; /* Execute use case processing tasks */ $this->prepared_output = $data_to_output; if($an_error_occurred) { $this->error = 'some explanatory error message'; return false; } return true; } Function output() { echo $this->prepared_output; } }; Here is a real example of an use case class: class use_case_submit_article_class { Public variables to pass application options to the class, and to return error and debug messages var $options; var $error = ''; var $debug = ''; Auxiliary private variables var $user; var $page; var $factory; var $form; var $window; initialize function Function initialize() { Verify whether the use case pre-conditions are met /* * Verify whether the user is authenticated */ $this->user = new user_authenticate_class; $this->user->options = &$this->options; $success = ($this->user->initialize() && $this->user->process()); $this->debug.=$this->user->debug; if(!$success) { $this->error = $this->user->error; return(0); } Initialize auxiliary variables and external objects if($this->user->authenticated) { /* * Initialize the factory class */ $this->factory = &new metanewsclass; $this->factory->debug = $this->options->debug; $this->factory->connection = $this->options->database_connection; $this->factory->includepath = METABASE_PATH; if(!$this->factory->initialize()) { $this->error = $this->factory->error; return(0); } /* * Initialize the submit article form class */ $this->form = &new submitarticleformclass; $this->form->images = 'graphics/'; $this->form->text['title'] = HtmlSpecialChars('Submit a new article'. ' - '.$this->options->application_name); $this->form->text['warningtitle'] = HtmlSpecialChars('Warning!'. ' - '.$this->options->application_name); if(!$this->form->initialize($this->factory)) { $this->error = $this->form->error; return(0); } } /* * Initialize the page template class */ $this->page = &new template_page_class; $this->page->title_prefix = $this->options->application_name; $this->page->title = 'Submit a new article'; return(1); } process function Function process() { Do nothing if the use case pre-conditions are not met if(!$this->user->authenticated) return(1); Implement the actions associated with the normal and alternative flows of events /* * Process the form */ $success = $this->form->process(); Returns false when an unexpected runtime error happens /* * If there was a problem, retrieve the error message */ if(!$success) $this->error = $this->form->error; /* * Finalize pending operations, if any */ $this->factory->finalize(); /* * Retrieve debug output, if any */ $this->debug.= $this->factory->debugoutput; return($success); } output function Function output() { Generates the Web script output $this->page->Header(); if(!$this->user->authenticated) { /* * Let the user know that the article submission can only be made * by an authenticated system administrator */ ?> <h2 class="importantmessage">Only the system administrator is allowed to submit new articles.</h2> <?php $this->user->output(); ?> <p class="importantmessage"><a href="index.php">List articles</a></p> <?php } Use result information passed by the process() function using the class private variables elseif($this->form->processed) { /* * Let the user know that the article submission succeeded */ ?> <h2 class="importantmessage">The article was submission successfully.</h2> <p class="importantmessage"><a href="index.php">List articles</a></p> <?php } elseif($this->form->canceled) { /* * Let the user know that the article submission was canceled */ ?> <h2 class="importantmessage">The article submission was canceled.</h2> <p class="importantmessage"><a href="index.php">List articles</a></p> <?php } else { /* * If the article submission is not yet done, output the form */ echo $this->form->output(); } if(strlen($this->debug)) { /* * Output the debug information in debug mode */ ?><pre><?php echo HtmlSpecialChars($this->debug); ?></pre><?php } $this->page->Footer(); } }; An use case class may fail due to an unexpected runtime error. When that happens, either of the initialize() or the process() use case class functions return false. Following the use case mapping methodology, unexpected runtime errors should be handled by a special error handling use case class. That class implements the same interface and should be called the same way at the end of the Web script. Here follows a Web script excerpt that shows how the error handling use case class is invoked: $case = new use_case_submit_article_class; $case->options = &$options; $success = ($case->initialize() && $case->process()); if($success) $case->output(); if(!$success) { require(APPLICATION_PATH.'/usecases/error.php'); $error_case = new use_case_error_class; $error_case->options = &$options; $error_case->error = $case->error; $error_case->debug = $case->debug; if($error_case->initialize() && $error_case->process()) $error_case->output(); } This special use case class is like an exception class, except that it can work with PHP 4 on which exception handling support is not available. It provides consistent error handling behavior. It works the same way regardless of the type of error that may have occurred. It may receive error context information via public variables, like for instance an error message, or debug log messages returned by the main use case class. The error handling class may perform several types actions. Such actions may be useful to help the system administrators or the developers to realize what happened and why, so they can act promptly and fix any problems as soon as possible. Common error handling actions may include recording the error message in a log file, notify the system administrators by e-mail, pager, instant messaging, etc.. The output of the use case must be an user-friendly message that should not cause user panic. That message also must not include sensitive details that could be used by malicious users to compromise the system security. Here is an example of an error handling use case class: class use_case_error_class { var $options; var $error = ''; var $debug = ''; var $page; var $window; Function initialize() { /* * Initialize the page and window frame generation template classes * that will be used by the output function */ $this->page = new template_page_class; $this->page->title_prefix = $this->options->application_name; $this->page->title = 'Application problem'; $this->window = new template_window_class; $this->window->title = 'Application problem!'; $this->window->center = 1; $this->window->background_color = $this->options->windows_background_color; return(1); } Function process() { /* * Here the class could execute important actions to help the * developers or system administrators to fix the problem promptly like: * * - Log the errors to a file * - Send an e-mail to the system administrator * - Etc. */ return(1); } Function output() { $this->page->Header(); $this->window->Header(); /* * Display an user friendly error message * without disclosing any details about the actual error */ ?> <p style="text-align: center"><b>Sorry, for the time being<br /> <?php echo HtmlEntities($this->options->application_name); ?> is not available.</b></p> <?php $this->window->Footer(); /* * In debug mode any details about the actual error */ if($this->options->debug) { ?> <hr /> <p><b>Error</b>: <?php echo HtmlEntities($this->error); ?></p> <?php if(strlen($this->debug)) { ?><pre><?php echo HtmlSpecialChars($this->debug); ?></pre><?php } } $this->page->Footer(); } }; An application may be made of many files for many different purposes. Usually each file should be stored in a directory according to its purpose. Using a consistent application directory structure helps developers understanding better their applications. This way they can find faster the files they need to create and maintain. The following directory structure is a proposal that addresses the needs of most types of Web applications. It is not necessarily the only directory structure that may be right for Web applications. It is a recommendation that may be followed by anybody. Eventually, you may rename, add, remove or rearrange directories according to your needs. Under restricted hosting environments it may not be possible to put the base directory for Web pages accessible to the public as a sub-directory of the installation directory named web, as it is proposed. In that case it is acceptable to make the base Web page scripts directory the same as the installation directory, as long as sensitive information files do not become accessible via Web, like for instance the configuration files that contain database access user names and passwords. /path/to/application/ - installation directory | |-> usecases/ - use case implementation classes | |-> web/ - Web page scripts | | | |-> graphics/ - static image files used in the site | | | -> css/ - CSS files used in the site | |-> templates/ - files and classes that define presentation aspects | |-> components/ - general purpose classes | |-> configuration/ - configuration files and scripts | |-> locale/ - configuration and text files to support different idioms | |-> setup/ - installation files and scripts | |-> backend/ - maintenance files and scripts | -> logs/ - events and error log files
Metanews is the name of simple application written with the help of code generated by Metastorage. It is a simples news publication system that lets a news editor submit articles. The published articles are listed in their own Web pages. By all means, it is not anything near a complete article publication system. It is just meant to illustrate how common aspects of Web applications can be implemented by glueing custom handwritten code with Metastorage generated code. Despite Metanews is a simple project for educational purposes, it required some planing. The planing consisted in looking at the project requirements and see which use cases would need to be implemented. An use case diagram was elaborated with the help of a UML modeling program named Umbrello. This is a free Linux program that is now part of the software development kit package of the KDE system. There are plenty of other free and commercial UML tools that you can use to design UML diagrams. These tools can save UML projects in the XMI. This is a XML based format meant for exchange project metadata between different tools. The Metanews use case diagram is available as part of UML project saved in XMI format. The Metanews XMI project file comes with the project source code distribution. It looks like this: Three main use cases were considered to develop in a first iteration of the Metanews project: Submit article, Show latest articles, and Show article. There is an additional use case that was not included in the use case diagram. That use case is for installing the application database schema. Lets document these use cases, so we can have a good perspective of how they can be implemented: Below follows the structure of files and directories of the Metanews application. Some directories have a sub-directory named metanews that contains files that belong specifically to the Metanews project. Using sub-directories this way is recommended in case you plan to install several applications or modules that coexist in the same Web site. The source directory is listed for learning purposes. In a real Web site production environment that directory is not need and should not be installed. metal/metastorage/applications/metanews/ | |-> components/ | | | |-> metanews/ | | | | | |-> article.php - Article persistent class | | | | | |-> articlesreport.php - Article reports class | | | | | |-> metanews.php - Metanews factory class | | | | | -> submitarticleform.php - Submit article form class | | | -> users/ | | | -> authenticate.php - User authentication class | |-> configuration/ | | | -> options.php - Application options class | |-> setup/ | | | -> metanews/ | | | |-> metanews.schema - Database schema definition | | | -> metanewsschema.php - Database installation class | |-> source/ | | | |-> components/ | | | | | |-> articles.report - Articles report class definition | | | | | |-> metanews.component - Metanews component definition | | | | | -> submitarticle.form - Submit article form class definition | | | -> uml/ | | | -> metanews.xmi - UML Use cases and classes diagrams | |-> templates/ | | | |-> page.php - Page header and footer generation class | | | -> window.php - Window frame generation class | |-> usecases/ | | | |-> error.php - Error handling use case class | | | -> metanews/ | | | |-> latest_articles.php - Show latest articles use case class | | | |-> show_article.php - Show article use case class | | | |-> start.php - Installation use case class | | | -> submit_article.php - Submit article use case class | -> web/ | |-> css/ | | | -- styles.css - CSS styles sheet | |-> graphics/ | | | |-> close.gif - Window close button image | | | -> rss.gif - RSS icon image | |-> index.php - Show latest articles page | |-> show_article.php - Show article page | |-> start.php - Installation page | -> submit_article.php - Submit article page
Each use case uses its own set of scripts and class files. Some were generated by Metastorage, others were handwritten. There are several groups of files located in different directories that are shared by practically all use cases: The installation use case is implemented using files for the schema definition and the database installation class that are located in the directory setup/metanews. The database installation class is capable of installing the database schema generated automatically by Metastorage. The schema includes all the tables that are necessary to store the objects of the persistent classes specified in the project component definition. Metastorage generates a database installation class that can either install the schema for the first time, or upgrade to a newer revision, also generated by Metastorage, after eventual changes made to the project component definition. When the database schema is installed, the file metanews.schema is copied to metanews.schema.installed. The installation use case class uses this information to determine whether the database schema is being installed for the first time, or is being upgraded to a newer revision, or if nothing happened when the installed schema revision is already the most upto date. The submit article use case is implemented using a form handling class located in the directory components/metanews. This class is also generated by Metastorage. The article submission form class takes care of most of the use case processing logic, including form validation, article object initialization, validation and storage. It also takes care of the situation when the user cancels the form submission. Since the use case has as pre-condition that the user must be authenticated, this verification is the first thing that is done in the initialize function of the submit article use case class. The authentication is done using a special use case class located in the components/users directory. This class performs basic HTTP user authentication. It verifies whether the user and password match those defined by the application options administrator and administrator_password. The show latest articles use case is implemented using a report data extraction class handling class located in the directory components/metanews. This class is also generated by Metastorage. The report class retrieves all the necessary data to present upto 10 of the latest articles into a class private array variable. The class iterates over this array to format the article information. The class also generates a link to a page that generate the same articles listing as an RSS 1.0 feed. This link points to same page but it takes an additional request parameters that tells the class to generate the RSS feed instead of the normal articles listing page. The show article use case is implemented using the same report data extraction class handling class generated by Metastorage that is located in the directory components/metanews. The report class retrieves all the data to present an article with an identifier passed by a page request parameter. The article data is also stored in a class private array variable that is accessed by the class output function to present the article in detail. Metastorage is a powerful code generation tool, but obviously it does not generate all the parts of a Web application. That is not its main goal either. However, the Metanews application demonstrated how Metastorage can be used already to generate a significant amount of code of a complete Web application, that otherwise would take a lot more time to write, test and debug by hand. The use of a mature and consistent development methodology, as the use case mapping, also helps the developer to become more productive once he gets used to work systematically using the same development procedures. Therefore, the adoption of productive tools and methodologies like Metastorage and use case mapping can obviously contribute in a significative way to the moral and financial satisfaction of the Web developer. |