REST Enterprise Architecture – Implementing a Composite REST Service

enterprise-architecturerest

The past years I have had the need to implement business logic in a larger type of service, most often orchestrating a handful of atomic/microservices.

Operations like "search and update document, then email HR team", "sign contract and update ERP", "start ERP batch job".

But it always feels weird mapping these composite services to a RESTful structure. What are some common ways to handle implementations like these?

I am not asking where this logic should be place (middleware or otherwise) but how REST endpoints should be constructed. In the examples provided above what would constitute a resource and with what verb?

/updatedocument with a POST?

/erp/batch/start with a PUT?

Best Answer

I have created many hypermedia services throughout the years and I have felt this pain. It boils down to perspective. So, I will try my best to describe my experience.

REST, at first glance, has been naively described as adhering to the an architectural constraint by using creating, updating, retrieving, and deleting resources. Many people equate this to simply having CRUD objects that are persisted into a database. Although a good portion of microservices do this, it is generally anemic and provide little functionality otherwise.

So, what exactly is a resource? To me, resources also provide abstractions of functionality and process. The representations of a resource are the data the show the state of that functionality or process.

Let's take your example of an "ERP batch". This is clearly a complex process and may involve multiple internal services. There most likely something that correlates this activity. This correlated data is your batch resource. It is the state of a batch. To meet the REST hypermedia constraint, we create a new batch resource by making a POST:

POST /erp/batch

I use a POST instead of PUT here because we are creating a new resource and do not know the exact URL of the resource. POSTs are not idempotent. Every invocation creates a new, unique resource. Also notice that the URL are singular nouns and not end in a verb.

The resource we are creating is a conceptual batch process. This POST action should store your initial state, start your process workflow asynchronously, and return the URL of the resource in the Location header. The workflow process does not need to complete before you return a URL, and should update the resource data periodically. How that happens depends on your internal architecture. It could be direct, event-driven, or whatever.

The client, in turn, can use that URL to retrieve the batch resource, where the representation is the current state of your batch process (for example):

GET /erp/batch/917163

The representation of your batch will change as your process moves through its workflow. The status of your batch, any errors or warning that may happen, and final results. The fact that the resource data changes, your client would need to know. This is where cache headers and conditional GET requests are used.

These two operations are probably the minimum you need. But what if you want to cancel a batch? How can we do this RESTfully? This takes a lot of thought. I have seen many options:

  • Some services expose a "cancel" flag as part of the resource representation and use a PUT operation. However, the PUT operations update the entire resource as a whole by having the client pass a new representation of that resource. This is OK if the representation is small and low risk, but it can allow clients to ruin your batch process.
  • In lieu of PUT, some use PATCH (a WebDAV HTTP Extension) to do a partial update of the resource and set this "cancel" flag. Again, does not feel that RESTful.
  • Some use DELETE to act like a cancel. DELETE means that we are removing the resource out of existence. The problem here is that doing a GET on the URL of a DELETEd resource should return a 400 level error, but you still need the correlation data to be present. This feels more like a shoehorned operation.

My preference is to make a cancellation resource:

PUT /erp/batch/917163/cancellation

If the service follows the hypermedia constraint, the client can discover the ability to cancel by following a link in the batch resource. I used a PUT method because I know the URL of this cancellation and may only accept exactly one cancellation. I could have easily done a POST to support multiple, uniquely trackable cancellation requests:

POST /erp/batch/917163/cancellation

Like the batch, it will also create a new URL. The cancellation representation may back link to the batch. The possibilities are infinite.

I hope this gives you a new perspective. Good luck.

Related Topic