Web Services API (Design Page)

This was the project page for the Webservices.rest Module while it was being created. See that page for up-to-date documentation

Conventions

Because there are so many options when creating REST urls, we have laid out a set of conventions that all REST developers should follow.  This will keep our web service api looking neat and uniform across all different types of objects and modules.

  • For resource,
    • GET /ws/rest/resource?q=query = search
    • GET /ws/rest/resource/uuid = retrieve
    • POST /ws/rest/resource = create
    • POST /ws/rest/resource/uuid = update the fields specified in body content
    • PUT = replace value of entire object (we don't plan to use this for now)
    • DELETE /ws/rest/resource/uuid = void for data, retire for metadata
    • DELETE /ws/rest/resource/uuid?purge=true = purge
  • Create and Update should not use query string params, but rather send instructions in the request body
  • Do not put verbs into the URL. Represent this idea with sub-resources.
    • For example, instead of addNameToPerson we POST to person/uuid/names.
  • URLS:
    • Must be prefixed with /ws/rest/
    • All lowercase
    • No special characters (e.g. no spaces or underscores)
    • Hyphens are ok if absolutely necessary
    • No extensions allowed (extension will be used to specify json or xml content)
  • Sub-resources (i.e. things like personname or patientidentifier which belong to a parent resource) should have their URIs inside the URI of their parent (like /ws/rest/person/uuid/names/nameuuid)
  • Domain objects that are separately managed get their own URI. (e.g. /ws/rest/enrollment/uuid instead of /ws/rest/program/uuid/enrollments/uuid)
  • Hide (aka don't expose) "helper" classes as must as possible (e.g. web service clients should never see ConceptSet, etc)
  • Resource names should usually be the same as the domain objects they represent, but they may differ if the domain object name is confusing.
    • For example org.openmrs.PatientProgram will be /ws/rest/enrollment

Domain Objects

Rather than creating lightweight & heavyweight versions of our objects, trying to anticipate which properties might be needed by web service clients, we imagine creating a single version of each domain object with optional filling of some properties.

When creating objects, there will be required properties and optional properties.  Required properties must be provided in order to create the object.

When requesting objects, there will be default properties that are always fetched vs. extra properties that are only fetched when specifically requested.  "Extra" properties will be missing/null if not requested. (See below)

Object References ("Ref")

When an object is a subresource on another object (e.g. conceptDatatype property on Concept object) the full ConceptDatatype object is not returned.  Instead a "Ref" kind of class with String properties for uuid, uri, and a display fills the concept.conceptDatatype property.  The ref looks like this:

Ref example
concept.conceptDatatype \->
{
display: "Numeric",
uuid: "8d4a4488-c2cc-11de-8d13-0010c6dffd0f",
uri: "/ws/rest/conceptdatatype/8d4a4488-c2cc-11de-8d13-0010c6dffd0f"
}

To fetch the full ConceptDatatype data, a second call to /ws/rest/conceptdatatype/8d4a4488-c2cc-11de-8d13-0010c6dffd0f is needed.

When saving a "concept" object, the conceptDatatype property can be a ref or simply the uuid. For some metadata, the "name" is unique across all active metadata, so that can also be used in place of the uuid when saving (POST or PUT).

Retrieving optional properties

Not all properties are returned when a call is performed.  There is a set of default properties for each resource/object.  The default properties returned should cover the 90% use-case. This keeps the bandwidth use to a minimum.

If a webservice user wants different properties returned, they must specify a different type of representation.  The request parameter "v" is used to specify this.

If all properties are desired, use the ?v=full representation.  Adding ?v=default is invalid, simply leave off the parameter.

Not yet implemented: If you want only certain attributes returned, then ask for custom(prop1, prop2, prop3, subprop.prop1).  If a custom property is requested that does not exist or is not allowed, that property is silently ignored.

Adding A Web Service

See this page on Adding a Web Service Step by Step Guide for Core Developers (REST 1.x)

Adding a Web Service to Your Module

See this page on Adding a Web Service Step by Step Guide for Module Developers (REST 1.x)

Concept Example

Concept Properties

Concept object
// required for creation
Ref conceptDatatype
Ref conceptClass
String name // primary name
String locale // locale of primary name

// always returned (in addition to above)
String uuid
boolean retired

// optional (null unless requested)
String description
AuditInfo auditInfo
List<Ref> names
List<Ref> shortNames
List<Ref> descriptions
List<Ref> setMembers
List<ConceptAnswer> answers

// intentionally not exposed: conceptId, isSet, version
Concept object in JSON
{
conceptDatatype: {display:"Numeric", uuid: "2348321402134", link:"/ws/rest/conceptdatatype/2348321402134"},
conceptClass: {display:"Finding", uuid: "nhj32fdnm2-23nmd32", link:"/ws/rest/conceptdatatype/2348321402134"},
name: "Weight (KG)",
locale: "en_US",
uuid: 3219fnmj23r9nmfd,
retired: false,
description: "A patient's weight as determined in kilograms", /\* (only returned if asked for) \*/
names: \[{display:"Weight (KG)", uuid: "3n23dnk23r", link:"ws/rest/concept/3219fnmj23r9nmfd/name/3n23dnk23r"}, {display:"Uzito (KG)", uuid: "39i324i8abf-234", link:"ws/rest/concept/3219fnmj23r9nmfd/name/39i324i8abf-234"}\],
descriptions: \[\],
setMembers: \[{...},{...},{...}\]
}
AuditInfo
Ref creator
Date dateCreated
Ref changedBy
Date dateChanged
Ref retiredBy
Date dateRetired
String retireReason
Ref voidedBy
Date dateVoided
String voidReason
ConceptAnswer
//properties
String answerType // "concept", "drug", "conceptSet", or "conceptClass"
Ref answer

// currently answers are only allowed to be concept or drug, but we want to be future-proof
// example usage
if (question.answers[0].answerType == "set") {
Concept\[\] members = api.getConceptSetMembers(question.answers[0].answer.uuid);
// do something with each
}

Standard CRUD, applied to Concept

Create a concept
POST to /ws/rest/concept
with content:
conceptDatatype: Numeric
conceptClass: test
name: HEMOGLOBIN
locale: en
description: "Stuff in the blood" (<-- this is an optional include)
returns:
success = 201 CREATED
location: http://.../ws/rest/concept/newconceptuuid
content: default rep of created concept

(on failure returns either 400 or 500 error codes)

Read a concept
GET to /ws/rest/concept?fully-specified-name=HEMOGLOBIN&locale=en
or
GET to /ws/rest/concept/2dw23-z113-234234-2sdfr3
or
GET to /ws/rest/concept?partial-name=HEMOGL&locale=en (<-- gets multiple concepts)

Update a concept
PUT to /ws/rest/concept/2dw23-z113-234234-2sdfr3
with content:
conceptClass: "Symptom"
description: "a new description"
returns:
success = 204 NO CONTENT
or no concept found for given uuid = 404 NOT FOUND error code returned
or on failure = 400 or 500 error code returned

Delete a concept
DELETE to /ws/rest/concept/2dw23-z113-234234-2sdfr3?reason=because
(delete calls do not have bodies)
returns:success = 204 NO CONTENT

Purge a concept
DELETE to /ws/rest/concept/2dw23-z113-234234-2sdfr3?purge=true
returns:
success = 204 NO CONTENT

Fuzzy/partial name search
GET to /ws/rest/concept?partial-name=weig&locale=en&concept-class=Symptom
returns:
success = 200 OK with a list of minimal patient representations as content

Find by fully specified name and locale
GET to /ws/rest/concept?fully-specified-namethe%20fully%20specified%20name?locale=en
returns:
success: 200 OK
content: default json representation of concept
no concept found for given uuid = 404 NOT FOUND

Other service calls

GET to /ws/rest/concept/thesetuuid/setmembers

GET to /ws/rest/concept/theconceptquestionuuid/answers

GET to /ws/rest/concept?mapping=sourceuuid&code=thecodeinthemapping

Collection properties of Concept

Add a name to a concept
POST to /ws/rest/concept/theconceptsuuid/name
with content:
name:a name
locale:en_US
preferred:true
type:Fully Specified
...other name properties optional

Remove a name from a concept (void the name)
DELETE to /ws/rest/concept/theconceptuuid/name/thenameuuid?reason=thereason

Add a short name to a concept
POST to /ws/rest/concept/theconceptuuid/shortname
with request body content:
name:a short name
locale:en_US

Remove a short name from a concept:
(same options as Add Name To Concept/Remove Name From Concept. The point is that short name is a separate property, not an alternate name)

Edit an existing name (only props sent will get changed)
PUT to /ws/rest/concept/theconceptuuid/name/thenameuuid
localePreferred:true
locale:en_US
name:a modified name

Add member to set needs work
PUT to /ws/rest/concept/theconceptuuid/members
beforeOtherMember:othermemberuuid (assigns isSet=true on the set if not already specified. if beforeOtherMember is null, thememberuuid is put at the end of the list)

Add many members to a set
POST to /ws/rest/concept/thesetconceptuuid/members
memberUuids:2342349323, 2349234923420394, snmcnmw4923409234234, 2349284347nhj3
(Replaces all with the given order)

Remove member from set
DELETE to /ws/rest/concept/conceptuuid/members/memberuuid

Concept subclasses (ConceptNumeric, ConceptComplex)

Are treated as optional properties on the concept class:
GET to /ws/rest/concept/349882349823
with representation: v=custom(default,conceptNumeric)
will return:
name: Weight (KG)
locale: en
retired: false
conceptNumeric:
   { hiAbsolute: 1000
     hiCritical: 150
     ....
     units: kg
     precise: false
  }

User Stories for 1.0

See Web Service 1.0 User Stories