When REST is not Enough

Mario Bittencourt
SSENSE-TECH
Published in
7 min readNov 26, 2021

Software development and a service-oriented approach are now almost inseparable as the complexity of the systems increases over time. The API-centric approach, using REST, grew in popularity almost becoming ubiquitous.

While it certainly has its benefits and recommended use cases, I see many teams overly focusing on it, as if it was the only way to implement APIs. In this article, I will explore what I consider to be a limitation of REST, why for certain cases you should look for a more powerful style, and how we try to strike a perfect balance at SSENSE.

REST

Representational State Transfer (REST) is an architectural style for network-based applications that aims to provide a low-entry-barrier set of requirements for Internet-scale usage. American Computer Scientist Roy Fielding presented the concept in 2000, introducing a lighter way to develop client and server, that encapsulates entities in resources, abstracting the implementation details from the client.

Figure 1. Relationship between elements according to REST.

The clients access resources via URIs and the server sends back a representation of the resource. This representation is sent in a hypertext format allowing the client to further interact with the resource, if necessary.

REST also establishes a set of constraints that you should follow:

  • Client-server architecture
  • Statelessness
  • Cacheability
  • Layered system
  • Uniform interface
  • Code on demand

RESTful APIs take the REST style and bring it to the world of web service APIs. This means we use HTTP as the protocol and establish the use of its methods as “verbs” for your application:

  • GET: This allows you to retrieve an entity (or list of entities)
  • POST: This allows you to create an entity
  • PUT/PATCH: This allows you to update an entity, either by replacing it altogether (PUT) or parts of it (PATCH)
  • DELETE: This allows you to delete an entity

As you can see above, they map nicely to the 4 CRUD operations of any persistent storage.

If you wanted to manipulate a Product you could see it being exposed by the set of API calls:

POST /products: create a new product

GET /products: get all products

GET /products/{id}: get one product

PUT /products/{id}: update one product

DELETE /products/{id}: remove one product

This provides a simple way of translating the usual actions you are expected to perform in a system and marries them with the standard way of referencing and navigating your entities, especially when the returned representation adopts something like HATEOAS.

Understanding the Limitations

If RESTful APIs are commonplace, why am I advocating that it may not be enough?

While participating in discussions and technical reviews with other developers, I’ve seen REST leading to a model centered around operations that may be too simplistic for the domain that lies behind it.

Let’s take a look at an example of a very simple Product Information Management (PIM) system. Approaching it from the CRUD perspective you may be tempted to model it like figure 2.

Figure 2. Treating a Product as a single unit.

The full cycle of retrieving and updating the information can be seen in figure 3.

Figure 3. The actor requests the resource and updates it entirely as if it is a single (flat) element.

One of the issues with this approach is that it disregards the fact that a Product is composed of various smaller units which are likely manipulated at different times — and perhaps by different individuals.

Figure 4. A potential breakdown of the Product into smaller units respecting the domain constraints.

Figure 4 highlights that what was perceived as one entity is indeed a group of entities with relationships established between them. In the first approach, all of this is lost, or at least mixed-up, as it flattens by combining all those as part of a single Product “entity”.

Another aspect, also shown in the previous section, is that the API design and terms used (Create/Add Product) are devoid of the business knowledge and definition that could have been captured in its ubiquitous language.

A better approach can be to look at manipulating the resources while respecting and highlighting their domain concepts. One such example is leveraging a task-based UI approach.

Task-Based UI Approach

In a task-based approach, your goal is to make sure the interaction between the client and your service(s) retains an intent, instead of trying to deduce it. This is reflected in the language used by your API that is no longer constrained by the 4 CRUD operations.

For those using Command Query Responsibility Segregation (CQRS), this is similar to the idea that the UI will send you commands (imperative instructions that tell your application what to do).

Let’s take a look at the example where a customer wants to manipulate an order that has been placed.

Figure 5. A simple Order model with specific transactional boundaries.

When discussing this with the domain experts for this post-checkout experience, they shared that the customer is expected to be able to:

  • Remove an item from the order
  • Change the delivery address
  • Change the delivery service level (from express to standard or vice versa)
  • Cancel the entire order

You will then use these guidelines as part of the design of your API and UI.

Figure 6. Leveraging interactions targeted at the transactional boundary.

When customers want to remove an item, they would inform which one should be removed and the corresponding API will receive only the necessary information, instead of a snapshot of what the entire order looks like.

The payload would be as simple as the one below:

As a result we can see that we have achieved the following:

  • The intent is explicit

RemoveItemFromOrder should never be confused with ChangeDeliveryAddress.

  • Only the necessary information is provided

No more sending the entire entity representation if all we want to do is update a subset of the entity.

  • Domain knowledge is respected

If you are acting at the entity level you are aligning the intent with the domain boundaries

By now, I hope to have convinced you of the benefits of this approach. Before moving on I want to highlight that although you see the User Interface (UI) being referenced, the principle applies in the same way if the client of your service is another service.

Getting Started

The first step towards adopting this approach is to look at your domain model. If you are using Domain-Driven Design (DDD) this means looking at your aggregate design.

Is your aggregate unnecessarily large? Evaluate and challenge the business invariants to see if they are true invariants, or if you simply have a relationship that needs to eventually be consistent?

As you review your aggregates, make sure they expose explicit behaviors that are aligned with the domain language, also known as ubiquitous language. If you see yourself using CRUD names associated with your models, stay alert! It may be that you have to refine the language used.

At this moment you should not only have a good grasp of the boundaries, but also understand which behavior should be exposed.

If you follow an API-first approach, the next step would be to document your endpoints, always having the business language in mind. Finally, you present this contract to the future client of your API to confirm all requirements have been properly captured and understood.

You may have to iterate a few times, and it’s recommended to make this process as fast and as inexpensive as possible. We chose OpenAPI, and at times stoplight software, to facilitate the prototyping.

The actual code development can begin after the aforementioned steps have been completed and then be adjusted as knowledge on the domain increases with each iteration.

Conclusion

It is important to point out that REST is not evil and remains a valid way to address the development of your service, but it should be evaluated on a case-by-case basis and not taken for granted. If you have a very simple domain it may be enough to satisfy your needs.

On the other hand, for cases where you have a more complex or richer domain, focusing primarily on identifying the boundaries and responsibilities should be your priority. In that aspect, a task-based UI approach can help you make sure your services expose the necessary functionality while respecting said responsibilities.

Editorial reviews by Deanna Chow, Liela Touré & Pablo Martinez

Want to work with us? Click here to see all open positions at SSENSE!

--

--