2.3. Software Interface Design
Separating the layers of our software means a little additional work designing the interfaces between these layers. Where we previously had one big lump of code, we'll now have three distinct lumps (business logic, interaction logic, and markup), each of which need to talk to the next. But have we really added any work for ourselves? The answer is probably not: we were already doing this when we had a single code layer, only the task of logic talking to markup wasn't an explicit segment, but rather intermeshed within the rest of the code.
Why bother separating the layers at all? The previous application style of sticking everything together worked, at least insome regards. However, there are several compelling reasons for layered separation, each of which becomes more important asyour application grows in size. Separation of layers allows different engineers or engineering teams to work on different layers simultaneously without stepping on each other's toes. In addition to having physically separate files to work with, the teams don't need an intimate knowledge of the layers outside of their own. People working on markup don't need to understand how the data is sucked out of the data store and presented to the templating system, but only how to use that data once it's been presented to them. Similarly, an engineer working on interaction logic doesn't need to understand the application logic behind getting and setting a piece of data, only the function calls he needs to perform the task. In each of these cases, the only elements the engineers need concern themselves with are the contents of their own layer, and the interfaces to the layers above and below.
What are the interfaces of which we speak? When we talk about interfaces between software layers, we don't mean interfaces in the Java object-oriented sense. An interface in this case describes the set of features allowing one layer to exchange requests and responses with another. For the data and application logic layers, the interface would include storing and fetching raw data. For the interaction logic and application logic layers, they include modifying a particular kind of resourcethe interface only defines how one layer asks another to perform a task, not how that task is performed.
The top layers of our application stack are the odd ones out because the interface between markup and presentation is already well defined by our technologies. The markup links in a stylesheet using a link tag or an @import statement, and then request particular rules through class and id attributes and by using tag sequences named in the sheets. To maintain good separation, we have to avoid using style attributes directly in our markup. While this book doesn't cover frontend engineering, the reasons for separation apply just as well to these layers as their lower neighborsseparating style and markup allows different teams to work on different aspects of the project independently, and allows layers to change internally without affecting the adjoining layers.
Our interaction logic layer typically communicates with our markup layer through a templating system. With our PHP and Smarty reference implementation, Smarty provides some functions to the PHP interaction logic layer. We can call Smarty methods to export data into the templates (which makes it available for output), export presentational functions for execution within the template, and render the templates themselves. In some cases we don't want to take the template output and send it to the end user. In the case where we're sending email, we can create a template for it in our templating system, export theneeded data and functions, render the template into a variable in our interaction logic layer, and send the email. In this way, it's worth noting that data and control don't only flow in one direction. Control can pass between all layers in both directions.
The interface between our two logic layers, depending on implementation, is probably the easiest to understand and the trickiest to keep well designed. If you're using the same language for both layers, the interface can simply be a set of functions. If this is the case, then the interface design will consist of determining a naming scheme (for the functions), a calling scheme (for loading the correct libraries to make those functions available), and a data scheme (for passing data back and forth). All of these schemes fall under the general design of the logical model for your application logic layer. I like to picture the choices as a continuous spectrum called the Web Applications Scale of Stupidity:
<---------- sanity ----------> OOP
The spectrum runs from One Giant Function on the left through to Object-Oriented Programming on the right. Old, monolithic Perl applications live on the very left, while Zope and Plone take their thrones to the right. The more interesting models live somewhere along the line, with the MVC crowd falling within the center third. Frameworks such as Struts and Rails live on the right of this zone, close to Zope (as a reminder of what can happen). Flickr lives a little left of center, with an MVC-like approach but without a framework, and is gradually moving right as time goes on and the application becomes more complicated.
Where you choose to work along this scale is largely a matter of taste. As you move further right, you gain maintainabilityat the expense of flexibility. As you move left, you lose the maintainability but gain flexibility. As you move too far outto either edge, optimizing your application becomes harder, while architecture becomes easier. The trend is definitely moving away from the left, and mid-right frameworks are gaining popularity, but it's always worth remembering that while you gain something moving in one direction, you always lose something else.
The layers at the very bottom of our stack tend to have a well-defined interface because they are physically separaterarely would you want to implement your own database in PHP. The interface and separation then occurs within the application code itself, in the form of abstraction layers. Your business logic doesn't need to know how to physically connect to your database clusters, but the code that establishes the connection does. A separate database and storage layer would take commands from the application logic layer, connect to the data store, perform the commands, and return the result. As an example, our business logic layer might know it wants to execute some SQL and what cluster it needs to be run on, so it can make the following call:
$result = db_query('my_cluster', 'SELECT * FROM Frobs;')
It's then the responsibility of the storage layer and its associated code to connect to the correct server, execute the command, and return the result. The server the query gets executed on can change (as hardware is swapped), be duplicated (perhaps for redundant failover), be logged and benchmarked (see the MySQL section in Chapter 8), or anything else we decide to do. The interface in this example is the db_query( ) function and its friends. For a file storage layer, the interface might consist of a store_file( ) function that gets passed a filename and performs its magic. The magic it performs doesn't matter to the application logic layer, so long as we can make the call we want and get the results we need.
Interface design plays a central part in the architecture of web applications. Interfaces will change as time goes on; teams working on the various layers have to communicate as the interfaces change, but those changes should comprise a small part of ongoing development. The more we can work within a layer without breaking other things unexpectedly, the more productive and flexible we can be.