REST is not SOAP with JSON


Back in the late 1990s, when Microsoft was king of everything and the Internet was new, shiny and a bit wild, Microsoft introduced a way to interact with servers remotely using a simple HTTP protocol (the same protocol websites use). Initially called XML-RPC and later renamed SOAP (or Simple Objects Access Protocol), it became the primary integration API and a standard in large institutions.

Fast forward to 2021 and we still have the SOAP protocol in our systems.  It’s flexible, it’s a known entity and it’s often the only way to connect to legacy systems, which if we are to be honest, the financial services world has a lot of legacy systems.  Modern web and mobile applications however, usually use REST APIs, not SOAP, and SVB’s API standards are centered around REST, so there sometimes feels like a tug of war between SOAP and REST. 

SOAP and REST have a lot of similarities; they both work over HTTP, you send a payload and get a response, you can manipulate that request with query parameters or headers, and you can even return REST in XML (although SOAP only supports XML).   So, it’s easy to think of REST as pretty much SOAP that supports JSON.

Only it’s not.

Let’s go back to the original name for SOAP – XML-RPC.  “RPC” refers to “Remote Procedure Call” which means it’s less about the API and more about what you’re telling the server to do.  A SOAP interface (that URL and payload) by design is tightly coupled with the code that is being executed – it assumes you are going to send a series of commands to the server and the server will execute code based on those commands.

In a traditional SOAP call to see a user record, for example, you might send a request like this:

POST /myservice 
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Header>
  </soap:Header>
  <soap:Body>
    <m:GetUser>
      <m:UserId>123</m:UserId>
    </m:GetUser>
  </soap:Body>
</soap:Envelope>

This gives the requestor a lot of flexibility – that SOAP envelope is essentially a scripted command, which is subtly different than a request.  All traffic can go to that one endpoint (/myserivce) and all requests use that one verb (POST) no matter what is actually happening on the server. If I want to delete user 123, I send a different payload (e.g. m:DeleteUser) but the actual endpoint is still POST /myservice

In REST we issue the same request with more structure in the URL and verb:

   GET /v1/identity/users/123

This structure means we don’t have to understand the backend server schema – the verb tells us this is a “read” request, and the path tells us we are looking at user 123.  If I want to delete user 123 I would just change the verb:

   DELETE /v1/identity/users/123

There are a few advantages to using the resource (path) and verb rather than describing the functionality in the payload:

Consistency: SOAP is extremely flexible, and therefore, a schema is required to understand what you’re looking at.  In our Delete example the directive could be any string, in REST it must be the DELETE verb. This consistency makes it easier to discover APIs and get a quick idea of what they do.

Human Readable: Because REST describes the object in the path (/users/{id}) we are able to understand what requests are being made (or could be made) without having to dig into the server configuration. This helps with audit and compliance but also with basic troubleshooting (“hey the POST requests to /users are failing” vs “myservice is intermittently failing”).

Abstraction: Because we have a consistent, human readable structure, we don’t have to be on the server to understand the basic request. This allows us to decouple the access from the actual execution – using a façade pattern we are able to grant access through an API gateway that can do simple orchestration.  We could send /users/{id} to one place and /users/{id}/documents to another, for example, or check for valid access rules before letting the request into the data center just based on the Resource and Verb combination.

Security: Because our core CRUD (Create, Read, Update and Delete) functionality is described in the verb, we can apply security at a gateway or firewall without needing the full schema.

One of the challenges with integrating any design patterns is understanding what’s okay and what’s not.  When we talk about “resource controllers” (such as /users/{id}/verify-id) it feels SOAP-ish.  “Verify ID” may not be a clear GET or POST, but something that triggers a process, like an RPC. 

But even when we bend REST a little for extended functionality, the end goal is to decompose services by separating functionality into distinct Resources and Verbs to give us that consistent, human readable, abstracted function that we can secure and audit which leads to happier consumers of the API.