When putFoo() is better than PUT ./foo

|

When designing interfaces for operations, the designer chooses between a specific operation interface or re-using a generic operation interface. There are many opportunities for re-use of generic operations when reading resources. The two areas of re-usability are in the application and intermediaries. Write or update operations typically do not have such re-usability. And update operations may have side-effects that prevent arbitrary composition of update transactions.

Let's look at 2 scenarios of write operations: a set timeout operation and a transfer operation that can be modeled as 2 set Balance. I'll model these generically and specifically.

PUT
A PUT operation is used for putting new timeouts and new balances, ie PUT /resource/timeout and PUT /balance/acct.

The put timeout operation has an input of the time requested, and an output of the granted timeout. There may be a fault of "invalid time" requested. Setting a timeout to zero indicates that the resource should be deleted. This is an example of a side-effect: the setting of a value has implications for things other than the value. In this case, it is the very existence of the resource that is affected.

The put balance operation has an input of the required new balance. The output is the new balance. There may be faults of "NegativeBalance", "TooBigAWithdrawal". Setting the balance to zero does not delete the account.

Interfaces are different
The interface to the PUT operation is different for the timeout and the balance scenario. A client must know the inputs, outputs, and faults for the operation on either timeout or balance resources. The application gains no benefit from re-using the same operation interface. There is also a significant side-effect on one of the operations that is not shared by the other operation. WebDAV uses the term "live" to denote those properties or resources that may have operation side-effects.

Intermediaries get no re-use
Another major reason to use generic operations is for intermediaries that know nothing of the contents of the message but use the resource and operation name. Two foremost examples are caching and security.

Neither of these operations should be cached. In fact, they MUST not be cached. Imagine doing a PUT to a balance and getting a cached response without affecting the balance.

Each operation will have a different security model, as it is very unlikely that a valid updater of balance #1 will be a valid updater of balance #2 or the timeout. In the update balance case, a person might be only allowed add to somebody else's account if they are doing a debit from an account they have authority over. Put another way, the balance operation's security model is imbued in the application and not externalizable.

The use of a generic operation (which enhances intermediaries visibility into the message) does not bring any benefits because there is nothing re-usable at an intermediary, as shown in the cases of caching and security.

Transactions
A transfer between 2 accounts can be modeled as a client controlled transaction or a service controlled transaction. Client controlled transactions have never been deployed on the internet because of the network latency, network errors, resource usage and distributed garbage collection problems which make it almost impossible to do ACID transactions.

One solution for transactions is to relax the ACID constraints and have client controlled compensation. If there is an error before completion of the last interaction, the client must attempt to ensure the correct state. If the last message returns an application error, the client will have to undo the previous operations. If there is a network loss, the client must determine whether the last message was successfully processed or not before any potential undo operations are done.

The solution to gain ACID properties is to "boxcar" the transaction requests. A transfer is then a message containing 2 PUT requests. A PUT on 2 different resources means that the PUT operation is recreated inside the boxcar. In the case of HTTP PUT, the 2 PUT operations are combined into a PUT of a new type to a new resource, ie PUT /transactionmgr with a transaction message. Thus all of the PUT syntax and semantics are created inside the transaction message. Intermediaries are prevented from operating on the messages in the transaction.

Combining operations that may have side-effects into transactions is potentially dangerous. An operation with a side-effect may cause errors in subsequent operations. An example is a set resource timeout to zero - which deletes the resource - followed by setting a property on the resource. The setting of the property will fail because the resource has been terminated.

Designing operations with side-effects as generic operations has the very undesirable effect of giving the impression that the operations may be arbitrarily combined and ordered. In reality, the ordering of the operations is completely determined by the results of the side-effects.

This shows that there is no efficiency to be gained by combining generic operations. Combining generic operations results in either: recreating all the generic infrastructure for service controlled updates or living with client controlled transaction frailties. The interface consumer must examine each of the operations for any potential side-effects to ensure that any transactions are constructed in a useful manner.

Specific operations: setTimeout and transfer
The specific operations interface for the set timeout and transfer are:
resource.setTimeout(time) and
transfer(acct1, acct2, amount)

These operations are specific to the resources and make not attempt to re-use any syntax or semantics between them. The applications gain no reuse of the interface, and intermediaries must treat the messages as opaque. The transfer operation is probably modeled as an atomic transaction on the service. The client has no opportunity for erroneously combining the operations into a transaction.

Specific operations are the appropriate interface design for operations such as these, which have potential side-effects and low re-use of application or intermediary functionality.

PUT utility
Generic update operations do have utility. It is the scenarios where there is some re-use of the update operation across resources. This typically means that there is a simple security model and no side-effects of the update operation. WebDAV uses the term "dead" to denote properties or resources that do not have operation side-effects. A simple scenario is a resource this is just bits on a disc.

An example of dead resources or properties is my web site. I can upload images or html files to various directories. I can use a single application - ftp or a browser - across the different resources and resource types. It is probably acceptable to return a cached image PUT if a previous identical image PUT was successful. The security model is identical across the resources. If there are any errors, the client can simply resend the messages. There are no side-effects of doing the update operations.

Conclusion
An interface designer has 2 significant factors to consider in interface design: the re-usability of functionality across application and intermediaries, and the potential for side-effects of the operation. This article has shown that specific write operations are the correct interface design choice for many complex update operations. There are also constrained situations where generic update operations such as HTTP PUT provide superior value. There is no single correct or canonical way to model an interface.

About this Entry

This page contains a single entry by Dave Orchard published on November 4, 2004 10:31 AM.

WS-Addressing: EPRs and URIs was the previous entry in this blog.

US Voting by IQ is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Categories