api, REST

REST is not the answer for everything

For those that know me, you know how strongly I believe in the power of the style of Representational State Transfer (REST) as the right way to serve the API. You know that I tend to divvy up each twenty-four hour day between RESTing and resting. You may thus be surprised to learn that I believe that REST is not the solution to everything. Blasphemy, you say? Allow me to explain.

A toasty example

For this example, consider the toaster. It solves an important problem in that it transforms breads into toast. Excellent, I am a fan of toast. We can extend this solution further – for the bagel lover, they have created a button that enables bagel toasting abilities. However, when we consider the domain of warming up other foods, it becomes a bit more of a stretch (shrimp? celery? macaroni and cheese may be a problem, but I believe the outcome would still be delicious – disclaimer, don’t try this at home). One could even argue you could solve world peace by providing bread-toasting abilities all over the world – end world hunger and untoasted bread in one fell swoop. However, there are some things toasters simply can’t solve – like finding words that rhyme with orange. Toasters are complementary to this problem, at best.

Where REST works and does not work

To understand where REST does not work, you must first understand where REST excels. REST revolves around two central pieces – the resource (akin to a real life object), and the simple actions of CRUD (create, read, update and delete). One of the best charts to refer to is the following (from Wikipedia), which maps HTTP verbs to object types:

GET PUT POST DELETE
Collections/bread List of all items Replace the entire collection Create a new entry in the collection Delete the entire collection
RESOURCE/bread/1 Retrieve the one resource Replace the item if it exists, otherwise, create it Not generally used Delete the element

There are plenty of ways to get into trouble with REST, but there are creative and innovative ways to mitigate this. For example, batching can become problematic, depending on back-end behavior. I explored this issue at an API Craft conference – if we transfer an entire diagnostic imaging study from one place to another, and we have a RESTful interface to create a study, series, and individual instances, how do we do this where it is efficient, that doesn’t suffer from race conditions, and that won’t return premature results before the entire study has been transferred? Some RESTful ways to approach this include having special transfer resource end-points, or through the use of locking. It may not be RESTful, but it is RESTy (there’s a difference, see Stephen Colbert’s thoughts on “truthy“). I’ll explore the topic of innovative methods to accomplish complicated goals in a future post.

REST will break down when we are either dealing with unconventional things, or with unconventional verbs.

  • An unconventional thing would be the Image Display actor in the IHE Radiology IID profile. Here, you are not manipulating an object, you are not even retrieving it – you are asking this object to take control of your client to perform some actions. This does not make any sense to do this RESTfully.
  • An unconventional verb would be, for example, “transfer me to over there”. It is not one of the simple RESTful verbs (create, read, update, or delete) – it actually is a compound set of actions. It may also require some work for the client to do to accomplish. Because of this, on it’s own, it cannot be RESTful.

REST will work, beautifully, in many projects, but it is not the solution to end all solutions. Consider it for all your great projects, but do not force it if it does not fit – it will stick out plainly, and your application (and constituents) will pay the price. If it fits, though, it will become a thing of beauty.

dicom

A DICOMweb Quick Start Guide

I wrote this example on my unofficial DICOMweb documentation page, but I felt it would be warranted to include it here after my last post. As an integrator, I find that this one-pager sort of example, with relevant use case, is very helpful. Granted, when one considers conformance and adherence to standards, referring to just an example like mine won’t be very helpful – but in order to “get into” the mindset of RESTful image access, to level set what will be required for integration – this is immensely helpful, and I’d argue to be a requirement in order to drive developer adoption. This sort of “quick start” documentation is prevalent in many of the most successful APIs – for example, have a look at Facebook’s quick start guide. It is a differentiator. Anyhow, on to the example.

By the way, at the bottom I will include references to the RESTful standards in DICOM. Some of the content (especially the actual data responses) come from that text before I modified them for the example.

DICOMweb Quick Start Guide

An oncologist, Karen Smith, is seeing patients in her clinic, and would like background on the patients she is seeing today. Her first patient of the day, Alex Thompson. has arrived. She launches her imaging software, and makes a query on Alex using his last name. Her imaging software makes a QIDO-RS query at the study level, to retrieve a list of studies for Alex. She finds one result, a CT of the abdomen, and decides she would like to view it. She launches it in her imaging software, which makes a WADO-RS query to download the images for the study. Once downloaded, Karen views the images, and decides to photograph a suspicious lesion. She captures a picture, and uploads it into her imaging software. The imaging software then makes aSTOW-RS request to store the image against the imaging repository.

QIDO-RS Query

Corresponding to “Karen’s imaging software makes a QIDO-RS query at the study level, to retrieve a list of studies for Alex”, this step demonstrates how the Imaging Software constructs a URL for this query:

GET https://dicomweb.myhospital.com/studies/?00100010=THOMPSON&includefield=00081030
Accept: application/json

Which returns the following JSON response:

[
	{
	    "00080005": {
	        "vr": "CS",
	        "Value": [
	            "ISO_IR192"
	        ]
	    },
	    "00080020": {
	        "vr": "DT",
	        "Value": [
	            "20130409"
	        ]
	    },
	    "00080030": {
	        "vr": "TM",
	        "Value": [
	            "131600.0000"
	        ]
	    },
	    "00080050": {
	        "vr": "SH",
	        "Value": [
	            "11235813"
	        ]
	    },
	    "00080056": {
	        "vr": "CS",
	        "Value": [
	            "ONLINE"
	        ]
	    },
	    "00080061": {
	        "vr": "CS",
	        "Value": [
	            "CT"
	        ]
	    },
	    "00080130": {
	        "vr": "LO",
	        "Value": [
	            "Abdomen CT"
	        ]
	    },
	    "00080090": {
	        "vr": "PN",
	        "Value": [
	            {
	                "Alphabetic": {
	                    "Family": [
	                        "SMITH"
	                    ],
	                    "Given": [
	                        "KAREN"
	                    ]
	                }
	            }
	        ]
	    },
	    "00100010": {
	        "vr": "PN",
	        "Value": [
	            {
	                "Alphabetic": {
	                    "Family": [
	                        "THOMPSON"
	                    ],
	                    "Given": [
	                        "ALEX"
	                    ]
	                }
	            }
	        ]
	    },
	    "00100020": {
	        "vr": "LO",
	        "Value": [
	            "12345"
	        ]
	    },
	    "00100030": {
	        "vr": "DT",
	        "Value": [
	            "19670701"
	        ]
	    },
	    "00100040": {
	        "vr": "CS",
	        "Value": [
	            "M"
	        ]
	    },
	    "0020000D": {
	        "vr": "UI",
	        "Value": [
	            "1.2.392.200036.9116.2.2.2.1762893313.1029997326.945873"
	        ]
	    },
	    "00200010": {
	        "vr": "SH",
	        "Value": [
	            "11235813"
	        ]
	    },
	    "00201206": {
	        "vr": "IS",
	        "Value": [
	            4
	        ]
	    },
	    "00201208": {
	        "vr": "IS",
	        "Value": [
	            942
	        ]
	    }
	    "00081190": {
	        "vr": "UT",
	        "Value": [
	            "https://dicomweb.myhospital.com/studies/1.2.392.200036.9116.2.2.2.1762893313.1029997326.945873"
	        ]
	    }
	}
]

By the way, you’ll note that the JSON tag names are in a numbered format. This is called a DICOM attribute tag, and it is in hexadecimal. I created a handy JSON lookup object here.

WADO-RS Query

Corresponding to “Karen launches it in her imaging software, which makes a WADO-RS query to download the images for the study”, this step demonstrates how the Imaging Software constructs a URL for this query: (using the URL passed from the QIDO-RS query):

GET https://dicomweb.myhospital.com/studies/1.2.392.200036.9116.2.2.2.1762893313.1029997326.945873
Accept: multipart/related; type=image/jpeg

Which returns a multipart response of JPEGs representing each image in the study.

STOW-RS Query

Corresponding to “the imaging software then makes a STOW-RS request to store the image against the imaging repository.”, this step demonstrates how the Imaging Software constructs a URL for this query.

POST https://dicomweb.myhospital.com/studies/2.25.329800735698586629295641978511506172918 
Content-Type: multipart/related; type=application/dicom+xml; boundary=MESSAGEBOUNDARY

--MESSAGEBOUNDARY
Content-Type: application/dicom+xml

<?xml version="1.0" encoding="UTF-8"?>
<NativeDicomModel>
	<DicomAttribute Tag="00080020" VR="DT" Keyword="StudyDate">
		<Value number="1">20130409</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080030" VR="TM" Keyword="StudyTime">
		<Value number="1">131600.0000</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080050" VR="CS" Keyword="AccessionNumber">
		<Value number="1">98765</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080056" VR="CS" Keyword="InstanceAvailability">
		<Value number="1"ONLINE</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080061" VR="CS" Keyword="ModalitiesInStudy">
		<Value number="1">XC</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080090" VR="PN" Keyword="ReferringPhysiciansName">
		<PersonName number="1">
			<SingleByte>
				<FamilyName>SMITH</FamilyName> 
				<GivenName>KAREN</GivenName>
			</SingleByte>
		</PersonName>
	</DicomAttribute>
	<DicomAttribute Tag="00100010" VR="PN" Keyword="PatientName">
		<PersonName number="1">
			<SingleByte>
				<FamilyName>THOMPSON</FamilyName> 
				<GivenName>ALEX</GivenName>
			</SingleByte>
		</PersonName>
	</DicomAttribute>
	<DicomAttribute Tag="00100020" VR="CS" Keyword="PatientID">
		<Value number="1">12345</value>
	</DicomAttribute>
	<DicomAttribute Tag="00100030" VR="DT" Keyword="PatientsBirthDate">
		<Value number="1">19670701</value>
	</DicomAttribute>
	<DicomAttribute Tag="00100040" VR="CS" Keyword="PatientsSex">
		<Value number="1">MALE</value>
	</DicomAttribute>
	<DicomAttribute Tag="00200010" VR="SH" Keyword="StudyID">
		<Value number="1">98765</value>
	</DicomAttribute>
	<DicomAttribute Tag="0020000D" VR="UI" Keyword="StudyInstanceUID">
		<Value number="1">2.25.329800735698586629295641978511506172918</value>
	</DicomAttribute>
	<DicomAttribute Tag="0020000E" VR="UI" Keyword="SeriesInstanceUID">
		<Value number="1">2.25.444800735698586629295641978511506172918</value>
	</DicomAttribute>
	<DicomAttribute Tag="00080018" VR="UI" Keyword="SOPInstanceUID">
		<Value number="1">2.25.555800735698586629295641978511506172918</value>
	</DicomAttribute>
	<DicomAttribute tag="00081150" vr="UI" keyword="ReferencedSOPClassUID">
		<Value number="1">1.2.840.10008.1.2.4.50</Value>
	</DicomAttribute>
	<DicomAttribute Tag="7FE00010" VR="OB" Keyword="PixelData">
		<Value number="1">329800735</value>
	</DicomAttribute>
</NativeDicomModel>

--MESSAGEBOUNDARY
Content-Type: image/jpeg
Content-Location: 329800735

<binary JPG image data>

--MESSAGEBOUNDARY--

Which returns an XML response reporting on the result of storing this image:

200 OK
<?xml version="1.0" encoding="UTF-8"?>
<NativeDicomModel>
	<DicomAttribute Tag="00081199" VR="SQ" keyword="ReferencedSOPSequence">
		<Item number="1">

			<DicomAttribute tag="00081150" vr="UI" keyword="ReferencedSOPClassUID">
				<Value number="1">1.2.840.10008.1.2.4.50</Value>
			</DicomAttribute>
			<DicomAttribute tag="00081155" vr="UI" keyword="ReferencedSOPInstanceUID">
				<Value number="1">2.25.555800735698586629295641978511506172918</Value>
			</DicomAttribute>
			<DicomAttribute tag="00081190" vr="UT" keyword="RetrieveURL">
				<Value number="1">https://dicomweb.myhospital.com/studies/2.25.329800735698586629295641978511506172918/series/2.25.444800735698586629295641978511506172918/instances/2.25.555800735698586629295641978511506172918</Value>
			</DicomAttribute>
		</Item>
	</DicomAttribute>
	<DicomAttribute Tag="00081190" VR="UT" Keyword="RetrieveURL">
		<Value number="1">https://dicomweb.myhospital.com/studies/2.25.329800735698586629295641978511506172918</value>
	</DicomAttribute>
</NativeDicomModel>

References