Richard Groß

IT Archaeologist

Home

JORM (Json Object-Relational Mapping) is not REST

The Richardson Maturity Model is a great model for explaining the basics of building an http Api. By the name of its layers it is unfortunately suggesting that reaching Level 1 is already REST. That is a problem because it closes people to the decoupling and integration merits that actual REST has and leaves them with an API that can only be described as JORM (Json Object-Relational Mapping): Their API is not designed to solve a problem but to get data from A to B, so the problem can be solved at B.

For Service B to solve the problem it needs to make decisions based on state. That means we have to transfer a lot of state and the resulting json needs a huge schema that needs to stay in sync. It also means that Service B has to interpret that data in the same way that Service A would, which means code duplication between different services and often different teams, which leads to coupled releases and coupled teams. This form of coupling is inherent with RPC (remote procedure call)-style Apis and it is something that REST was designed to solve.

With this in mind I present the updated model, Richard’s Maturity Model (sorry for the name, couldn’t resist):

Richards Maturity Model

When you think REST, think Hypermedia. The inventor of REST, Roy Fielding, described the architectural style quite well in his dissertation in 2000, and explained the point again in 2008 (REST APIs must be hypertext-driven).

An example

To make this more transparent, let’s see how JORM and REST differ when we model a bank Api for viewing your account balance and transferring money.

GET /accounts/1234 (JORM-Style)
{
  "id": "aa-11",
  "ownerId": "b2-d2",
  "balance": 490.00,
  "currency": "EUR",
  "transactions": [
    {
      "id": "aa-11-11",
      "source": "b2-d2",
      "target": "c3-pd",
      "amount": 10.00,
      "currency": "EUR",
      "date": "2024-08-20T01:23:45.678Z"
    }
  ]
}

It’s possible the Api is too simplistic but it’s a good place to start discussing things because this Api is missing some important things:

  1. Where can we find further information? Where can we lookup the source and target of the transaction?

  2. What actions are we allowed to take? Can we transfer more money? Can we close the account?

The first question is usually solved by something like Api Blueprint or OpenApi which provides documentation for a human to sift through, guess and try if GET /users/b2-d2 or GET /owners/b2-d2 provides the right data for the source of the transaction. It would be nicer and less error-prone if the Account-Representation would tell us where the data is but that is a somewhat-solved problem of JORM-Apis.

The second question is not satisfactory solvable because it always requires code duplication: If we can and are allowed to withdraw money or close this account is dependent on multiple business rules. A transaction is always possible unless the account is closed and unless the balance is below zero, but sometimes below zero is also ok but only up to a certain balance and so on. A client would have to implement the same rules as the server and always keep them in sync lest it allows something that the server rejects.

REST solves these problems, because REST publishes all the possible actions from the current state. A client can simply look at the list of actions and decide what to do next. It does not need to know nor care why an action is allowed and thus less information needs to be shared and almost no duplication occurs:

GET /accounts/1234 (REST-Style, using Mason Format)
{
  "ownerId": "b2-d2",
  "balance": 490.00,
  "transactions": [
    {
      "source": "b2-d2",
      "target": "c3-pd",
      "amount": 10.00,
      "date": "2024-08-20T01:23:45.678Z",

      "@controls": { 1
        "self": { "href": "http://cool-bank/transactions/aa-11-11" }, 2
        "coolb:view-source": { "href": "http://cool-bank/transactions/b2-d2" }, 3
        "coolb:view-target": { "href": "http://cool-bank/transactions/c3-pd" } 3
      }
    }
  ],
  "@controls": { 1
    "self": { "href": "http://cool-bank/accounts/aa-11" }, 2
    "coolb:close-account": { 4
      "title": "Close account",
      "href": "http://cool-bank/accounts/aa-11",
      "method": "DELETE"
    }
  }
}
1 Additional hypermedia elements
2 Link to self
3 Link to source and target of the transaction
4 The presence of this control means that we are allowed to close the account

What is missing is the control for transferring money from a source to a target. It is missing because the account owner is on vacation right now and does not want any transfers. Or maybe it is missing because the account is closed. We don’t actually need to know the business reason. All our client sees is that the control is missing and therefore it cannot do it. We don’t need to duplicate why something is not allowed and that means Service A is free to add or remove rules whenever it wants and the client, Service B, just needs to react to the presence of a control or its absence. This is a supremely powerful concept and why the relatively new REST-style from the year 2000 is better at decoupling teams than the older RPC-Style Apis. The cost is that the services require more protocol knowledge as Oliver Drotbohm describes. If we adapt his naming to include JORM and REST we get:

Benefits of REST

Smart Server, Dumb Client

The client only having protocol knowledge gives us very clear benefits when we design a SPAs (Single-Page Applications) or need to support multiple devices such as iOS or Android.

The clients just need to look for the existence of the "coolb:close-account" or the "coolb:transfer-money" to know they should display the buttons for closing or transferring money. They ignore controls and optional fields they are unfamiliar with or render generic widgets based on the inputs the server requires.

All in all a lot less code is required for each client, because the server controls the interaction and you have a lot less errors to deal with because your iOS, Android or Web Apps cannot mismatch state.

If that seems far-fetched then please consider that you are viewing this post in your browser that allows you to react to arbitrary content without Mozilla, Google, Apple or Microsoft making adaptions in their client.

All your browser has is the protocol knowledge and that is enough. If we design our Apis the same way the web is designed, then we get the same decoupling and scalability benefits that have powered the last 30 years. This is not surprising because Roy Fielding’s dissertation, which defined the REST architectural style, was defining the core properties of HTTP so they knew which changes would benefit the protocol.

What makes HTTP significantly different from RPC is that the requests are directed to resources using a generic interface with standard semantics that can be interpreted by intermediaries almost as well as by the machines that originate services.

— Mike Amundsen
http://mamund.com/blog/archives/1107

Should everyone adopt REST?

By now it should be clear why REST has clear benefits and why a small but vocal group (so called RESTafarians) take every opportunity to point out that most street-REST is not REST (REST: I don’t Think it Means What You Think it Does).

It seems that this grouped has switched to using the name Hypermedia and Hypermedia Apis when talking about actual REST though. Semantic diffusion has taken its toll but since the ideas are so great and there is a dissertation describing them it is worth bringing back the term REST. For that to work we need a name for the alternative, and since that alternative is close enough to what many object-relational modelling tools give us, the name for the alternative is simply JORM.

Should everyone adopt REST?

Now for the real question: should everyone adopt REST over JORM?

I think the sweet spot is when teams own their server (backend) as well as one or more clients (frontend: SPA, iOS or Android app). When they adopt REST, they can make their clients significantly dumber and by that not only save a lot of code but also iterate faster because their client is decoupled from their server.

Sadly it is less clear for company-wide or even public Apis for two reasons:

REST Apis make it very easy to solve problems because the server exposes all allowed actions in the form of Hypermedia Controls. That however means you have to somehow know the problems your clients want to solve, which requires more work than just exposing state and letting them figure it out.

The second problems is that most clients are not used to the discoverable nature of REST Apis and demand stable URLs. Even without documentation teams often try to retro-engineer the URLs and when they inevitably change, their clients break, which is what Hyrum’s Law describes:

With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.

— Hyrum Wright
https://www.laws-of-software.com/laws/hyrum/

That however means the Apis cannot evolve as intended which is why solutions like FIT URLs are proposed where all your URLs are signed and any mismatch leads to a 404 Not Found.