A Quick Refresher on REST and CRUD
REST APIs have become an industry-standard in recent years, seemingly taking over RPC and SOAP APIs in terms of popularity.
Coined by Roy Fielding in his dissertation in 2000, the term REST or RESTful stands for Representational State Transfer, and is a design paradigm for building APIs that has several core constraints:
- Client-Server: Both the client and the server act independently.
- Stateless: The server does not record the client’s state.
- Cacheable: The server marks data as cacheable or not.
- Uniform Interface: The client and server behave against a uniform interface, or predictably.
- Layered System: The application is abstracted away from all layers between the client and server.
Oftentimes, REST APIs are built to mimic a CRUD-like interface, and this stems from the fact that the HTTP standard methods are structured very similarly to the CRUD interface, and REST APIs deal in resources that can be manipulated by said interface.
Extrapolating from this, the CRUD life-cycle of a REST resource is thus: You
create a resource, maybe you want to
update it occasionally. Down the track, you
read the resource, and maybe you decide to
HTTP Method | CRUD Operation POST | Create Resource PUT | Update / Set Resource GET | Retrieve Resource DELETE | Delete Resource
My Main Concern With CRUD
This article isn’t a CRUD slander article, I promise. It has its place in the world (in most systems, truthfully), and personally, I believe it’s really handy in a majority of cases.
When a domain or problem set starts to spiral outwards in terms of complexity, however, I believe CRUD starts to show some of its weak points.
Anyway, let’s create an example we can use for the rest (heh) of this article, it’s not a very complex design, but it should serve the purpose of demonstration.
Here we have a simple model for some kind of product shipping domain.
The entity here is the order and it encapsulates an active orders state, and has several value objects within it to help explain that state.
CRUD requires a very coarse-grained design
It’s true, and that’s fine for a lot of cases, especially regarding smaller domains or models. Many resources contain sub-resources or fields within them that are tied to certain pieces of domain logic.
In the example above, if you wanted to make changes to the orders
CurrentLocation, there may be three reasons why:
- The order has gone from
in progress, and now has an actual
- The order which was in
- The order which was
in progresshas moved to its
Let’s say you were given an
Order that was in progress, and its
CurrentLocation was “Arizona”. Its destination was “Mexico”.
In a normal CRUD like operation, to make a change to the
CurrentLocation to deliver the
Order, you might do something like this:
This is coarse-grained as it takes the entire object as the payload, which forces the user to have knowledge of how the business logic works in order to create a correct call (i.e. if
CurrentLocation == Destination, then the
status should be
This is a request tailored to the CRUD interface, as opposed to being tailored for the problem at hand.
Enter: An Intent or Action based REST API
By understanding that a user will interact with your API to perform specific actions or with a specific intent in mind, it is easy to tailor the models of the request to these intents to alleviate the burden of knowledge from the end-user and shift it into the system’s responsibility.
This strays away from some of the conventional aspects of REST and into almost an RPC-style area, as the API allows actions on verb-based resources or intents/actions as opposed to traditional nouns.
In the example above we saw some cons in the CRUD styled request, so let’s compare it to a possible intent-based variant:
Pretty simple, eh? Well, we’re dealing with a very simplified domain. Regardless, this requires the user to have much less knowledge of the domain and makes it impossible for the user to create requests that would leave the data in a bad state.
By POSTing to this resource, we’re acknowledging that the user wants to set the given
Orders state to delivered. So, let’s set it’s
CurrentLocation to the
Destination, and while we’re at it, let’s change its
Status to delivered.
Through this, we fulfill the user’s intent and perform the required business logic within our system, without requiring the user to understand how the fields should wire up together.
As mentioned, this is a very simplified example, which is why it may seem a bit plain or simple, however, GitHub actually does something similar with its merges, and a few other places in its API. It uses the resource merges and accepts a POST request with a required few fields, however, it ends up returning a “201 created” for the commit resource. The merges resource doesn’t map to a physical entity, but instead an intent. Neat!
This may come as a shock to you, but even though CRUD APIs work great in a lot of use-cases, it doesn’t work so well in all of them. Intent APIs exist as a way to reify the user intent as an API model, pulling away domain logic from the request or chain of requests, and into the handler for that intent resource, nice and safe away from the poor client.
ThoughtWorks has a brilliant article on this topic which goes into much greater detail. Definitely check it out!
Full Article: Dominic C @ Better Programming
Mobile App Development Best Practices – 03.10
iOS MetaCodable – Supercharge Swift’s Codable implementations with macros meta-programming How to build a Tuist plugin and publish it using...
How to make and use BOM (Bill of Materials) dependencies in Android projects
By using a BOM dependency, you can avoid specifying the versions of each individual library in your app, and let...
Telegram turns 10 years old and revenues stagnate
Telegram seems to want to grow not only through messaging but also through communities, which pretty much means it wants...
MetaCodable – Supercharge Swift’s Codable implementations with macros meta-programming
Supercharge Swift‘s Codable implementations with macros. Overview MetaCodable framework exposes custom macros which can be used to generate dynamic Codable implementations. The core of the framework...
How to get started with Swift Concurrency 🧵 (Beginner Tutorial)
Swift has built-in support for writing asynchronous and parallel code in a structured way. Asynchronous code can be suspended and resumed later,...
Mobile App Development Best Practices – 02.10
Data.ai has summarized the interim results of the year – and once again we have a record. Annual consumer spending...