Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
633 views
in Technique[技术] by (71.8m points)

api - Call a Server-side Method on a Resource in a RESTful Way

Keep in mind I have a rudimentary understanding of REST. Let's say I have this URL:

http://api.animals.com/v1/dogs/1/

And now, I want to make the server make the dog bark. Only the server knows how to do this. Let's say I want to have it run on a CRON job that makes the dog bark every 10 minutes for the rest of eternity. What does that call look like? I kind of want to do this:

URL request:

ACTION http://api.animals.com/v1/dogs/1/

In the request body:

{"action":"bark"}

Before you get mad at me for making up my own HTTP method, help me out and give me a better idea on how I should invoke a server-side method in a RESTful way. :)

EDIT FOR CLARIFICATION

Some more clarification around what the "bark" method does. Here are some options that may result in differently structured API calls:

  1. bark just sends an email to dog.email and records nothing.
  2. bark sends an email to dog.email and the increments dog.barkCount by 1.
  3. bark creates a new "bark" record with bark.timestamp recording when the bark occured. It also increments dog.barkCount by 1.
  4. bark runs a system command to pull the latest version of the dog code down from Github. It then sends a text message to dog.owner telling them that the new dog code is in production.
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Why aim for a RESTful design?

The RESTful principles bring the features that make web sites easy (for a random human user to "surf" them) to the web services API design, so they are easy for a programmer to use. REST isn't good because it's REST, it's good because it's good. And it is good mostly because it is simple.

The simplicity of plain HTTP (without SOAP envelopes and single-URI overloaded POST services), what some may call "lack of features", is actually its greatest strength. Right off the bat, HTTP asks you to have addressability and statelessness: the two basic design decisions that keep HTTP scalable up to today's mega-sites (and mega-services).

But REST is not the silver bulltet: Sometimes an RPC-style ("Remote Procedure Call" - such as SOAP) may be appropriate, and sometimes other needs take precedence over the virtues of the Web. This is fine. What we don't really like is needless complexity. Too often a programmer or a company brings in RPC-style Services for a job that plain old HTTP could handle just fine. The effect is that HTTP is reduced to a transport protocol for an enormous XML payload that explains what's "really" going on (not the URI or the HTTP method give a clue about it). The resulting service is far too complex, impossible to debug, and won't work unless your clients have the exact setup as the developer intended.

Same way a Java/C# code can be not object-oriented, just using HTTP does not make a design RESTful. One may be caught up in the rush of thinking about their services in terms of actions and remote methods that should be called. No wonder this will mostly end up in a RPC-Style service (or a REST-RPC-hybrid). The first step is to think differently. A RESTful design can be achieved in many ways, one way is to think of your application in terms of resources, not actions:

?? Instead of thinking in terms of actions it can perform ("do a search for places on the map")...

...try to think in terms of the results of those actions ("the list of places on the map matching a search criteria").

I'll go for examples below. (Other key aspect of REST is the use of HATEOAS - I don't brush it here, but I talk about it quickly at another post.)


Issues of the first design

Let's take a look a the proposed design:

ACTION http://api.animals.com/v1/dogs/1/

First off, we should not consider creating a new HTTP verb (ACTION). Generally speaking, this is undesirable for several reasons:

  • (1) Given only the service URI, how will a "random" programmer know the ACTION verb exists?
  • (2) if the programmer knows it exists, how will he know its semantics? What does that verb mean?
  • (3) what properties (safety, idempotence) should one expect that verb to have?
  • (4) what if the programmer has a very simple client that only handles standard HTTP verbs?
  • (5) ...

Now let's consider using POST (I'll discuss why below, just take my word for it now):

POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com

{"action":"bark"}

This could be OK... but only if:

  • {"action":"bark"} was a document; and
  • /v1/dogs/1/ was a "document processor" (factory-like) URI. A "document processor" is a URI that you'd just "throw things at" and "forget" about them - the processor may redirect you to a newly created resource after the "throwing". E.g. the URI for posting messages at a message broker service, which, after the posting would redirect you to a URI that shows the status of the message's processing.

I don't know much about your system, but I'd already bet both aren't true:

  • {"action":"bark"} is not a document, it actually is the method you are trying to ninja-sneak into the service; and
  • the /v1/dogs/1/ URI represents a "dog" resource (probably the dog with id==1) and not a document processor.

So all we know now is that the design above is not so RESTful, but what is that exactly? What is so bad about it? Basically, it is bad because that is complex URI with complex meanings. You can't infer anything from it. How would a programmer know a dog have a bark action that can be secretly infused with a POST into it?


Designing your question's API calls

So let's cut to the chase and try to design those barks RESTfully by thinking in terms of resources. Allow me to quote the Restful Web Services book:

A POST request is an attempt to create a new resource from an existing one. The existing resource may be the parent of the new one in a data-structure sense, the way the root of a tree is the parent of all its leaf nodes. Or the existing resource may be a special "factory" resource whose only purpose is to generate other resources. The representation sent along with a POST request describes the initial state of the new resource. As with PUT, a POST request doesn’t need to include a representation at all.

Following the description above we can see that bark can be modeled as a subresource of a dog (since a bark is contained within a dog, that is, a bark is "barked" by a dog).

From that reasoning we already got:

  • The method is POST
  • The resource is /barks, subresource of dog: /v1/dogs/1/barks, representing a bark "factory". That URI is unique for each dog (since it is under /v1/dogs/{id}).

Now each case of your list has a specific behavior.

##1. bark just sends an e-mail to dog.email and records nothing.

Firstly, is barking (sending an e-mail) a synchronous or an asynchronous task? Secondly does the bark request require any document (the e-mail, maybe) or is it empty?


1.1 bark sends an e-mail to dog.email and records nothing (as a synchronous task)

This case is simple. A call to the barks factory resource yields a bark (an e-mail sent) right away and the response (if OK or not) is given right away:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(entity-body is empty - or, if you require a **document**, place it here)

200 OK

As it records (changes) nothing, 200 OK is enough. It shows that everything went as expected.


1.2 bark sends an e-mail to dog.email and records nothing (as an asynchronous task)

In this case, the client must have a way to track the bark task. The bark task then should be a resource with it's own URI.:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

This way, each bark is traceable. The client can then issue a GET to the bark URI to know it's current state. Maybe even use a DELETE to cancel it.


2. bark sends an e-mail to dog.email and then increments dog.barkCount by 1

This one can be trickier, if you want to let the client know the dog resource gets changed:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}

303 See Other
Location: http://api.animals.com/v1/dogs/1

In this case, the location header's intent is to let the client know he should take a look at dog. From the HTTP RFC about 303:

This method exists primarily to allow the output of a POST-activated script to redirect the user agent to a selected resource.

If the task is asynchronous, a bark subresource is needed just like the 1.2 situation and the 303 should be returned at a GET .../barks/Y when the task is complete.


3. bark creates a new "bark" record with bark.timestamp recording when the bark occured. It also increments dog.barkCount by 1.

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

In here, the bark is a created due to the request, so the status 201 Created is applied.

If the creation is asynchronous, a 202 Accepted is required (as the HTTP RFC says) instead.

The timestamp saved is a part of bark resource and can be retrieved with a GET to it. The updated dog can be "documented" in that GET dogs/X/barks/Y as well.


4. bark runs a system command to pull the latest version of the dog code down from Github. It then sends a text message to dog.owner telling them that the new dog code is in production.

The wording of this one is complicated, but it pretty much is a simple asynchronous task:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

The client then would issue GETs to /v1/dogs/1/barks/a65h44 to know the current state (if the code was pulled, it the e-mail was sent to the owner and such). Whenever th


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...