© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
V. GagliardiDecoupled Django https://doi.org/10.1007/978-1-4842-7144-5_1

1. Introduction to the Decoupled World

Valentino Gagliardi1  
(1)
Colle di Val D’Elsa, Italy
 
This chapter offers a brief introduction to:
  • Monoliths and decoupled architectures

  • REST architectures

  • The GraphQL query language

In this chapter, we review traditional web applications, the classic MVC pattern based on views, models, and controllers.

We begin to outline use cases, benefits, and drawbacks of decoupled architectures. We explore the foundations of REST, look at how it compares to GraphQL, and learn that REST APIs are not only RESTful after all.

Monoliths and MVC

For at least two decades, traditional websites and applications all shared a common design based on the Model-View-Controller pattern, abbreviated MVC.

This pattern wasn’t built in a day. In the beginning, there was an intertwined mess of business logic, HTML, and what once was a pale imitation of the JavaScript we know today. In a typical MVC arrangement, when a user requests a path to a website, the application responds with some HTML. Behind the scenes, a controller, usually a function or a method, takes care to return the appropriate view to the user. This happens after the controller populates the view with data coming from the database layer, most of the time, through an ORM (object-relational mapping ). Such a system, acting as a whole to serve the user, with all its components living in a single place, is called monolith. In a monolithic web application, the HTML response is generated right before returning the page to the user, a process known as traditional server-side rendering . Figure 1-1 shows a representation of MVC.
../images/505838_1_En_1_Chapter/505838_1_En_1_Fig1_HTML.png
Figure 1-1

An MVC application responds to the user with a view, generated by a controller. The model layer supplies the data from the database

MVC has variations, like the Model-View-Template pattern employed by Django. In Django’s MVT, the data comes still from the database, but the view acts like a controller: it gets data from the database through the ORM and injects the result in a template, which is returned to the user. MVC and its variations are well and alive: all the most popular web frameworks like .NET core, Rails, Laravel, and Django itself employ this pattern with success. However, in recent times we are seeing the spread of decoupled applications based on a service-oriented architecture.

In this design, a RESTful or a GraphQL API exposes data for one or more JavaScript frontends, for a mobile application, or for another machine. Service-oriented and decoupled architectures are a broader category that encompasses the galaxy of microservices systems. Throughout the book, we refer to decoupled architectures in the context of web applications, mainly as systems with a REST API, or GraphQL on the backend, and a separated JavaScript/HTML frontend. Before focusing on REST APIs, let’s first unpack what’s behind a decoupled architecture.

What Makes a Decoupled Architecture?

A decoupled architecture is a system that abides to one of the most important rules in software engineering: separation of concerns.

In a decoupled architecture, there is a clear separation between the client and the server. This is also one of the most important constraints required by REST. Figure 1-2 shows an overview of such a system, with a backend and a frontend.
../images/505838_1_En_1_Chapter/505838_1_En_1_Fig2_HTML.png
Figure 1-2

A decoupled application with a REST API as a data source for a JavaScript/HTML frontend

As you will see later in the book, this separation between client and server, views and controllers, is not always strict, and depending on the decoupling style, the distinction becomes blurry. For example, we can have the REST API and the frontend living in two completely different environments (separate domains or different origins). In this case, the division is crystal clear. In some situations, when a full JavaScript frontend would not make sense, Django can still expose a REST or GraphQL API, with JavaScript embedded in a Django template talking to the endpoints.

To muddle things further, frameworks like Angular adopt the Model-View-Controller pattern even for structuring frontend code. In a single-page application, we can find the same MVC design, which replicates the backend structure. You can already guess that one of the disadvantages of a purely decoupled architecture is, to some extent, code duplication. Having defined what is a decoupled architecture, let’s now talk about its use cases.

Why and When to Decouple?

This isn’t a book about the JavaScript gold rush. In fact, you should weigh your options long before thinking about a full rewrite of your beloved Django monolith.

Not every project needs a single-page application. If instead your application falls under one of the following categories, you can start evaluating the advantages of a decoupled architecture. Here’s a list of the most common use cases:
  • Machine-to-machine communication

  • Interactive dashboards with heavy JS-driven interactions

  • Static site generation

  • Mobile applications

With Django, you can build all sorts of things involving machine-to-machine communication. Think of an industrial application to collect data from sensors that can be later aggregated in all sorts of data reporting. Such dashboards can have a lot of JS-driven interactions. Another interesting application for decoupled architectures are content repositories . Monoliths like Django, CMS like Drupal, or blogging platforms like WordPress are good companions for static site generators. We explore this topic in detail later.

Another benefit of a decoupled architecture is the ability to serve different types of clients: mobile applications are one of the most compelling use cases. Now, if decoupled architectures sound too appealing, I advise you to consider their drawbacks. Decoupled architectures based exclusively on single-page applications are not always a valid choice for:
  • Constrained teams

  • Websites with little or no JS-driven interactions

  • Constrained devices

  • Content-heavy websites with search engine optimization in mind

Note

As you will see in Chapter 7, frameworks like Next.js can help with search engine optimization for single-page apps by producing static HTML. Other examples of frameworks employing this technique are Gatsby and Prerenderer.

It’s easy to get overwhelmed by modern frontend development, especially if the team is small. One of the most serious hindrances when designing or building a decoupled architecture from scratch is the sheer amount of complexity lurking behind the surface of JavaScript tooling. In the next sections, we focus on REST and GraphQL, the two pillars of a decoupled architecture.

Hypermedia All the Things

The foundation for almost any decoupled frontend architecture is the REST architectural style .

REST is hardly a novel concept these days. The theory is that through verbs or commands, we create, retrieve, or modify resources on a system. For example, given a User model on the backend, exposed by a REST API as a resource, we can get a collection of all the instances present in the database with a GET HTTP request. The following shows a typical GET request to retrieve a list of entities:
GET https://api.example/api/users/
As you can see, we say users, not user, when retrieving the resource. As a convention, resources should always be plural. To retrieve a single resource from the API, we pass instead the ID in the path, as a path parameter . The following shows a GET request to a single resource:
GET https://api.example/api/users/1
Table 1-1 shows a breakdown of all the verbs (HTTP methods) and their effect on the resources.
Table 1-1

HTTP Methods with the Corresponding Effect on a Given Resource Present on the Backend

Method

Effect

Idempotent

POST

Create resource

No

GET

Retrieve resource(s)

Yes

PUT

Update resource

Yes

DELETE

Delete resource

Yes

PATCH

Partial update resource

No

To refer to this set of HTTP methods we also use the term CRUD, which stands for Create, Read, Update, and Delete. As you can see from the table, some HTTP verbs are idempotent, meaning that the result of the operation is always stable. A GET request for example always returns the same data, no matter how many times we issue the command after the first request.

A POST request instead will always induce a side effect, that is, create a new resource on the backend with different values for each call. When retrieving a resource with GET, we can use search parameters in a query string to specify search constraints, sorting, or to limit the number of results. The following shows a request for a limited set of users:
GET https://api.example/api/users?limit=20

When creating a new resource with POST instead, we can send a request body alongside the request. Depending on the operation type, the API can respond with an HTTP status code, and with the newly created object. Common examples of HTTP response code are 200 OK and 201 Created, 202 Accepted. When things don’t go well, the API might respond with an error code. Common examples of HTTP error codes are 500 Internal Server Error, 403 Forbidden, and 401 Unauthorized.

This back and forth communication between the client and the server carries JSON objects over the HTTP protocol. Nowadays, JSON is the preferred format for exchanging data, whereas in the past you could have seen XML over HTTP (SOAP architectures are still alive these days). Why does REST follow these conventions, and why does it use HTTP? When Roy Fielding wrote his dissertation entitled, “Architectural Styles and the Design of Network-based Software Architectures” in 2000, he defined the following rules:
  • Hypermedia as the engine: When requesting a resource, the response from the APIs must also include hyperlinks to related entities or to other actions.

  • Client-server separation: The consumer (JavaScript, a machine, or a generic client) and the Web API must be two separate entities.

  • Stateless: The communication between client and server should not use any data stored on the server.

  • Cacheable: The API should leverage HTTP caching as much as possible.

  • Uniform interface: The communication between client and server should use a representation of the resources involved, and a standard language for the communication.

It’s worth taking a quick detour to dive deeper into each of these rules.

Hypermedia as the Engine

In the original dissertation, this constraint is buried under the Uniform Interface section, but it’s crucial for understanding the real nature of REST APIs.

What hypermedia as the engine means in practice is that when communicating with an API, we should be able to see what’s next by examining any link in the response. Django REST framework, the most popular framework for building REST APIs in Django, makes it easy to build Hypermedia APIs. In fact, Django REST framework serializers have the ability to return hyperlinked resources. For example, a query for a List model can return the many side of a one-to-many relationship. Listing 1-1 illustrates a JSON response from an API where the Card model is connected with a foreign key to List.
{
  "id": 8,
  "title": "Doing",
  "cards": [
      "https://api.example/api/cards/1",
      "https://api.example/api/cards/2",
      "https://api.example/api/cards/3",
      "https://api.example/api/cards/4"
  ]
}
Listing 1-1

A JSON Response with Hyperlinked Relationships

Other examples of hyperlinked resources are pagination links. Listing 1-2 is a JSON response for a boards resource (Board model) with hyperlinks for navigating between results.
{
  "id": 4,
  "title": "Doing",
  "next": "https://api.example/api/boards/?page=5",
  "previous": "https://api.example/api/boards/?page=3"
}
Listing 1-2

A JSON Response with Pagination Links

Another interesting feature of the Django REST framework is the browsable API, a web interface for interacting with the REST API. All these features make Django REST framework Hypermedia APIs ready, which is the correct definition for these systems.

Client-Server Separation

The second constraint, client-server separation, is easily achievable.

A REST API can expose endpoints to which consumers can connect to retrieve, update, or delete data. In our case, consumers will be JavaScript frontends.

Stateless

A compliant REST API should be stateless.

Stateless means that during the communication between client and server, the request should not use any context data stored on the server. This doesn’t mean that we can’t interact with the resources exposed by the REST APIs. The constraint applies to session data, like session cookies or other means of identification stored on the server. This strict prescription urged engineers to find new solutions for API authentication. JSON Web Token, referred to as JWT later in the book, is a product of such research, which is not necessarily more secure than other methods, as you will see later.

Cacheable

A compliant REST API should take advantage of HTTP caching as much as possible.

HTTP caching operates through HTTP headers. A well-designed REST API should always give the client hints about the lifetime of a GET response. To do so, the backend sets a Cache-Control header on the response with a max-age directive, which drives the lifespan of the response. For example, to cache a response for one hour, the server can set the following header:
Cache-Control: max-age=3600
Most of the time, there is also an ETag header in the response, which indicates the resource version. Listing 1-3 shows a typical HTTP response with cache headers.
200 OK
Cache-Control: max-age=3600
ETag: "x6ty2xv"
Listing 1-3

An HTTP Response with Cache Headers

Note

Another method for enabling HTTP caching involves the Last-Modified header. If the server sets this header, the client can in turn use If-Modified-Since or If-Unmodified-Since to check the resource’s freshness.

When the client requests the same resource and max-age is not yet expired, the response is fetched from the browser’s cache, not from the server. If max-age has expired, the client issues a request to the server by attaching the If-None-Match header, alongside with the value from the ETag. This mechanism is known as a conditional request . If the resource is still fresh, the server responds with 304 Not Modified, hence avoiding unnecessary exchange of data. If the resource instead is stale, that is, it’s expired, the server responds with a fresh response. It’s important to remember that browsers cache only the following response codes:
  • 200 OK

  • 301 Moved Permanently

  • 404 Not Found

  • 206 Partial Content

Moreover, responses with the Authorization header set aren’t cached by default, unless the Cache-Control header includes the public directive. Also, as you will see later, GraphQL operates mainly with POST requests, which aren’t cached by default.

Uniform Interface

Uniform interface is one of the most important rules of REST.

One of its tenets, representations, prescribes that the communication between client and server, for example to create a new resource on the backend, should carry the representation of the resource itself. What this means is that if I want to create a new resource on the backend, and I issue a POST request, I should provide a payload with the resource.

Let’s say I have an API that accepts a command on an endpoint, but without a request body. A REST API that creates a new resource based only on a command issued against an endpoint is not RESTful. If we talk in terms of uniform interface and representations instead, when we want to create a new resource on the server, we send the resource itself in the request body. Listing 1-4 illustrates a complaint request, with a request body for creating a new user.
POST https://api.example/api/users/
{
  "name": "Juliana",
  "surname": "Crain",
  "age": 44
}
Listing 1-4

A POST Request

Here we use JSON as the media type, and a representation of the resource as the request body. Uniform interface refers also to the HTTP verbs used to drive the communication from the client to the server. When we talk to a REST API, we mainly use five methods: GET, POST, PUT, DELETE, and PATCH. These methods are also the uniform interface, that is, the common language we use for client-server communication. After reviewing REST principles, let’s now turn our attention to its alleged contender, GraphQL.

An Introduction to GraphQL

GraphQL appeared in 2015, proposed by Facebook, and marketed as a replacement for REST.

GraphQL is a data query language that allows the client to precisely define what data to fetch from the server and combine data from multiple resources in one request. In a sense, this is what we always did with REST APIs, but GraphQL takes this a step further, pushing more control to the client. We saw how to request data from a REST API. To get a single user, for example, we can visit the following URL of a fictional REST API:
https://api.example/api/users/4
In response, the API returns all the fields for the given user. Listing 1-5 shows a JSON response for a single user, which also happens to have a one-to-many relationship with a Friend model.
{
  "id": 4,
  "name": "Juliana",
  "surname": "Crain",
  "age": 44,
  "city": "London",
  "occupation": "Software developer",
  "friends": [
      "https://api.example/api/friend/1",
      "https://api.example/api/friend/2",
      "https://api.example/api/friend/3",
      "https://api.example/api/friend/4"
  ]
}
Listing 1-5

A JSON Response from a REST API

This is a contrived example, but if you imagine an even larger set of fields in the response, it becomes clear that we are over-fetching , that is, we are requesting more data than we need. If we think for a moment of the same API, this time implemented with GraphQL, we can request a smaller subset of fields. To request data from a GraphQL API, we can make a query. Listing 1-6 shows a typical GraphQL query to request a single user, by ID, with only a subset of fields.
query {
    getUser(userID: 4) {
        surname,
        age
    }
}
Listing 1-6

A GraphQL Query

As you can see, the client controls what fields it can request. Here, for example, we skipped all fields except surname and age. This query has also an argument identified by userID, which acts as a first filter for the query. In response to this query, the GraphQL API returns the requested fields. Listing 1-7 shows the JSON response for our query.
{
  "surname": "Crain",
  "age": 44
}
Listing 1-7

A JSON Response from the Previous Query

“No more over-fetching” is one of the main selling points for GraphQL over REST. In reality, this filtering capability based on fields is not an exclusive of GraphQL APIs. For example, REST APIs that follow the JSON API specification can use sparse fieldsets to request only a subset of data. Once we issue a query against a GraphQL endpoint, this query travels over a POST request as a request body. Listing 1-8 shows a request to a GraphQL API.
POST https://api.example/graphql
{
"query" : "query { getUser(userID: 4) { surname, age } }",
"variables": null
}
Listing 1-8

A GraphQL Query over a POST Request

You can already notice in this request that we call the /graphql endpoint instead of /api/users/4. Also, we use POST instead of GET to retrieve the resource. This is a large departure from the REST architectural style. Query requests in GraphQL are only half of the story. Whereas REST uses POST, PUT, and DELETE to create, update, or remove a resource, respectively, GraphQL has the concept of mutations as a means of altering the data. Listing 1-9 shows a mutation for creating a new a user.
mutation {
   createUser(name: "Caty", surname: "Jonson") {
       name,
       surname
   }
}
Listing 1-9

A GraphQL Mutation

Subscriptions are another interesting feature of GraphQL services. Clients can subscribe to events. For example, we may want to receive a notification from the server any time a new user registers to our service. In GraphQL, we register a subscription for this. Listing 1-10 illustrates a subscription.
subscription {
   userRegistered {
       name,
       email
   }
}
Listing 1-10

A GraphQL Subscription

What happens when a GraphQL query reaches the backend? How does the flow compare to a REST API? Once the query lands on the backend, it is validated against a schema, which contains type definitions . Then one or more resolvers, dedicated functions connected to each field in the schema, assemble and return the appropriate data for the user. Speaking of type definitions, everything is a type in GraphQL: queries, mutations, subscriptions, and the domain entities. Each query, mutation, and entity has to be defined in a schema before it can be used, written in a Schema Definition Language. Listing 1-11 shows a simple schema for the queries we have seen so far.
type User {
      name: String,
      surname: String,
      age: Int,
      email: String
}
type Query {
      getUser(userID: ID): User!
}
type Mutation {
      createUser(name: String, surname: String): User
}
type Subscription {
      userRegistered: User
}
Listing 1-11

A Simple GraphQL Schema

By looking at this schema, you can immediately notice how GraphQL enforces strong types, much like a typed language like TypeScript or C#. Here, String, Int, and ID are scalar types, while User is our custom type. These custom types go under the definition of object types in GraphQL parlance. How does GraphQL fit in the Python ecosystem? Nowadays, there are a number of libraries for building Pythonesque GraphQL APIs. The most popular are as follows:
  • Graphene, with its code-first approach to building GraphQL services

  • Ariadne, a schema-first GraphQL library

  • Strawberry, built on top of data classes, code-first, and with type hints

All these libraries have integrations with Django. The difference between a code-first approach and a schema-first approach to GraphQL is that the former promotes Python syntax as a first-class citizen for writing the schema. The latter instead uses a multi-line Python string to represent it. In Chapters 10 and 11, we work extensively with GraphQL in Django with Ariadne and Strawberry.

Summary

This chapter reviewed the fundamentals for both traditional and decoupled architectures. You learned that:
  • Monoliths are systems acting as a whole unit to serve HTML and data to the users

  • REST APIs are in reality Hypermedia APIs because they use HTTP as the communication medium, and hyperlinks for providing paths to related resources

  • JavaScript-first and single-page apps are not the perfect solution to every use case

  • GraphQL is a strong contender for REST

In the next chapter, we dive deep into the JavaScript ecosystem to see how it fits within Django.

Additional Resources

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.144.32