Designing REST Applications

One reason web applications are popular is their ability to scale the load and distribute responsibilities among system components. Client-server architecture is used to manage the complexity of web applications, allowing not only to distribute responsibilities but also to significantly complicate the server side by making it multi-layered with many intermediate components.

Since the bulk of a web application is not the business logic but the infrastructure, the task arises of managing the correct transfer of the business logic state. The REST architectural style was developed to take full advantage of distributed systems, and its adaptation to web applications became known as RESTful. When we talk about REST applications, we usually mean RESTful applications.

In this stream, we will consider the main architectural aspects related to building such applications. The proposed principles and rules will allow you to build and expand web applications without violating REST principles and will help you gain the full benefits of such systems.

REST is not a specific pattern but an architectural style, which means that a wide variety of different solutions can meet REST requirements. This is enough to receive all the benefits of using this architectural style.

The following are the constraints:

  • Client-server model – the principle of a black box for both the client and server.
  • Statelessness – each request is self-sufficient.
  • Caching – resource identification must allow caching of the result when necessary.
  • Uniform interface:
    • Resource identification;
    • Manipulating resources through representations;
    • "Self-descriptive" messages;
    • Hypermedia is a way of changing the application state.
  • Layers – separation of abstractions and responsibilities into layers – network systems are multi-layered, meaning the server can be a proxy to other servers.
  • Code on demand – client functionality can be extended through code provided by the server.

All resources are structures:

  • A URN (Uniform Resource Name) is expanded into a data representation.
  • A resource with an "entity" type is indivisible.
  • {...} – structure indicates "entity" type.
  • [...] – structure indicates "collection" type.
  • (a,b) – structure indicates "relationship" type.
  • A forward slash / denotes a binary relation between structures of different types.
  • The term "relationship" has a special meaning and must always be in the service subset "relationships."
  • Identifiers can be composite, and the result is obtained by folding (left to right) and applying the rule of expansion into a representation.


Resource Identification:

  • The identifier does not reflect the resource storage structure.
  • Identifiers do not need to be composite: composite: /users/:id/orders/:id, non-composite: /user_orders/:user_id.
  • The identifier does not define the resource representation – /users/:id/orders and /books/:id/orders may contain the same orders, but we don't know if they were obtained via reference or by copying the value.
  • The URL must support simple extension rules.

Resources are structures filled with data, and the amount of data is exhaustive for the described resource. The structure is stored in full but can be displayed in different ways. The user receives the resource's representation, not the resource itself.

The developer defines the resource representation:

  • Variants of representation:
    • An unordered global set mapped to an ID.
    • A set of sets that are in a "relationship."
    • A set of nested sets in a "contains" relationship.
    • A combination of all previous methods.
    • Any other method.

Idempotence of Requests: REST applications are not required to be based on the HTTP protocol, but practical implementations of REST are realized in web applications running on HTTP. Therefore, in practice, REST is represented as RESTful.

HTTP requests can be mapped one-to-one to functions:

  • GET /users/:id => get_users_id(id)
  • GET /users/:id => get(users_id)
  • GET /users/:id?status=active => get_user_id(id, status='active')

In the proposed REST implementation, it is possible to combine several actions in one request:

GET(/users/:id/posts/2022)
POST(/users/:id/posts, []) = 2022 # updates the alias, but previous collections remain at their URN PUT(2022, GET(users/:id/posts, params: {filter: {year=2022}})) GET(/users/:id/posts/2022) # returns the result, meaning it's a representation and the expansion rule must apply

However, we can simplify unnecessary actions and get REST that works within the proposed constraints:

GET(/users/:id/posts/2022) => GET(users/:id/posts, params: {filter: {year=2022}})

A temporary collection that can be obtained by filtering.

  • Business Errors
  • Infrastructure Errors

Comments

Popular posts from this blog

Books Every Developer Should Read (In My Opinion)

TypeScript: Why It's Needed and Why It's So Popular

Reactive Architecture at the Code Level