Layered Architecture

The shop’s backend does three different jobs. Take placeOrder:

  • Read the request. Get the cart identifier and the idempotency key. Find out which shopper sent it. Build the response.
  • Apply the shop’s rules. The cart belongs to this shopper. It is still open. There is enough stock. Record the order.
  • Talk to the database. Load the cart. Save the order. Update the stock.

You can write all of this code in a single resolver, but doing so mixes the three jobs and makes the code harder to maintain. A common practice is to separate the code into layers. This is called a layered architecture.

Three layers

Layered architecture is a design pattern that organizes an application into horizontal layers, each with a specific responsibility. Each layer provides operations to the layer above it and calls operations in the layer below it.

In our case, we can organize the shop’s backend into three layers:

graph TB
    C[Client] --> A[API layer]
    subgraph "Shop backend"
        A --> B[Business logic layer]
        B --> D[Data access layer]
    end
    D --> DB[(Database)]

The API layer talks to clients. It reads a request, parses the information it contains, and then calls an operation from the next layer, the business logic layer, to fulfill the request. Once it receives the result, it formats it into the response and sends it back to the client. The resolvers in GraphQL or the handlers in REST are part of this layer. If we change from REST to GraphQL, the API layer changes, but the business logic and data access layers do not change.

The business logic layer applies the shop’s rules. For example, the logic for placing an order is in this layer, as one function. In the REST API, the handler for POST /orders calls this function. In the GraphQL API, the placeOrder resolver calls it. This keeps the business rules separate from the API implementation and the database operations. If we change from REST to GraphQL, the business logic remains unchanged. If we switch to a different database, the business logic also remains unchanged. A layer with a single responsibility has one reason to change. For this layer, that reason is a change in the business rules.

The data access layer reads from and writes to the database. It knows how a cart’s items are stored and how an order is saved. This means that if we use a relational database, it knows how to query the tables for carts and orders. If we change the database (while preserving those operations and their guarantees), only this layer needs to be updated. Changing the API or the business logic does not necessarily affect this layer.

A request from the client travels down the layers, from the API layer to the business logic layer to the data access layer. The response travels back up the layers to the client.

Benefits and costs

Each layer can be understood and tested on its own. A change in one layer is mostly contained in that layer, and if not, it usually affects only the layers next to it. We use three layers. A different application may have more, depending on its complexity.

There are costs. There are more files and more indirection, so a request is harder to trace. Each layer adds a little processing, as arguments are passed down and results are passed back up. In a small or simple application, much of the code does nothing but pass data between layers. This is called the architecture sinkhole anti-pattern: the layers only pass data along and add little value.