Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

...

Design Thoughts and Questions

How much Hypermedia?

The API we are going to expose in our 1.0 pass at web services is entirely about doing CRUD on a data store. We're not dealing with any application flow or business logic. As such we will aim for level "2.5" of the Richardson Maturity Model (described by Martin Fowler here) and ignore the idea of embedding state transitions in our resources. Is this a mistake?. Resources will include links to URIs of other resources, but not phrased as "rel"s. (We want to support json, we and we're not describing meaningful business-process in those links.)

We will allow Spring to automatically transform our results to json or xml depending on the HTTP request. This probably prevents us from having a proper DAP or XSD. 

Is this a mistake?

Standard CRUD pattern as applied to our API

We'll be following (mostly) standard practice with HTTP methods for domain object CRUD:

...

  • success: 201 CREATED with Location: "uri-of-created-resource", content: default representation of created object
  • failure: 400 or 500

...

Action

URI

Outcomes

Search

GET /ws/rest/patient?q=name+or+identifier

success

...

= 200 OK with a list of minimal patient representations as content

Create

POST /ws/rest/patient

success = 201 CREATED; Location: "uri-of-resource", content: default rep of created patient
failure

...

= 400 or 500

Retrieve

...

GET /ws/rest/patient/uuid.json

success: 200 OK

...

; content: default json representation of patient
no patient found for given uuid

...

= 404 NOT FOUND

...

  • like above, but a "full" representation (e.g. also containing a list of encounters)

...

Update

POST /ws/rest/patient/uuid

...


request content

...

= only properties

...

you want to

...

update, e.g.

...


{ "birthdate": "1978-05-24", "birthdateEstimated": false }

...


success

...

= 204 NO CONTENT
no patient found for given uuid

...

= 404 NOT FOUND
failure

...

= 400 or 500

Soft Delete

...

DELETE /ws/rest/patient/uuid

success

...

= 204 NO CONTENT

...


already soft deleted = 204 NO CONTENT
no patient found for given uuid

...

= 404 NOT FOUND
failure

...

= 500

Hard Delete

...

DELETE /ws/rest/patient/uuid?purge=true

...

success

...

= 204 NO CONTENT
no patient found for given uuid

...

= 204 NO CONTENT

...


failure

...

= 400 or 500

...

Questions:

  • Is it okay to use a POST to update only parts of a resource (instead of PUT with the complete object)?
  • Is there a better way to expose the rarely-used Hard Delete method?

Multiple representations (minimum, default, full, ...)

Our domain objects are often large (e.g. a patient and all their encounters and clinical observations), and contain lots of rarely-interesting book-keeping information (creator, date created, etc), and we expect many clients to be bandwidth-limited. Therefore we want to allow clients to fetch different representations -sized versions of any resource. For example:

URI

Content

/ws/rest/patient/uuid

...

default representation (not including audit info,

...

including the patient's preferred name

...

but not aliases, etc)

/ws/rest/patient/uuid

...

?v=full

...


representation including all audit info, all names, all

...

historic addresses, etc.

/ws/rest/patient/uuid

...

?v=fullwithencounters

...


full

...

representation plus

...

 summaries of all the patient's encounters (this

...

can get very big)

/ws/rest/patient/uuid?v=ref

minimal representation, just containing a displayable string, the uri of the default rep, and the uuid

Question: Is this appropriate? If so, what's the right terminology for this? If not, what alternate approach should we be taking?

We also introduce the idea Usually the default representation of a resource will include "Ref", which is a minimal representation of any object, just containing its uuid, a displayable string, and a URI link to the full resource. So for example if I fetch the default representation of a patient, I might get this:

...

ref"-sized versions of other resources, while the full representation includes "default"-sized versions of other resources. For example a patient would include

default

full

{
"personAddress" : {
"uuid" : "3350d0b5-821c-4e5e-ad1d-a9bce331e118",

...


"display"

...

:

...

"1050

...

Wishard

...

Blvd.,

...

RG5,

...

Indianapolis,

...

IN",

...


"uri"

...

:

...

"http://.../ws/rest/person/puuid/

...

address/3350d0b5-821c-4e5e-ad1d-a9bce331e118"

...


},

...


/

...

*

...

more

...

stuff

...

*/

...


}

...

Whereas if I fetch the full representation of a patient I might get:

...

{
"personAddress"

...

:

...

{

...


"uuid"

...

:

...

"3350d0b5-821c-4e5e-ad1d-a9bce331e118",

...


"address1"

...

:

...

"1050

...

Wishard

...

Blvd.",

...


"address2"

...

:

...

"RG5",

...


"cityVillage"

...

:

...

"Indianapolis",

...


"stateProvince"

...

:

...

"IN",

...


"uri"

...

:

...

"http://.../ws/rest/person/puuid/

...

address/3350d0b5-821c-4e5e-ad1d-a9bce331e118"

...


},

...


/

...

*

...

more

...

stuff

...

*/

...

A more relevant example of this is that an encounter might contain 100 observations. The default encounter representation would just include a displayable "weight = 70", whereas the full encounter representation would include full details about the "weight" question (e.g. that it's unit is "kg", its range is 0-250, etc).

...

We also have the idea of "sub-resources". Take the example of a "Program" (e.g. "Tuberculosis Treatment Program") and a "Program Enrollment" (i.e. a patient may be enrolled in a program from date X to date Y). Our plan is that:

...

For example a patient has identifiers. These sub-resources will not get a top-level uri, rather:

list all identifiers for the given patient

GET /ws/rest/

...

patient/uuid/identifiers

add an identifier to the given patient

POST /ws/rest/patient/

...

uuid/

...

identifiers

fetch/update/delete a specific identifier

GET/POST/DELETE /ws/rest/patient/uuid/

...

identifiers/

...

  • POST /ws/rest/patient/patient-uuid/enrollments  // may omit the patient from the request body
  • POST /ws/rest/program/program-uuid/patients  // may omit the program from the request body
  • POST /ws/rest/enrollment  // must specify both patient and program in the request body

identifier-uuid

We'll lean towards making things top-level resources rather than sub-resources, for example:

  • encounter will be a top-level resource (even though every encounter belongs to a patient) because you might want to search for encounters across different patients, like GET /ws/rest/encounter?from=2011-01-01&to=2011-01-31
  • "enrollment" (e.g. the fact that a patient is enrolled in a program or study) will be top-level, instead of /patient/uuid/enrollments or /program/uuid/patients

Does this sound right?

Authentication

...

We've tried to standardize the way we do CRUD via interfaces (Searchable, Creatable, Retrievable, Updatable, Deletable, Purgeable), and via a base class that helps reflect these interfaces onto our pre-web-service domain objects: DelegatingCrudResource.