API Design Best Practices – Managing Many Similar Operations

api-design

I have a web service (axis2) and the server performs 4 primary operations: createX, removeX, getX and updateX

Where X is represented by a combination of 4 values: id1, id2, id3 and id4

So, to simplify developer life, we created 3 extra helper functions:

removeXForId, getXForId and updateXForId where we represent the set of 4 values as 1 Identifier so that the developers who are calling us do not need to pass all those 4 values again and again and just store the unique identifier on their end.

But, we cannot also completely remove removeX, getX and updateX operations completely abd just rely on the unique identifier as some use cases need to pass all 4 set of values.

So, now we have around 7 operations where half (3) of them are duplicate operations with different input values.

Operations:

/createX
/getX {input: id1, id2, id3, id4}
/getXForId {input: id1234}
/removeX
/removeXForId
/updateX
/updateXForId

Is this a good practice?

UPDATE:

getX is exactly the same as getXForId in terms of behavior except the input arguments.

getX takes in 4 inputs: val1, val2, val3 and val4. But getXForId takes in just 1 ID which represents (val1, val2, val3 and val4)

val1, val2, val3 and val4 are some how related to each other, they in combination represent a unique link to a product in our system. So we tried to club them together.

Best Answer

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.

Related Topic