Posts tagged with “distributed”

Units of Work

Friday, 20 March, 2009

In regard to cross-resource transactions, I’m a member of the camp that wonders whether distributed transactions are strictly necessary in the REST/HTTP world; indeed I wonder whether they represent a design failure in modeling the granularity of your resources.

That said, we don’t live in a perfect world — and even if I can’t envisage why a properly designed RESTful application might require access to a distributed transaction, I can certainly envisage the environment where such an application might evolve. I’ve worked in a few of them. Places where interdepartmental barriers are as solid as the Great Wall; bastions of archaic technology where “one might provide a pseudo-RESTful interface, but one certainly won’t be re-architecting one’s legacy system in the buzzword language of the day”.

But, I think there’s a certain amount of smoke and mirrors in the JBossTS article “Transactional support for JAX RS based applications“:

Certainly it is worth pointing out that if a system cannot be made reliable then it can be of only limited utility. That said it is a worthwhile exercise to show how a REST based system can be made reliable.

Lack of distributed transactions would hardly seem to make a REST based system “unreliable” and, as a consequence, of “only limited utility”. Imagine a hotel booking facility — perhaps a booking resource, which internally might be constructed from a number of components, all governed (again internally) by transaction demarcation. Does the fact that the booking resource is coarse-grained and does not require an external transaction make it less reliable than a number of fine-grained resources which do? On the contrary. The latter sounds more like a WS-* api than a RESTful architecture, nothing to do with reliability.

So… hopefully it’s obvious I think it’s a bad idea. But if I were to write such an API, I think the 8-year old spec mentioned in the article falls short of the mark. Here’s my first-cut attempt at an alternative (which I still think falls a bit short of the mark, but is possibly an improvement):

Resource: tc
Method URL Content Description Statuses
GET /tc Returns HTML containing a summary of all transactions (status), plus an href to the transaction detail 200 – ok
/tc/{txid} Returns HTML containing detail for the transaction with id {txid}, including the status and a list of hrefs to the each of the participants. For example:


<html>
<body>
	<dl>
		<dt>Transaction ID</dt>
			<dd id="transaction-id">12345a</dd>
		<dt>Status</dt>
			<dd id="transaction-status">ACTIVE</dd>
		<dt>Timeout</dt>
			<dd id="transaction-timeout">5000</dd>
	</dl>
	<ul id="participants">
		<li>
			<a href="/tc/12345a/participants/1">
				Participant 1
			</a>
		</li>
		<li>
			<a href="/tc/12345a/participants/2">
				Order
			</a>
		</li>
	<ul>
</body>
</html>
200 – ok

404 – if txid is not found
409 – if the transaction has been deleted
/tc?status={status-type} Return HTML containing a summary of transactions with a specific status, with href to the transaction detail
For example: /tc?status=recovering or /tc?status=active
200 – ok
/tc/{txid}/participants Return a list of participants in the transaction (list of hrefs) 200 – ok
404 – if the transaction does not exist
/tc/{txid}/partipants/{rec-coord-id} Return HTML containing the detail of a participant. For example:


<html>
<title>Participant #2</title>
<body>
<dl>
    <dt>ID</dt>
        <dd>2</dd>
    <dt>Name</dt>
        <dd>Order</dd>
    <dt>URL</dt>
        <dd>
            <a href="http://internal.mydomain.com/someresource/123">Order</a>
        </dd>
</dl>
</body>
</html>
200 – ok
404 – if the transaction or participant does not exist
POST /tc [timeout={timeout}] Start a transaction (with default timeout) returning the url /tc/{txid} — which is deleted after the timeout or after completion (any HTTP method relating to {txid} thereafter returns 404). Use timeout={timeout} to override the default timeout period. 201 – created
DELETE /tc/{txid} Rollback and stop a transaction 204 – ok
404 – if the transaction does not exist
/tc/{txid}?commit Commit and stop a transaction 204 – ok
404 – if the transaction does not exist
POST /tc/{txid}/participants url={url}&[name={name}] Enlist {url} in the transaction, returning a unique resource for that participant of the form /tc/{txid}/participants/{rec-coord-id}. If name exists, record against the participant detail 201 – created
404 – if the transaction does not exist
PUT /tc/{txid}/participants/{rec-coord-id} url={url} Replace the participant url 200 – ok
404 – if the transaction or participant does not exist

The resource identified by a participant URL will have the following semantics:

Method URL Content Description Statuses
PUT URL/tx/{rec-coord-id} action=prepare The participant prepares any work done in the context of the transaction. The Warning header will contain additional info about the state of the prepare (either readonly, or notok). 200 – ok
200 – ok (+ Warning: readonly)
200 – ok (+ Warning: notok)
404 – participant has rolled back
action=commit The participant commits any work done in the context of the transaction. 200 – ok
200 – ok (+ Warning: heuristic)
404 – participant has rolled back
action=rollback The participant commits any work done in the context of the transaction. 200 – ok
200 – ok (+ Warning: heuristic)
404 – participant has already rolled back

Basic usage might look something like the following:

1. Create a new transaction resource
POST /tc
Host: somedomain.com

timeout=5000
HTTP/1.1 201 Created
Connection: close
Date: Thu, 19 Mar 2009 21:01:56 GMT
Location: http://somedomain.com/tc/10a23v991
X-Powered-By: TransactionServer/0.1
2. Create a new resource of some kind (notify the resource that it will operate with a distributed transaction)
POST /res1?tx
Host: internaldept1.somedomain.com

<xml>some xml describing the resource</xml>
HTTP1.1 201 Created
Connection: close
Date: Thu, 19 Mar 2009 21:01:56 GMT
Location: http://internaldept1.somedomain.com/res1/100
3. Update another resource (again notify that it will be operating within a transaction)
PUT /res2/somename?tx
Host: internaldept2.somedomain.com

<xml>some xml describing the update,
perhaps including a reference to the previously
created resource</xml>
HTTP1.1 200 Ok
Connection: close
Date: Thu, 19 Mar 2009 21:01:56 GMT
Location: http://internaldept2.somedomain.com/res2/somename
4. Enlist the url for each resource in the transaction
POST /tc/10a23v991/participants
Host: somedomain.com

name=resource1&url=http://internaldept1.somedomain.com/res1/100
HTTP/1.1 201 Created
Connection: close
Date: Thu, 19 Mar 2009 21:01:56 GMT
Location: http://somedomain.com/tc/10a23v991/participants/a01abgv21
X-Powered-By: TransactionServer/0.1
POST /tc/10a23v991/participants
Host: somedomain.com

name=resource2&url=http://internaldept2.somedomain.com/res2/somename
HTTP/1.1 201 Created
Connection: close
Date: Thu, 19 Mar 2009 21:01:56 GMT
Location: http://somedomain.com/tc/10a23v991/participants/a01abgv22
X-Powered-By: TransactionServer/0.1
5. Commit the transaction
DELETE /tc/10a23v991?action=commit
Host: somedomain.com
HTTP/1.1 204 Committed and deleted
Connection: close
Date: Thu, 19 Mar 2009 21:01:56 GMT
X-Powered-By: TransactionServer/0.1
6. ‘Behind the scenes’, the commit results in the following (2-phase commit at this point)…
PUT /res1/100/tx/a01abgv21
Host: internaldept1.somedomain.com

action=prepare
PUT /res2/somename/tx/a01abgv22
Host: internaldept2.somedomain.com

action=prepare

and

PUT /res1/100/tx/a01abgv21
Host: internaldept1.somedomain.com

action=commit
PUT /res2/somename/tx/a01abgv22
Host: internaldept2.somedomain.com

action=commit