§6.3.

Layering in web applications

Layering was the topic of Chapter 3. I described the design of the internet as a series of stacked layers.

As a developer of internet applications, you can also use layering as an architectural strategy in your code.

In this chapter, I will explore how to introduce layering into new projects.

Three layers

There is no ‘correct’ architecture for internet applications. Different situations will require different architectures. Some architectures are surprisingly versatile: they appear in response to widely varied design problems. By studying these architectures, we benefit from the wisdom and hard-learned lessons of experts.

One of the most versatile and well-known architectures is the three-layer architecture. It is used extensively in internet development as well as enterprise software development.

The three-layer architecture divides a system into three layers: [1]

1

Presentation
(user interface)

2

Domain
(core application/business logic)

3

Persistence
(data storage and retrieval)

Presentation logic

The presentation logic is responsible for formatting and displaying data for user interaction. The name presentation logic refers to its responsibility for presenting information to the user.

In a web application, the presentation logic might include:

  • Code that generates HTML

  • Code that decodes submitted HTML form data

  • Styles defined in CSS

  • JavaScript code to show alerts or animations

Presentation logic can encompass multiple user-interfaces. The presentation logic might also include:

  • A native client to install on Windows, Mac or Linux

  • Mobile applications for installation on iPhone or Android

  • Command-line clients for use by developers or in scripts

  • Public APIs that allow other developers to integrate with your system

Domain logic

The presentation layer depends on the domain logic to perform the core functionality of an application.

The domain logic is responsible for handling core “business” services. The word domain in domain logic means ‘an area of activity’ (i.e., the problem domain or the business domain).

In a typical web application, the domain logic might perform the following services:

  • Managing the contents of a shopping cart

  • Calculating totals and sales tax payable

  • Processing a payment

  • Determining which warehouse should handle an order

  • Calculating the number of “likes” on a post

  • Ensuring that private profiles stay private and that anonymous users can only retrieve public profiles

  • Detecting spelling errors in a submission

Note that layering means that the domain logic should be unchanged when the presentation logic changes. For example, Amazon.com’s formula to calculate sales tax does not depend on whether a user orders a book through their browser, in the Amazon mobile app, or from their Kindle.

Persistence

The domain logic depends on the persistence logic to store and retrieve data.

Persistence logic is responsible for managing the underlying state of an application. The name persistence refers to saving information so that it is not lost when a computer restarts (i.e., the data is persistent).

Most commonly, the persistence layer is responsible for communicating with the database. In a typical web application, the persistence layer is responsible for the following services:

  • Creating, reading, updating and deleting database records

  • Establishing a database connection

  • Reconnecting to the database when a connection is lost

  • Translating between the format of database records and higher-level objects used in the application

  • Communicating with other low-level data storage systems (messaging systems, transaction managers, legacy systems)

Why layer?

Whether or not to use these three layers is up to you as a developer and software designer. There is no legal or technological requirement to use these layers. However, the three-layered architecture is a proven strategy for structuring web applications.

By structuring your code around these three layers, you will help isolate parts of your application that evolve at different speeds:

  1. The presentation layer separates your domain logic from the rapid changes in end-user interface technology. It protects the domain logic from rewrites when, for example, switching from React to Angular, adding a public API, creating a native iOS/Android app, or updating the graphic-design.

  2. The domain logic tends to evolve slowly, with business requirements. When domain logic is in a separate layer, business changes are isolated from the technology changes that occur in presentation and persistence logic.

  3. The persistence logic helps protect the domain logic from changes when the application scales and grows. It is easier to switch databases (e.g., from a MySQL database to a PostgreSQL database) or deploy a sophisticated database cluster when persistence is separate from the domain logic.

Adding additional layers

The three layers are widely understood. However, in practical situations, developers may choose to add additional layers as implementation details.

For example, if you are building a web application with React or Angular, you might insert two additional layers to manage the communication between your browser and the server. The result is a five-layer architecture:

Client-side / browser layers

1

Presentation
(user interface)

2

View model / API client
(stores client-side state, and communicates with the API)

Server-side layers

3

API
(the server-side interface to the domain logic)

4

Domain
(core application/business logic)

5

Persistence
(data storage and retrieval)

In this five-layer architecture, the presentation layer uses a local view model that runs in the browser. The view model encapsulates all the details about communicating with the server, such as using HTTP and setting the correct authentication cookies.

On the server-side, the API serves as the public interface to the domain logic. The API is responsible for translating incoming requests (e.g., sent in JSON format) into function calls in the underlying domain logic.

Other refinements of the three-layer architecture are possible. For example, Martin Fowler [2] divides the domain logic into service, domain object and data access layers, which respectively implement units of work, individual entities and the translation of entities into database storage.

Using layers

Software architecture is not a fixed set of rules. There are many ways to implement the design principle.

To get started with the three-layered architecture, identify the parts of your code that correspond with each layer. Once you have identified those layers, you can restructure the project, to ensure each layer’s code appears together but separate from other layers.

The specific technical structure for separating layers can vary. Some possibilities are as follows:

  1. Each layer in a separate function

  2. Each layer in a separate file

  3. Each layer in a separate directory

You may also choose to identify the layers by name. If you like the formal names, you could name these layers presentation, domain, persistence. If you prefer shorter names, you could name the layers like ui, api and db [3] (or fe, be and db [4]). The names are up to you.

As you continue to develop your application, these layers will evolve. Sometimes you may even decide to shift some logic between layers. For example, the currency symbol ($) might be hard-coded into the presentation logic on a simple web store. However, as the store grows to serve an international audience, this logic may be moved to the domain logic to support sales in foreign currencies.

Exercise: Identify layers

Examine the source for the shopping list again and identify which parts of the code might belong to which layers.

Attempt to answer this yourself, before advancing to the next section.

const express = require('express');
const app = express();
const port = 3000;

// A list of items in the shopping list
// Each item is an object: { description: string, quantity: number }
let items = [];

app.get('/', (req, res) => {
    // Prepare the header
    let result =
        `<!DOCTYPE html><title>AIP shopping list</title>
         <h1>AIP shopping list</h1>
         <h3>Current items:</h3>`;

    // Generate the items in the shopping list and a total
    // or a placeholder if nothing has been added.
    if (items.length > 0) {
        let total = 0;
        result += `<ul>`;
        for (let item of items) {
            result += `<li>${item.quantity} units of ${item.description}</li>`;
            total += item.quantity;
        }
        result += `</ul>`;
        result += `<p>Total quantity of units: ${total}.</p>`;
    } else {
        result += `<p>No items have been added.</p>`
    }

    // Prepare the form to add new items to the shopping list
    result +=
        `<h3>Add item:</h3>
         <form action="/new" method="GET">
         <p>
            <label>Description:
                <input type="text" name="description">
            </label>
         </p>
         <p>
            <label>Quantity:
                <input type="number" name="quantity">
            </label>
         </p>
         <p>
            <input type="submit" value="Add">
         </p>
         </form>`;

    // Send the result to the client
    res.send(result);
});

app.get('/new', (req, res) => {
    // Get the user input
    const description = req.query['description'];
    const quantity = parseInt(req.query['quantity']);

    // Add to the shopping list
    items.push({ description, quantity });

    // Redirect back to the home page to show the full list
    res.redirect('/');
});

app.listen(port, () => console.log(`The shopping list is running on http://localhost:${port}/`));

1. These names were popularized in Fowler (2003) Patterns of enterprise application architecture and Amsden, Berg, Brown, Craig, Hester, Jakab, Pitt, Stinehour, Weitzel (2003) Enterprise Java Programming with IBM WebSphere
2. Fowler (2003) Patterns of enterprise application architecture.
3. UI = user interface. API = application programming interface. DB = database.
4. FE = front-end. BE = back-end. DB = database.