Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm in the process of designing an HTTP API, hopefully making it as RESTful as possible.

There are some actions which functionality spreads over a few resources, and sometime needs to be undone.

I thought to myself, this sounds like a command pattern, but how can I model it into a resource?

I will introduce a new resource named XXAction, like DepositAction, which will be created through something like this

POST /card/{card-id}/account/{account-id}/Deposit
AmountToDeposit=100, different parameters...

this will actually create a new DepositAction and activate it's Do/Execute method. In this case, returning a 201 Created HTTP status means the action has been executed successfully.

Later if a client wishes to look at the action details he can

GET /action/{action-id}

Update/PUT should be blocked I guess, because it is not relevant here.

And in order to Undo the action, I thought of using

DELETE /action/{action-id}

which will actually call the Undo method of the relevant object, and change it's status.

Let's say I'm happy with only one Do-Undo, I don't need to Redo.

Is this approach ok?

Are there any pitfalls, reasons not to use it?

Is this understood from the POV of the clients?

share|improve this question
    
Short answer, that's not REST. –  Evan Plaice Jan 23 '13 at 18:53
1  
@EvanPlaice care to elaborate on that? that's exactly the question. –  Mithir Jan 24 '13 at 8:23
1  
I would have elaborated in an answer but Gary's answer already covers most/all of what I'd add. I say it's not rest because URIs are only supposed to represent resources (ie not actions). Actions are handled through GET/POST/PUT/DELETE/HEAD. Think of REST as an OOP interface. The goal being to make the API fit the general pattern and decouple it from implementation specific details as possible. –  Evan Plaice Jan 25 '13 at 0:57
1  
@EvanPlaice Ok I understand, thanks. I think it's confusing here because Deposit could be thought of as a noun and as a verb... –  Mithir Jan 25 '13 at 6:33
    
In this case the URI should represent a transaction where debiting (taking money) and crediting (giving money) are actions done via POST requests. POST is used for both because each time money is moved in either direction it represents a new transaction being created. In your specific case, the transactions are taking place on a cardholder's account so the card's account number is the resource URI. –  Evan Plaice Jan 25 '13 at 19:39

2 Answers 2

up vote 8 down vote accepted

You're adding in a layer of abstraction that is confusing

Your API starts off very clean and simple. A HTTP POST creates a new Deposit resource with the given parameters. Then you go off the rails by introducing the idea of "actions" that are an implementation detail rather than a core part of the API.

As an alternative consider this HTTP conversation...

POST /card/{card-id}/account/{account-id}/Deposit

AmountToDeposit=100, different parameters...

201 CREATED

Location=/card/123/account/456/Deposit/789

Now you want to undo this operation (technically this should not be allowed in a balanced accounting system but what the hey):

DELETE /card/123/account/456/Deposit/789

204 NO CONTENT

The API consumer knows that they are dealing with a Deposit resource and is able to determine what operations are permitted on it (usually through OPTIONS in HTTP).

Although the implementation of the delete operation is conducted through "actions" today there is no guarantee that when you migrate this system from, say, C# to Haskell and maintain the front end that the secondary concept of an "action" would continue to add value, whereas the primary concept of Deposit certainly does.

Edit to cover an alternative to DELETE and Deposit

In order to avoid a delete operation, but still effectively remove the Deposit you should do the following (using a generic Transaction to allow for Deposit and Withdrawal):

POST /card/{card-id}/account/{account-id}/Transaction

Amount=-100, different parameters...

201 CREATED

Location=/card/123/account/456/Transation/790

A new Transaction resource is created which has exactly the opposite amount (-100). This has the effect of balancing the account back to 0, negating the original Transaction.

You might consider creating a "utility" endpoint like

POST /card/{card-id}/account/{account-id}/Transaction/789/Undo <- BAD!

to get the same effect. However, this breaks the semantics of a URI as being an identifier by introducing a verb. You are better off sticking to nouns in identifiers and keeping operations constrained to the HTTP verbs. That way you can easily create a permalink from the identifier and use it for GETs and so on.

share|improve this answer
3  
+1 "technically this should not be allowed in a balanced accounting system". Somebody knows how to count beans. That statement is absolutely correct, the way to reverse would be to create another transaction crediting the funds back. General ledger entries should always be considered immutable and permanent once a transaction is completed. –  Evan Plaice Jan 23 '13 at 18:56
    
So, if I change, in my questions, instead of Delete /action/... to Delete /deposit/... is it ok? –  Mithir Jan 24 '13 at 8:24
2  
@Mithir I was describing the accounting rule. In a standard double-entry bookkeeping system you never remove transactions. History once committed is considered immutable to keep people honest. In your case you could still use a DELETE action but on the back-end (ex general ledger database table) you'd add another transaction representing crediting (ie giving back) the money back to the user. I'm no bean counter (ie accountant) but it's one of the standard practices taught in a "Principles of Accounting I" course. –  Evan Plaice Jan 25 '13 at 1:04
2  
(cont) Database logs use transactions in a similar manner. That's why it's possible to replicate and/or rebuild a dataset using only the logs. As long as the transactions are replayed in the chronologically, it should be possible to rebuild the dataset from any point in its history. Removing mutability from the equation ensures consistency. –  Evan Plaice Jan 25 '13 at 1:18
1  
Fair enough just rename it to Transaction. –  Gary Rowe Jan 25 '13 at 7:18

The main reason for REST existence is resilience against network errors. To which end all operations should be idempotent.

The basic approach seems reasonable, but the way you describe the DepositAction creation does not sound to be idempotent, which should be fixed. By having client provide unique ID that will be used to detect duplicate requests. So the creation would change to

PUT /card/{card-id}/account/{account-id}/Deposit/{action-id}
AmountToDeposit=100, different parameters...

If another PUT to the same URL is made with the same content as previously, the response should still be 201 created if the content is the same and error if the content is different. This allows the client to simply retransmit the request when it fails, since the client can't tell whether the request or response got lost.

It makes more sense to use PUT, because it just writes the resource and is idempotent, but using POST wouldn't really cause any problem either.

To look at the transaction details the client will GET the same URL, i.e.

GET /card/{card-id}/account/{account-id}/Deposit/{action-id}

and to undo it, it can DELETE it. But if it actually has anything to do with money as the sample suggests, I would suggest PUTting it with added "cancelled" flags instead though for accountability (that there remains trace of created and cancelled transaction).

Now you need to choose a method of creating the unique id. You have several options:

  1. Issue client-specific prefix earlier in the exchange that must be included.
  2. Add a special POST request to get blank unique ID from the server. This request does not have to be idempotent (and can't, really), because unused IDs don't really cause any trouble.
  3. Simply use UUID. Everybody uses them and nobody seems to have any problem with neither the MAC-based ones nor the random ones.
share|improve this answer
2  
From What I know, POST is not idempotent.en.wikipedia.org/wiki/POST_(HTTP)#Affecting_server_state –  Mithir Jan 23 '13 at 8:34
    
@Mithir: POST is not assumed to be idempotent; it still can be. But it's true that since all REST operations are supposed to be idempotent, POST has basically no place in REST. –  Jan Hudec Jan 23 '13 at 8:46
1  
I'm confused... content I've read and existing implementation I'm familiar with(ServiceStack, ASP.NET Web API), all suggests that POST has a place in REST. –  Mithir Jan 23 '13 at 9:01
    
@Mithir: Actually POST is used even in one of the suggestions above, for getting fresh ID. But for resilience against errors, most things need to be idempotent and such would not use POST. –  Jan Hudec Jan 23 '13 at 9:20
3  
In REST idempotence is assigned to the resource, not the protocol or its response codes. Thus, in REST over HTTP the methods GET, PUT, DELETE, PATCH and so on are considered idempotent although their response codes may vary for subsequent calls. POST is idempotent in the sense that every call creates a new resource. See Fielding's It is OK to use POST. –  Gary Rowe Jan 23 '13 at 14:47

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.