This wiki has undergone a migration to Confluence found Here
<meta name="googlebot" content="noindex">

Difference between revisions of "FHIR and GraphQL"

From HL7Wiki
Jump to navigation Jump to search
Line 199: Line 199:
 
  }
 
  }
  
Example: [[http://test.fhir.org/r3/$graphql?query={PatientList(name:"pet"){name{family,given}}} http://test.fhir.org/r3/$graphql?query={PatientList(name:"pet"){name{family,given}}}]]
+
Example: [[http://test.fhir.org/r3/$graphql?query={PatientList(name:%22pet%22){name{family,given}}} http://test.fhir.org/r3/$graphql?query={PatientList(name:%22pet%22){name{family,given}}}]]
  
 
Servers may reject the request if there are too many matches. If they do so, they SHALL return an error indicating that the query could not be fulfilled. (rather than silently filtering the list).
 
Servers may reject the request if there are too many matches. If they do so, they SHALL return an error indicating that the query could not be fulfilled. (rather than silently filtering the list).
Line 237: Line 237:
 
  }
 
  }
  
Example 1: [[http://test.fhir.org/r3/$graphql?query={PatientConnection(name:"pet"){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{name{family,given}}}}} http://test.fhir.org/r3/$graphql?query={PatientConnection(name:"pet"){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{name{family,given}}}}}]]
+
Example 1: [[http://test.fhir.org/r3/$graphql?query={PatientConnection(name:%22pet%22){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{name{family,given}}}}} http://test.fhir.org/r3/$graphql?query={PatientConnection(name:%22pet%22){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{name{family,given}}}}}]]
  
 
Example 2 (cursor): [[http://test.fhir.org/r3/$graphql?query={PatientConnection(){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}} http://test.fhir.org/r3/$graphql?query={PatientConnection(){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}}]]
 
Example 2 (cursor): [[http://test.fhir.org/r3/$graphql?query={PatientConnection(){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}} http://test.fhir.org/r3/$graphql?query={PatientConnection(){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}}]]

Revision as of 23:07, 26 May 2017

this page contains early/provisional notes related to the development of a standard approach for use of graphQL with FHIR.

Note: this page anticipates community agreement around a standard interoperable graphql interface for FHIR servers. There is always other ways to use graphQL with FHIR, which are useful and valid, *and are not prohibited* (if they are other end-points).

For discussion, see [chat.fhir.org]

Goals

The graphql functionality should be described by a schema

Invoking GraphQL

For reasons of consistency with other aspects of FHIR, the standard end points for graphQL are

[base]/$graphql
[base]/[Type]/[id]/$graphql

GraphQL can be invoked by get with a query parameter, or a POST with the graphQL as the body, or a JSON body. (see [[1]].

The mime type of the response is application/json. Other formats are not described by the specification. The mimetype is *not* application/fhir+json - the response is not a FHIR resource (it may look like one closely)

Invocation Context

  • System level ([base]/$graphql) - the query must start by selecting a resource type and some search criteria (see below)
  • Resource Instance level ([base]/[Type]/[id]/$graphql) - the query assumes a single resource is in scope

Data type mappings

Primitive Data types:

  • GraphQL Type | FHIR Type
  • Int | integer, positiveInt, unsignedInt
  • Float | decimal
  • Boolean | boolean
  • ID | id
  • String | everything else


Complex Data Types:

  • Names are unchanged

Error handling

Servers SHALL return an error when fields that do not exist are used, or when arguments, types, directives that are not recognized or supported are encountered

Field Selection

Any FHIR defined field can be used directly e.g. this graphql against the Patient resource (r3)

{ 
 name { text given family } 
}

Example: [http://test.fhir.org/r3/Patient/example/$graphql?query={name{text,given,family}}]

Polymorphic fields are represented by their JSON property name E.g. for Observation.value[x]:

{ 
 valueQuantity { value unit } 
}

Example: [http://test.fhir.org/r3/Observation/example/$graphql?query={valueQuantity{value,unit}}]

Note: This is because the leaf names have to correspond to scalar types, so there is no use selecting all the variants at once

Extensions on primitives: the JSON convention for primitives is observed. e.g. use _[name] for accessing extensions on primitives.

So

{
 birthDate _birthDate { extension {valueDateTime} } 
}

Example: [http://test.fhir.org/r3/Patient/example/$graphql?query={birthDate,_birthDate{extension{valueDateTime}}}]

results in

{
 "birthDate":"2016-05-18",
 "_birthDate":{
   "extension":[{
     "valueDateTime":"2016-05-18T10:28:45Z"
    }]
 }
}

Field Arguments

Primitive fields SHALL not have any arguments at all.

Complex fields may have one of more of the following parameters:

  • "fhirpath" - a fhir path statement selecting which of the subnodes is to be included. e.g.
{ 
 name(fhirpath: "family.exists()") { text given family } 
}

Example [http://test.fhir.org/r3/Patient/example/$graphql?query={name(fhirpath:%22family.exists()%22){text,given,family}}] (as compared to [http://test.fhir.org/r3/Patient/example/$graphql?query={name{text,given,family}}])

  • "[field]" - the name of a sub-property with a specified value that must be matched for the field to be included. e.g.
{ 
 name(use: official) { text given family } 
}

Example: [http://test.fhir.org/r3/Patient/example/$graphql?query={name(use:official){text,given,family}}]

Additional Selectors

Resource references

An object of type reference can have an additional selection "resource". This is an instruction to the server to resolve the reference, and then include the contents of the resource as specified by sub-selections in the property name "resource" (can be aliased).

e.g. On Observation:

{ 
   subject { reference, resource {active} } 
}


The resource selector has two arguments:

  • optional : true | false. (default is false). If the server cannot resolve the reference (e.g. resource does not exist, or user security rights or choices do not permit resource to be seen), and optional is not true, the server returns an error instead of the graph output
  • type : [Resource] - only selects resources of a particular type. Note that this is similar in effect to
{ 
 id
 subject { 
  reference
  resource {
     ...on Patient {
         birthDate
   }
   ...on Practioner {
       practitionerRole {  speciality }
   }
  }
 }  
 code {coding {system code} }
}

Example: [http://test.fhir.org/r3/Observation/example/$graphql?query={id,subject{reference,resource{...on%20Patient{birthDate}...on%20Practioner{practitionerRole{speciality}}}}code{coding{system,code}}}]

but slightly denser:

{ 
 id
 subject { 
  reference
   resource(type : Patient) { birthDate }
   resource(type : Practioner) { practitionerRole {  speciality } }
 }  
 code {coding {system code} }
}

Example: [http://test.fhir.org/r3/Observation/example/$graphql?query={id,subject{reference,resource(type:Patient){birthDate}resource(type:Practioner){practitionerRole{speciality}}}code{coding{system,code}}}]

Clients can use either approach - type selection as a parameter, of using a fragment type condition - the first is shorter while the second aligns with schema

Searching resources

When a GraphQL statement is run at the system level, rather than against a particular resource, the first thing the query must do is select the resource(s) of interest. The logic of this follows the FHIR search API, but reimplements in a graphQL centric way.

There are 3 ways to query, for a single resource, for a simple list of resources, and a full API. For a single resource, the client names the type of the resource, and provides an id:

{ 
  Patient(id: example) { id, active } 
}

This returns a single Patient with the name id. The output from this is a single resource:

{
  "Patient" { 
    "id" : "example",
    "active" : "true",
  }
}

Example: [http://test.fhir.org/r3/$graphql?query={Patient(id:example){id,name{text}}}]

Alternatively, the client can ask for a list of resources. Here, the client simply asks for a list of resources:

{ 
  ConditionList(clinical_status: relapse, patient: example) { id, clinicalStatus } 
}

This is a request to list all the Condition resources that have a status if 'relapsed' for the patient with id = example. The arguments are most of the search parameters defined on the specified resource (see below for the unsupported parameters). Note that the parameter's names are changed by replacing '-' in the name with '_' (for graphql syntax requirements) (FHIR defines parameters starting with _ but will never define search parameters starting with '-'). There is one additional possible argument, "fhirpath" which the server evaluates on all the possible matches.

The output of this is a list:

{
  "ConditionList" [{ 
    "id" : "100",
    "clinicalStatus" : "relapse",
  },{ 
    "id" : "100",
    "clinicalStatus" : "relapse",
  }]
}

Example: [http://test.fhir.org/r3/$graphql?query={PatientList(name:%22pet%22){name{family,given}}}]

Servers may reject the request if there are too many matches. If they do so, they SHALL return an error indicating that the query could not be fulfilled. (rather than silently filtering the list).

The simple list approach does not allow for the management of long lists. To do this, clients are able to request a Connection based approach (based on http://graphql.org/learn/pagination/, but adapted to the existing FHIR Search API).

{ 
  ConditionConnection (clinical_status: active, patient: example) { 
    count offset pagesize
    edges {
      mode, score, resource { id, active }
    }
    first previous next last
  } 
}

The arguments are the same as for the simple List case, with the addition of the special argument 'cursor' (see below). The server returns a connection object that contains information about the search, along with a list of 'edges', one for each match. Each edge has 3 properties: mode, score (match the same properties on Bundle.entry.search) and "resource" which is the actual matches.

{
 "ConditionConnection" : {
   "count": 50,
   "offset" : 0,
   "pageSize" : 25,
   "next" : "45f9ada8-db37-4498-ba7d-75a044668387"
   "edges" : [{
      "resource" : { 
        "id" : "100",
        "clinicalStatus" : "relapse",
      }
  },{ 
    "resource" : {
      "id" : "100",
      "clinicalStatus" : "relapse",
     }
  }]
 }
}

Example 1: [http://test.fhir.org/r3/$graphql?query={PatientConnection(name:%22pet%22){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{name{family,given}}}}}]

Example 2 (cursor): [http://test.fhir.org/r3/$graphql?query={PatientConnection(){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}}]

The client can follow up on the first/previous/next/last links using the argument 'cursor':

{ 
  ConditionConnection (cursor : "45f9ada8-db37-4498-ba7d-75a044668387") { 
    count offset pagesize
    edges {
      id, clinicalStatus
    }
    first previous next last
  } 
}

Example: ["5b0719b5-0f01-442e-9576-5b0514c19a:50"){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}} http://test.fhir.org/r3/$graphql?query={PatientConnection(cursor:"5b0719b5-0f01-442e-9576-5b0514c19a:50"){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}}] (note, though, that to make this one work, you have to replace the cursor token with one that you got from the link above

The client can not change the parameters for the search (and should not specify them) when providing a cursor. The value of the token is opaque to the client, and only understood by the server.

The following search parameters are not supported on the GraphQL List and Connection interfaces: _id, _include, _revinclude, _contained, _containedType.

Note that when using the fhirpath argument with Connection based searches, the server may choose to apply the fhirpath filter after the search paging is performed, so that individual pages may be shorter (or empty).


Reverse References

It's also possible to use search is a special mode, doing reverse lookups - e.g. list all the resources that refer to this resource. An example of this use is to look up a patient, and also retrieve all the Condition resources for the patient.

This is a special case of search, above, but with an additional mandatory parameter "_reference". For example:

{
  name { [some fields] }
  ConditionList(_reference: patient) {
   [some fields from Condition]
  }
}

There must be at least the argument "_reference" which identifies which of the search parameters for the target resource is used to match the resource that has focus. In addition, there may be other arguments as defined above in search (except that the "id" argument is prohibited here as non-sensical)

The response for the query above would be

{

 "name: [ [some fields] ],
 "ConditionList" : [
   { [some fields from matching Condition resource] }
 ]

}

Example: [http://test.fhir.org/r3/Patient/example/$graphql?query={name{family,given}ConditionList(_reference:patient){id,clinicalStatus}}]

The "connection" based search option is also supported, as described above, with the addition of _reference. If the client wishes to pursue any of the cursor based links in the graphQL results it asks for back, then it initiates this a new separate query as defined above, rather than repeating the nested query. I.e. the 'cursor' argument is prohibited, except at the root of query.

Example: [http://test.fhir.org/r3/Patient/example/$graphql?query={name{family,given}ConditionConnection(_reference:patient){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{...onCondition{resourceType,id,clinicalStatus}}}}}]

open questions

  • need for directives?
  • generate schema
  • mutations