• Aucun résultat trouvé

Web server MVC

Dans le document Dave Crane Eric Pascarello (Page 123-133)

Introducing order to Ajax

3.4 Web server MVC

Web applications are no stranger to MVC, even the classic page-based variety that we spend so much time bashing in this book! The very nature of a web application enforces some degree of separation between the View and the Model, because they are on different machines. Does a web application inherently follow the MVC pattern then? Or, put another way, is it possible to write a web application that tangles the View and the Model together?

Unfortunately, it is. It’s very easy, and most web developers have probably done it at some point, the authors included.

Most proponents of MVC on the Web treat the generated HTML page, and the code that generates it, as the View, rather than what the user actually sees when that page renders. In the case of an Ajax application serving data to a JavaScript client, the View from this perspective is the XML document being returned to the client in the HTTP response. Separating the generated document from the busi-ness logic does require a little discipline, then.

3.4.1 The Ajax web server tier without patterns

To illustrate our discussion, let’s develop an example web server tier for an Ajax application. We’ve already seen the fundamentals of the client-side Ajax code in chapter 2 and section 3.1.4, and we’ll return to them in chapter 4. Right now, we’ll concentrate on what goes on in the web server. We’ll begin by coding it in the simplest way possible and gradually refactor toward the MVC pattern to see how it benefits our application in terms of its ability to respond to change. First, let’s introduce the application.

We have a list of clothes in a clothing store, which are stored in a database, and we want to query this database and present the list of items to the user, showing an image, a title, a short description, and a price. Where the item is available in sev-eral colors or sizes, we want to provide a picker for that, too. Figure 3.6 shows the main components of this system, namely the database, a data structure represent-ing a srepresent-ingle product, and an XML document to be transmitted to our Ajax client, listing all the products that match a query.

Let’s say that the user has just entered the store and is offered a choice between Menswear, Womenswear, and Children’s clothing. Each product is assigned to one of these categories by the Category column of the database table named Gar-ments. A simple piece of SQL to retrieve all relevant items for a search under Menswear might be

SELECT * FROM garments WHERE CATEGORY = 'Menswear';

We need to fetch the results of this query and then send them to the Ajax appli-cation as XML. Let’s see how we can do that.

Generating XML data for the client

Listing 3.5 shows a quick-and-dirty solution to this particular requirement. This example uses PHP with a MySQL database, but the important thing to note is the general structure. An ASP or JSP page, or a Ruby script, could be con-structed similarly.

<?php

header("Content-type: application/xml");

echo "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";

$db=mysql_connect("my_db_server","mysql_user");

mysql_select_db("mydb",$db);

$sql="SELECT id,title,description,price,colors,sizes"

."FROM garments WHERE category=\"{$cat}\"";

$result=mysql_query($sql,$db);

echo "<garments>\n";

while ($myrow = mysql_fetch_row($result)) { printf("<garment id=\"%s\" title=\"%s\">\n"

Listing 3.5 Quick-and-dirty generation of an XML stream from a database query

ORM Template engine Client-side parser

<?xml version="1.0"?>

1. Database tables

2. Object model 3. XML stream

4. Web browser

Figure 3.6 Main components used to generate an XML feed of product data in our online shop example. In the process of generating the view, we extract a set of results from the database, use it to populate data structures representing individual garments, and then transmit that data to the client as an XML stream.

Tell client we are returning XML

Fetch the results from the database

Iterate through resultset

Web server MVC 95

."<description>%s</description>\n<price>%s</price>\n", $myrow["id"],

$myrow["title"], $myrow["description"], $myrow["price"]);

if (!is_null($myrow["colors"])){

echo "<colors>{$myrow['colors']}</colors>\n";

}

if (!is_null($myrow["sizes"])){

echo "<sizes>{$myrow['sizes']}</sizes>\n";

}

echo "</garment>\n";

}

echo "</garments>\n";

?>

The PHP page in listing 3.5 will generate an XML page for us, looking something like listing 3.6, in the case where we have two matching products in our database.

Indentation has been added for readability. We’ve chosen XML as the communi-cation medium between client and server because it is commonly used for this purpose and because we saw in chapter 2 how to consume an XML document gen-erated by the server using the XMLHttpRequest object. In chapter 5, we’ll explore the various other options in more detail.

<garments>

<garment id="SCK001" title="Golfers' Socks">

<description>Garish diamond patterned socks. Real wool.

Real itchy.</description>

<price>$5.99</price>

<colors>heather combo,hawaiian medley,wild turkey</colors>

</garment>

<garment id="HAT056" title="Deerstalker Cap">

<description>Complete with big flappy bits.

As worn by the great detective Sherlock Holmes.

Pipe is model's own.</description>

<price>$79.99</price>

<sizes>S, M, L, XL, egghead</sizes>

</garment>

</garments>

So, we have a web server application of sorts, assuming that there’s a nice Ajax front end to consume our XML. Let’s look to the future. Suppose that as our prod-uct range expands, we want to add subcategories (Smart, Casual, Outdoor, for

Listing 3.6 Sample XML output from listing 3.5

example) and also a “search by season” function, maybe keyword searching, and a link to clearance items. All of these features could reasonably be served by a sim-ilar XML stream. Let’s look at how we might reuse our current code for these pur-poses and what the barriers might be.

Problems with reusability

There are several barriers to reusing our script as it stands. First, we have hard-wired the SQL query into the page. If we wanted to search again by category or keyword, we would need to modify the SQL generation. We could end up with an ugly set of if statements accumulating over time as we add more search options, and a growing list of optional search parameters.

There is an even worse alternative: simply accepting a free-form WHERE clause in the CGI parameters, that is,

$sql="SELECT id,title,description,price,colors,sizes"

."FROM garments WHERE ".$sqlWhere;

which we can then call directly from the URL, for example:

garments.php?sqlWhere=CATEGORY="Menswear"

This solution confuses the Model and the View even further, exposing raw SQL in the presentation code. It also opens the door to malicious SQL injection attacks, and, although modern versions of PHP have some built-in defenses against these, it’s foolish to rely on them.

Second, we’ve hardwired the XML data format into the page—it’s been buried in there among the printf and echo statements somewhere. There are several reasons why we might want to change the data format. Maybe we want to show an original price alongside the sale price, to try to persuade some poor sap to buy all those itchy golfing socks that we ordered!

Third, the database result set itself is used to generate the XML. This may look like an efficient way to do things initially, but it has two potential problems. We’re keeping a database connection open all the time that we are generating the XML. In this case, we’re not doing anything very difficult during that while() loop, so the connection won’t be too lengthy, but eventually it may prove to be a bottle-neck. Also, it works only if we treat our database as a flat data structure.

3.4.2 Refactoring the domain model

We’re handling our lists of colors and sizes in a fairly inefficient manner at present, by storing comma-separated lists in fields in the Garments table. If we normalize our data in keeping with a good relational model, we ought to have a

Web server MVC 97

separate table of all available colors, and a bridging table linking garments to col-ors (what the database wonks call a many-to-many relationship). Figure 3.7 illus-trates the use of a many-to-many relationship of this sort.

To determine the available colors for our deerstalker hat, we look up the Garments_to_Colors table on the foreign key garment_id. Relating the color_id column back to the primary key in the Colors table, we can see that the hat is available in shocking pink and blueberry but not battleship gray. By running the query in reverse, we could also use the Garments_to_Colors table to list all gar-ments that match a given color.

We’re making better use of our database now, but the SQL required to fetch all the information begins to get a little hairy. Rather than having to construct elab-orate join queries by hand, it would be nice to be able to treat our garments as objects, containing an array of colors and sizes.

Object-relational Mapping tools

Fortunately, there are tools and libraries that can do that for us, known as Object-Relational Mapping (ORM) tools. An ORM automatically translates between data-base data and in-memory objects, taking the burden of writing raw SQL off the developer. PHP programmers might like to take a look at PEARDB_DataObject, Easy PHP Data Objects (EZPDO), or Metastorage. Java developers are relatively spoiled for choice, with Hibernate (also ported to .NET) currently a popular choice. ORM tools are a big topic, one that we’ll have to put aside for now.

Looking at our application in MVC terms, we can see that adopting an ORM has had a happy side effect, in that we have the beginnings of a genuine Model on

Garments_to_Colors

Figure 3.7 A many-to-many relationship in a database model. The table Colors lists all available colors for all garments, and the table Garments no longer lists any color information.

our hands. We now can write our XML-generator routine to talk to the Garment object and leave the ORM to mess around with the database. We’re no longer bound to a particular database’s API (or its quirks). Listing 3.7 shows the change in our code after switching to an ORM.

In this case, we define the business objects (that is, the Model) for our store example in PHP, using the Pear::DB_DataObject, which requires our classes to extend a base DB_DataObject class. Different ORMs do it differently, but the point is that we’re creating a set of objects that we can talk to like regular code, abstract-ing away the complexities of SQL statements.

require_once "DB/DataObject.php";

class GarmentColor extends DB_DataObject { var $id;

var $garment_id;

var $color_id;

}

class Color extends DB_DataObject { var $id;

var $name;

}

class Garment extends DB_DataObject { var $id;

if (!isset($this->colors)){

$linkObject=new GarmentColor();

$linkObject->garment_id = $this->id;

$linkObject->find();

$colors=array();

while ($linkObject->fetch()){

$colorObject=new Color();

$colorObject->id=$linkObject->color_id;

$colorObject->find();

Listing 3.7 Object model for our garment store

Web server MVC 99

As well as the central Garment object, we’ve defined a Color object and a method of the Garment for fetching all Colors that it is available in. Sizes could be imple-mented similarly but are omitted here for brevity. Because this library doesn’t directly support many-to-many relationships, we need to define an object type for the link table and iterate through these in the getColors() method. Nonetheless, it represents a fairly complete and readable object model. Let’s see how to make use of that model in our page.

Using the revised model

We’ve generated a data model from our cleaner database structure. Now we need to use it inside our PHP script. Listing 3.8 revises our main page to use the ORM -based objects.

<?php

header("Content-type: application/xml");

echo "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";

include "garment_business_objects.inc"

printf("<garment id=\"%s\" title=\"%s\">\n"

."<description>%s</description>\n<price>%s</price>\n", $garment->id,

$garment->title, $garment->description, $garment->price);

$colors=$garment->getColors();

if (count($colors)>0){

echo "<colors>\n";

for($i=0;$i<count($colors);$i++){

echo "<color>{$colors[$i]}</color>\n";

}

echo "</colors>\n";

}

echo "</garment>\n";

}

echo "</garments>\n";

?>

We include the object model definitions and then talk in terms of the object model. Rather than constructing some ad hoc SQL, we create an empty Garment

Listing 3.8 Revised page using ORM to talk to the database

object and partly populate it with our search criteria. Because the object model is included from a separate file, we can reuse it for other searches, too. The XML View is generated against the object model now as well. Our next refactoring step is to separate the format of the XML from the process of generating it.

3.4.3 Separating content from presentation

Our View code is still rather tangled up with the object, inasmuch as the XML for-mat is tied up in the object-parsing code. If we’re maintaining several pages, then we want to be able to change the XML format in only one place and have that apply everywhere. In the more complex case where we want to maintain more than one format, say one for short and detailed listings for display to customers and another for the stock-taking application, then we want to define each format only once and provide a centralized mapping for them.

Template-based systems

One common approach to this is a template language, that is, a system that accepts a text document containing some special markup notation that acts as a placeholder for real variables during execution. PHP, ASP, and JSP are themselves templating languages of sorts, written as web page content with embedded code, rather than the code with embedded content seen in a Java servlet or traditional CGI script. However, they expose the full power of the scripting language to the page, making it easy to tangle up business logic and presentation.

In contrast, purpose-built template languages, such as PHP Smarty and Apache Velocity (a Java-based system, ported to .NET as NVelocity), offer a more limited ability to code, usually limiting control flow to simple branching (for example, if) and looping (for example, for, while) constructs. Listing 3.9 shows a PHP Smarty template for generating our XML.

<?xml version="1.0" encoding="UTF-8" ?>

<garments>

{section name=garment loop=$garments}

<garment id="{$garment.id}" title="{$garment.title}">

<description>{$garment.description}</description>

<price>{$garment.price}</price>

{if count($garment.getColors())>0}

<colors>

{section name=color loop=$garment.getColors()}

<color>$color->name</color>

{/section}

</colors>

Listing 3.9 PHP Smarty template for our XML output

Web server MVC 101

{/if}

</garment>

{/section}

</garments>

The template expects to see an array variable garments, containing Garment objects, as input. Most of the template is emitted from the engine verbatim, but sections inside the curly braces are interpreted as instructions and are either sub-stituted for variable names or treated as simple branch and loop statements. The structure of the output XML document is more clearly readable in the template than when tangled up with the code, as in the body of listing 3.7. Let’s see how to use the template from our page.

Using the revised view

We’ve moved the definition of our XML format out of our main page into the Smarty template. As a result, now the main page needs only to set up the tem-plate engine and pass in the appropriate data. Listing 3.10 shows the changes needed to do this.

<?php

header("Content-type: application/xml");

include "garment_business_objects.inc";

include "smarty.class.php";

$garment=new DataObjects_Garment;

$garment->category = $_GET["cat"];

$number_of_rows = $garment->find();

$smarty=new Smarty;

$smarty->assign('garments',$garments);

$smarty->display('garments_xml.tpl');

?>

Smarty is very concise to use, following a three-stage process. First, we create a Smarty engine. Then, we populate it with variables. In this case, there is only one, but we can add as many as we like—if the user details were stored in session, we could pass them in, for example, to present a personalized greeting through the template. Finally, we call display(), passing in the name of the template file.

We’ve now achieved the happy state of separating out the View from our search results page. The XML format is defined once and can be invoked in a few lines of code. The search results page is tightly focused, containing only the

Listing 3.10 Using Smarty to generate the XML

information that is specific to itself, namely, populating the search parameters and defining an output format. Remember that we dreamed up a requirement earlier to be able to swap in alternative XML formats on the fly? That’s easy with Smarty; we simply define an extra format. It even supports including tem-plates within other temtem-plates if we want to be very structured about creating minor variations.

Looking back to the opening discussion about the Model-View-Controller pat-tern, we can see that we’re now implementing it quite nicely. Figure 3.8 provides a visual summary of where we are.

The Model is our collection of domain objects, persisted to the database auto-matically using our ORM. The View is the template defining the XML format. The Controller is the “search by category” page, and any other pages that we care to define, that glue the Model and the View together.

This is the classic mapping of MVC onto the web application. We’ve worked through it here in the web server tier of an Ajax application that serves XML doc-uments, but it’s easy to see how it could also apply to a classic web application serving HTML pages.

Depending on the technologies you work with, you’ll encounter variations on this pattern, but the principle is the same. J2EE enterprise beans abstract the Model and Controller to the point where they can reside on different servers.

.NET “code-behind” classes delegate the Controller role to page-specific objects, whereas frameworks such as Struts define a “front controller” that intercepts and routes all requests to the application. Frameworks such as Apache Struts have worked this down to a fine art, refining the role of the Controller to route the user between pages, as well as applying at the single-page level. (In an Ajax

.NET “code-behind” classes delegate the Controller role to page-specific objects, whereas frameworks such as Struts define a “front controller” that intercepts and routes all requests to the application. Frameworks such as Apache Struts have worked this down to a fine art, refining the role of the Controller to route the user between pages, as well as applying at the single-page level. (In an Ajax

Dans le document Dave Crane Eric Pascarello (Page 123-133)