The "verb" and "noun" terminology is somewhat unfortunate here. As you already mentioned, you can easily create object for function. All object oriented languages except Java have that transformation built-in and in Java you end up doing it all the time anyway ending up with lots of objects with single method and often one called "invoke", "execute", "apply" or somesuch (so it's the programming languages where the "verb"/"noun" distinction actually doesn't make sense).
The "verbs" of REST is more like classifying your methods to getters, setters (deleters; can be considered kind of setters) and other. And trying to do everything with getters and setters. The reason for this is:
- Easier semantics in face of communication failure, since both getters and setters are idempotent. Getting the resource twice has no additional effect and nor does setting it to the value it already has.
- Defining some semantics that can be used by possibly caching proxy that does not understand the specific interface. Getters are cached and setters are known to invalidate the cache.
HTTP was designed from the beginning with both caches and fault-tolerance in mind, so these two points lead to it's four basic methods:
GET
is a getter. It's assumed not to modify server state and return the same value each time with possibility to specify expiration and revalidation policy.
PUT
and DELETE
are the setter and deleter (= setter with nil). They are not normally used in context of normal web, but make sense for REST.
POST
is a generic "invoke" kitchen sink for which caches can assume nothing.
REST is a design pattern describing how to use raw HTTP or similar network protocols to implement interface that allows easy handling of failures by simple retrying and works nicely with caching proxies.
It doesn't correspond easily to regular object-oriented programming API. I think it is actually a good thing. The challenges of interfacing over network, which is inherently unreliable and where round-trips are much slower than transferring even moderate amount of data call for different design approach than in-process API, so when it looks different, people don't try to apply invalid experience from the other domain that much (that's the bane of SOAP, XML-RPC and such; it looks like procedure calls, but doesn't work like it and ends up being pain to work with).
It would make more sense to me to organize the API around resources and use the verbs (GET, PUT, POST, DELETE) that come with the http protocol, in short: Make it more RESTful, that is surely considered good practice.
This is a good succinct talk on API design that also analyzes popular APIs such as facebook's or twitter's.
I am lost on the difference between your /getX
and /getXForId
so I can't really give you an example of how I would change your API.
Edit
I tried to model the API in Sinatra. It is pretty self explanatory and straight forward. (I saw you are using axis but it's been a while since I did serious Java). If you are serious about RESTful then you probably want to drop the approach with the four ids. However the approach works:
require "sinatra"
require "sinatra/reloader"
def all_ids_given?(ids_given, ids_needed)
ids_needed.reduce(true){|memo, id| memo &= ids_given.include? id}
end
#Your old /getXForId
get '/X/:combined_id' do |id|
"Getting X with combined id #{id}\r\n"
end
#Your old /createX
post '/X' do
post_params = request.body.read
"Creating a new X with #{post_params} \r\n"
end
#Your old /updateXForId
put '/X/:combined_id' do |id|
put_params = request.body.read
"Updating X with combined id #{id}: New values are #{put_params}\r\n"
end
#Your old /removeXForId
delete '/X/:combined_id' do |id|
"Deleting X with combined id #{id}\r\n"
end
#Your old /getX
get '/X' do
if all_ids_given?(params.keys, ["id1","id2","id3","id4"])
"Getting X with the ids #{params}\r\n"
else
"Error: Need arguments for id1, id2, id3 and id4\r\n"
end
end
#Your old /updateX
put '/X' do
if all_ids_given?(params.keys, ["id1","id2","id3","id4"])
put_params = request.body.read
"Updating X with the ids #{params}: New values are #{put_params}\r\n"
else
"Error: Need arguments for id1, id2, id3 and id4\r\n"
end
end
#Your old /removeX
delete '/X' do
if all_ids_given?(params.keys, ["id1","id2","id3","id4"])
"Deleting X with combined id #{params}\r\n"
else
"Error: Need arguments for id1, id2, id3 and id4\r\n"
end
end
#Extensibility
get '/X/:combined_id/Y' do |id|
"Getting all Ys for the X with combined id #{id}\r\n"
end
Testing the API with curl:
base_url="http://localhost:4567/"
echo "Getting a X with a combined id"
echo -n "-> "; curl ${base_url}X/id1234
echo "Creating a new X with a list of properties"
echo -n "-> "; curl --data "property_1=test" ${base_url}X
echo "Updating an existing X with a combined id"
echo -n "-> "; curl -X PUT --data "property_1=test&property_2=another_test" ${base_url}X/id1234
echo "Deleting an existing X with a combined id"
echo -n "-> "; curl -X DELETE ${base_url}X/id1234
echo "Getting a X with 4 ids"
echo -n "-> "; curl ${base_url}"X?id1=1&id2=2&id3=3&id4=4"
echo "Getting a X with 4 ids, but supplying only 3"
echo -n "-> "; curl ${base_url}"X?id1=1&id3=3&id4=4"
echo "Updating a X with 4 ids"
echo -n "-> "; curl -X PUT --data "property_1=test&property_2=another_test" ${base_url}"X?id1=1&id2=2&id3=3&id4=4"
echo "Updating a X with 4 ids"
echo -n "-> "; curl -X DELETE ${base_url}"X?id1=1&id2=2&id3=3&id4=4"
echo "\r\nTesting the extensibility"
echo "Getting all Ys for a single X (Unrelated example: /post/2/comments would give all comments to a single post with the id 2)"
echo -n "-> "; curl ${base_url}X/id1234/Y
echo "This won't work for the 4 ids"
echo -n "-> "; curl ${base_url}"X?id1=1&id2=2&id3=3&id4=4/Y"
The script returns the following:
Getting a X with a combined id
-> Getting X with combined id id1234
Creating a new X with a list of properties
-> Creating a new X with property_1=test
Updating an existing X with a combined id
-> Updating X with combined id id1234: New values are property_1=test&property_2=another_test
Deleting an existing X with a combined id
-> Deleting X with combined id id1234
Getting a X with 4 ids
-> Getting X with the ids {"id1"=>"1", "id2"=>"2", "id3"=>"3", "id4"=>"4"}
Getting a X with 4 ids, but supplying only 3
-> Error: Need arguments for id1, id2, id3 and id4
Updating a X with 4 ids
-> Updating X with the ids {"id1"=>"1", "id2"=>"2", "id3"=>"3", "id4"=>"4", "property_1"=>"test", "property_2"=>"another_test"}: New values are property_1=test&property_2=another_test
Updating a X with 4 ids
-> Deleting X with combined id {"id1"=>"1", "id2"=>"2", "id3"=>"3", "id4"=>"4"}
Testing the extensibility
Getting all Ys for a single X (Unrelated example: /post/2/comments would give all comments to a single post with the id 2)
-> Getting all Ys for the X with combined id id1234
This won't work for the 4 ids
-> Getting X with the ids {"id1"=>"1", "id2"=>"2", "id3"=>"3", "id4"=>"4/Y"}
What you can see is that nesting resources and the four parameter approach does not work well together. Maybe one could model it differently and do something like /X/:id1/:id2/:id3/:id4
.
Best Answer
You need to watch this talk by Jim Webber.
Think "messaging"; send a message to your domain, describing what you want to have happen. The side effect of the message is that your domain model actually changes its state. The "resource" is the message queue.
The spelling of the resource name doesn't matter to the machines; but people tend to get fussy when the identifiers you use break from the convention that resources are "nouns".
Also, we're talking about a resource that is subordinate to
/subscriptions/{subscriptionid}
, so convention (see RFC 3986) calls for expressing that relationship with a path segment, rather than using the query part.So these spellings might be reasonable