jdubray
04/13/13

Formalizing the API Style

 

I explained last month that the Web Style never took off since Tim Bray's post which predicted the End of SOA with a great dose of exaggeration. Instead, developers quickly pivoted towards a traditional, albeit HTTP-based, API style. I am not sure who could have expected a different outcome... 

Over all these years, the process of defining an API style has been quite chaotic as people swung from resource orientation to service orientation and back and, today, there is no particular "standard" or "sytle" which people can confidently rely on to design their API. Every developer decides artistically (and often inconsistenly) how he wants to use HTTP to marshal a particular API call.

I was doing some research on a few APIs this week-end and took another look at Swagger. I really like what they do. Incidentally, if anybody doubts that the Web Style is dead, I would take a peak at their petshop demo... That being said, that petshop API is terrible. 

When it comes to Resource Orientation, Service Orientation, Event Orientation... I actually like them all: I am a polyadic programmer (a.k.a metaprogrammer) and if anyone doubts it, I would encourage you to look at a framework I built in 2007, WSPER ("whisper"), which stands for Web Service, Process, Event, Resource.  

So, after so many years, should we continue opposing Resource and Service orientation? I would like to show here that indeed we can weave them together and as a result we can surface resource orientation in a much cleaner way while retaining all the benefits of service orientation in the process.

Services have this nice property of being exposed via an endpoint which creates a natural boundary of deployment, access and management. Enpoints truly enable an interface to be totally decoupled from its implementation. I cannot emphasize enough how important that decoupling is to distributed computing.

Service orientation brought another important innovation by moving the focus from objects to views. Services hide, just enough, the model behind the Service Interface: you understand that you are dealing with a customer view, but you never have to understand the extend of a customer class. Actually I would argue that a "true" customer class cannot be built, you always interact with a particular resource in a given context (Sales, Billing, Support...). If you doubt it, please take a look at this presentation from Daniel Jacobson. To be fair, however, as a side effect of this approach, queries or (partial) updates are notoriously painful when you need to define them in a WSDL, no argument there. I get that. But is that a reason to give up altogether on Service Orientation? 

On the other hand the RESTafarians have grossly misunderstood Service Orientation and at the same time, never really understood that REST brought back distributed objects into the programming model. Worse, because of its sole focus on resources, REST forces you to expose every single detail of the model to the resource consumer. It shouldn't be a surprise to anyone that in the end all they can deliver is CRUD. 

One thing that people often forget in SOA is that a service implementation can and should expose multiple service contracts, this is a bit harder to do with APIs because resource paths are not polymorphic by design and they are often hard wired to the implementation. Yes, I know, I could use yet another HTTP header or even concatenate an nth parameter to the Content-type but that is inelegant because you lose the natural boundary offered by a "service" endpoint. The real elegance I see behind Resource orientation (that none of the Web APIs I know implement, including the so-called RESTful ones) is when a path, say /customers/1234, truly points to the same resource regardless of which service I use to access it. 

What I mean here is when you have a customer service and a purchase order service, you should be able to fetch the same customer information (albeit with a different view) using the same path. Yes, when you query an order GET /orders/345 and it returns a path (not a link) to /customers/1234 the PO Service could implement that path and return a view of the customer in the context of a purchase order and if you invoke the same path on the customer service, you would get a more complete view (assuming you have the access rights). 

When you marshal the service name within the path (and the version for that matter), you cannot achieve that elegant behavior without a lot of out of band conventions and string manipulations. That's why I suggest that you define the service/API boundaries using subdomains rather than paths. For instance we would have two subdomains:

http://v1.customers.api.ebpml.org

http://v2.orders.api.ebpml.org

Each subdomain would provide access to its own resource collections, including overlapping collections. Both API calls would return a view of the same customer:

[GET] http://v1.customers.api.ebpml.org/customers/1234

[GET] http://v2.orders.api.ebpml.org/customers/1234

Imagine a "link" that works across services and versions? Many times I have mentioned that REST couples access and identity. Here we have a clear separation between access (subdomain endpoint) and identity (at the path level). 

This API style is capable of supporting the traditional Query/Create/Update/Delete operations quite efficiently, in a way that could look attractive to Web developers. However, I would like to emphasize again that HTTP alone is not a good query language. If you doubt it,please take look at the MongoDB API. Any serious service implementation will have to follow their lead, especially for complex queries and partial updates.

Actions are another point of misalignment between resource and service orientation. Many of the RESTafarians have dismissed the need for actions and advised to use a simple update instead. In the real world this is of course a completely bogus approach (validation, authorization, logging, throttling, versioning, duplication of the update logic on the consumer side...) and Web API designers have been prompt to use action verbs in their paths, wired to specific method calls.

I have also explained many times that every resource has a lifecycle. Some resource (such as a Tweet or Status) have a CUD lifecycle (Create -> Update* -> Delete) which maps well to the lifecycle of a Web page, and hence the HTTP verbs, but again in the real world, lifecycles are complex and each transition maps to an action which you need to express one way or another. A verb is what everyone understands. If you want to use a noun for a verb, be my guest, but semantically you have not changed the meaning of the call, you are still triggering an action, which when successful will transition the resource from one state to the next. 

 

So there is no reason to be afraid (and do like everyone else): use verbs as needed, either behind a resource when they apply to a resource instance or at the collection level when you initiate a lifecycle.

[POST] http://v2.orders.api.ebpml.org/orders/create

[POST] http://v2.orders.api.ebpml.org/orders/1234/submit

[POST] http://v2.orders.api.ebpml.org/orders/1234/pay

[POST] http://v2.orders.api.ebpml.org/orders/1234/ship

[POST] http://v2.orders.api.ebpml.org/orders/1234/deliver

[POST] http://v2.orders.api.ebpml.org/orders/1234/return

...

Composite calls (which fire several actions that transition to multiple states on different resources) should also be considered.

You will notice that the use of a subdomain as a service boundary greatly simplifies the migration from one major version to the next since API signatures and identities are not polluted by the version number like it is often the case in traditional API designs.

That's it, I believe that approach defines a clean Web API style which brings together the best of Resource Orientation and Service Orientation:

  • It achieves a true decoupling between resource access and identity, which in turns allow service consumers to use these identities across multiple services and supports a polymorphic resource access model (at the service level) which encourages the usage of views on the model, rather than a direct access to the model
  • It introduces a clean API boundary which can be much more efficiently managed (and authorized)
  • It provides a clear and clean way to define actions which map directly to the resource lifecycle
  • It simplifies version migrations

I came up with these ideas while building a Swagger stencil for Omnigraffle. So here it is.

omnigraffle_for_bolt

2 comments

# Douglass Parker Email on 05/01/13 at 18:40
**---
I understand that REST is not inherently virtuous and that it is occasionally desirable to bend the conventions a bit. But this article is, in my opinion, wrong. To summarize your arguments:
REST and the “death of SOA” have been over-hyped. True, but not particularly relevant. What technology isn’t over-hyped?

Most Web APIs do not use REST. So REST must be bad. I think this more reflects the difficulty of transitioning from RPC to resource orientation. Roy Fielding, the originator of REST comments on this at
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

“Services have this nice property of being exposed via an endpoint which creates a natural boundary of deployment, access and management. Enpoints (sic) truly enable an interface to be totally decoupled from its implementation.” True, but irrelevant. In a REST system, HTTP verbs and resource types are the interface, not the implementation. Think of database programming. If one wishes to embed complex logic directly in a relational database, one can either use stored procedures or triggers. Using a stored procedure is an RPC style. One directly calls the procedure by name and passes parameters. REST is more like using database triggers. To the client, the call appears to be a simple CRUD operation. But the trigger can contain logic as complex as needed. The claim that REST does not separate interface from implementation is just wrong.

“Service orientation brought another important innovation by moving the focus from objects to views. Services hide, just enough, the model behind the Service Interface: you understand that you are dealing with a customer view, but you never have to understand the extend (sic) of a customer class.” REST is not object oriented. It is resource oriented. A resource may or may not correspond with a domain object. It often is a “view” of a domain object. I can't see any inherent reason why a resource-oriented URL precluded the presentation of "views".

“REST forces you to expose every single detail of the model to the resource consumer. It shouldn't be a surprise to anyone that in the end all they can deliver is CRUD.” It’s hard to know where to begin. This is just an outrageously false statement. This should be apparent from my comments above. See also
http://blog.dhananjaynene.com/2009/08/crud-is-not-only-good-for-but-is-the-only-consistent-way-to-build-rest-over-http/.

“One thing that people often forget in SOA is that a service implementation can and should expose multiple service contracts, this is a bit harder to do with APIs because resource paths are not polymorphic by design and they are often hard wired to the implementation.” First of all, most service implementations expose a single contract. Secondly, resource paths are not hard wired to any implementation. In practice, a REST implementation is generally quite thin, delegating the real work to a service object, where you can polymorph to your heart’s content if that’s what you want. The example you use to illustrate this point really only serves to demonstrate that different domains might have resources with the same name. This is true, but it is true in SOA as well. REST solves your “verb problem” by using a uniform interface based on HTTP. You don’t have to worry about if a service, e.g., should be called “save”, “store”, “create”, “persist”, etc, when all these verbs mean the same thing. REST does not solve your “noun problem”. Is your resource a “customer”, “account”, “client”, etc? Solving this issue would require a move to RDF or something along those lines.

“HTTP alone is not a good query language.” Right. It will probably be adequate only 99% of the time.

“Many of the RESTafarians have dismissed the need for actions and advised to use a simple update instead. In the real world this is of course a completely bogus approach (validation, authorization, logging, throttling, versioning, duplication of the update logic on the consumer side...) and Web API designers have been prompt to use action verbs in their paths, wired to specific method calls.” These cross-cutting concerns are, of course, important. But they have nothing to do with the choice of resource-oriented vs. action-oriented URLs. I confess I can’t even begin to grasp the point you are trying to make here. It seems quite nonsensical to me.

Resources often have complex lifecycles. REST is unsuited for this. This is a common argument. Say I want to cancel an order. Say furthermore that a cancellation requires a reason for cancellation. The RESTful way is (supposedly) to replace the entire order with a PUT just to update the status, so wouldn’t it make more sense to put an action verb in your URL? Look, I realize the world will not end if you put a verb in your URL. And it would be ridiculous to PUT an entire order just to change the status. But it is simply not true that one cannot accomplish this easily and efficiently in a RESTful style. So while you presumably would support this:

POST /orders/123/cancel
Content: application/vnd.example. cancellation +json}
{ “reason” : “Customer has not paid” }

You are disdainful of this:

PUT /orders/123/ cancellation
Content: application/vnd.example.cancellation+json}
{ “reason” : “Customer has not paid” }

Or, better yet, this:

PATCH /orders/123
Content: application/vnd.example.cancellation+json}
{ “reason” : “Customer has not paid” }

The new HTTP PATCH verb has a specific use, partial updates to existing resources, which is, of course, exactly, what a state transition is. For readers unfamiliar with PATCH, see http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/. A PATCH call is almost always a good solution when one is tempted to use a verb in a URL. PATCH is best used with specific content types, because that allows one to have many kinds of patches.

“If you want to use a noun for a verb, be my guest, but semantically you have not changed the meaning of the call, you are still triggering an action, which when successful will transition the resource from one state to the next.” As you can see from the above example, verbs in the URLs are not necessarily more expressive. URL stands for Uniform Resource Locator. A URL usually also serves as a Uniform Resource Name, i.e. it usually also provides identity for a resource. URL does not stand for Uniform Resource Action. If you want to embed actions in URLs, be my guest, but you are needlessly perverting the concept of the URL. The fact that many others do it as well does not make it less of an anti-pattern.
# jdubray [Member] Email on 05/01/13 at 22:44
Doug,

thank you for your thoughtful response. I have debated this topic for way too many years, so I won't be able to continue. I think I made my argument clear, from MongoDB query language to resource lifecycles, to how long does it take to write a REST client.

Even people like Stefan Tilkov have been cornered to move to HATEOAS to not fall in the CRUD trap. People like Subbu, made some interesting comments about what they think of REST after practicing it, in the real world, not just the blogs and magazines. Last but not least, people like Mike Amundsen finally understand that you need a "shared understanding" for HATEOAS to work.

Nothing that I have argued for too many years has proven wrong based on where Stefan, Subbu and Mike are. Sure I can make HATEOAS work with some shared understanding, but why bother? what's the benefit? HATEOAS is the UDDI of REST. Mark my words. Everything else is a pile of CRUD.

It seems that we live in two different worlds and we'll never be able to bridge it.

This post has 1 feedback awaiting moderation...

Search

 Subscribe







blogtool