8.0.1FHIR Storage (MongoDB) Module

 

The FHIR Storage (MongoDB) module is responsible for resource storage and retrieval. All resources are stored in a non-relational database and unlike the relational module, no indexes are created to help in finding them.

8.0.2Preparing for a New Installation

 

An external MongoDB instance should be created already. You will need the connection url and credential details for setting up the module.

You will also require a relational database with a user created. See the Platform Requirements page for information on supported database platforms.

8.0.3Creating the Module

 

When creating a FHIR Storage (MongoDB) module, you must first choose which version of FHIR it will support. Currently, only DSTU3 and R4 is supported.

8.0.4MongoDB Document Schema

 

The Smile CDR MongoDB design uses two collections per resource type:

  • The Resource Type Collection stores a single document per FHIR resource. Each document contains both the raw contents of the document (encoded as JSON) but also has a number of special fields which store indexed values as defined by the server Search Parameters. FHIR servers use the SearchParameter resource to define which fields within a resource will be indexed and available for clients to search on. See Search Parameters for information on how to define and manage search parameters.

  • The History Type Collection stores previous revisions of resources in order to support the FHIR history function.

The following is a simple example of a document that will be stored in the Resource Type Collection:

{
  "_id": {
    "$oid": "5e737aaa741d430a9a74dc8d"
  },
  "resource": {
    "resourceType": "Patient",
    "id": "ed558ec1-b3a3-4744-98b7-46584227309c",
    "meta": {
      "versionId": "1",
      "lastUpdated": "2020-03-19T09:59:06.760-04:00"
    },
    "identifier": [ {
        "system": "https://example.com/identifiers/alt_id",
        "value": "12345"
    } ],
    "name": [ {
        "family": "Simpson",
        "given": [ "Homer" ]
    } ],
    "birthDate": "1951-05-12"
  },
  "identifier": [
    "https://example.com/identifiers/alt_id|12345"
  ],
  "name": [ "HOMER", "SIMPSON" ],
  "birthdate": {
    "start": {
      "$date": {
        "$numberLong": "-588283200000"
      }
    },
    "end": {
      "$date": {
        "$numberLong": "-588283200000"
      }
    }
  }
}

Note that the document has a child element named resource that contains the raw contents of the resource. All other elements at the root level represent search parameters defined in the database. For example:

  • String Search Parameters: The Patient.name values in the resource body have been copied (in normalized form according to FHIR normalization rules) to the name array in the document.

  • Date Search Parameters: The Patient.birthDate value has been copied to a field called birthdate (note that the search parameter name has a different capitalization from the element name in the resource). Dates are always indexed as a range in order to support period searches.

  • Token Search Parameters: The Patient.identifier value has been copied to a field called identifier, and has been encoded using the FHIR token parameter format of system|value.

8.0.5MongoDB Uplift Search Parameter Extensions

 

Smile CDR defines several extensions that can be placed on a search parameter in order to "uplift" specific values within resources to the root of the document in defined elements. This is helpful in situations where you wish to define custom indexes to support very high throughput scenarios.

8.0.6Uplifting Tokens

 

Consider the following resource:

{
   "resourceType": "Patient",
   "identifier": [ {
      "system": "https://example.com/identifiers/alt_id",
      "value": "12345"
   } ]
}

By default, search parameter index fields will be extracted and placed in elements at the root of the document for easy access.

A custom extension can be added however to the SearchParameter that instructs the system to use a custom root-level element to store the value of the token. This simplifies the creation of custom indexes in some cases.

Consider the following SearchParameter resource:

{
  "resourceType": "SearchParameter",
  "id": "patient-identifier",
  "extension": [ {
    "url": "https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-token",
    "extension": [ {
      "url": "system",
      "valueUri": "https://example.com/identifiers/alt_id"
    }, {
      "url": "token-to-element",
      "valueUri": "my_alt_id"
    } ]
  } ],
  "status": "active",
  "description": "A patient identifier",
  "code": "identifier",
  "base": [ "Patient" ],
  "type": "token",
  "expression": "Patient.identifier"
}

A search parameter with the URL https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-token has been added to the resource. It has two child extensions:

  • A child extension with URL system refers to the identifier system to place at the root of the document.

  • A child extension with URL token-to-element refers to the name of the element to place at the root of the document with the identifier value

Therefore, given the SearchParameter resource above, when the Patient above is created, the resulting document in MongoDB will resemble the following:

{
  "_id": {
    "$oid": "5e73b755ffdafa22d61f49dd"
  },
  "resource": {
    "resourceType": "Patient",
    "id": "3744f105-4043-47d7-8b49-1020c8020144",
    "identifier": [ {
        "system": "https://example.com/identifiers/alt_id",
        "value": "12345"
    } ]
  },
  "my_alt_id": "12345"
}

8.0.6.1Overlapping Search Parameter

Smile CDR defines a search parameter which contains the same code and base resources with existing search parameters as an overlapping search parameter. Any attempt to create such search parameters will be rejected with the following error message: "Can't process submitted SearchParameter as it is overlapping an existing one". In this case, removal of the target base resource from the existing search parameter base list is required before creating the new definition.

See the following steps as an example:

  • Get information of the standard search parameter that contains the target code and base resource. For example, the following search returns all SearchParameter resources that contain "patient" as the code and "Observation" as the base. http://hapi.fhir.org/baseR4/SearchParameter?code=patient&base=Observation

  • Update the SearchParameter resource that returned from above to remove the target base from the base list. The following example shows a JSONPatch being used to update an SearchParameter base list:

Note that all the other base resources should remain the same except for the target base.

PATCH SearchParameter/theStandardSearchParameterID
Content-Type: application/json-patch+json

[
 { 
   "op": "replace", 
   "path": "/base", 
   "value": [
      "RiskAssessment",
      "CareTeam",
      "MedicationDispense",
      "DeviceUseStatement",
      "SupplyDelivery",
      "ClinicalImpression",
      "MedicationAdministration"
    ]
  }
]
  • Lastly, the new search parameter can be created.

8.0.7Uplifting Reference Chains

 

The FHIR standard defines a feature called reference chaining, which allows searches that effectively perform a join across resource boundaries. For example the following search returns all Observation resources that reference a Patient, where the Patient has a specific identifier (i.e. the value in Patient.identifier, not the resource ID). http://hapi.fhir.org/baseR4/Observation?patient.identifier=http://example.com/ident|12345

This style of query can be helpful for clients, since it avoids the need for clients to know the FHIR resource IDs for relevant resources and instead allows business identifiers and other real-world attributes to be used.

In the relational database world, this style of search is performed using SQL JOIN statements. In the MongoDB world however, joining across collections is an anti-pattern to be avoided.

Instead, in Smile CDR MongoDB storage, allowable chained values must be explicitly declared up front via an extension on the SearchParameter. When a resource is created/updated, if it allows any chained values, these will be copied to the resource being written.

For example, consider a Patient resource with the identifier { "system":"http://example.com/ident", "value":12345" }. Now suppose an Observation resource is created with a subject reference to that Patient. If the chain patient.identifier is supported on the Patient resource, the identifier value will be copied into the Observation document when it is created (or updated).

This has several implications:

  • It means that the patient.identifier query can be performed efficiently, as the value of the chained parameter is copied into the Observation collection document and can be indexed and searched without requiring a join.

  • It also means that if the identifier value of the relevant Patient resource changes later, this change will not be reflected in the Observation resoure. In other words, this feature should only be used in cases where the target value of the chain is not expected to change.

To create a search parameter with an uplifted chain, see the following example:

{
  "resourceType": "SearchParameter",
  "id": "observation-patient",
  "extension": [ {
    "url": "https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-refchain",
    "extension": [ {
      "url": "code",
      "valueCode": "identifier"
    }, {
      "url": "element-name",
      "valueCode": "patient-identifier"
    } ]
  } ],
  "status": "active",
  "description": "The subject that the observation is about (if patient)",
  "code": "patient",
  "base": [ "Observation" ],
  "type": "reference",
  "expression": "Observation.subject.where(resolve() is Patient)",
  "target": [ "Patient" ]
}

An example Observation collection document produced by a system with the SearchParameter above is shown below. Note the patient-identifier index element, which is created as a result of the chain.

{
  "_id": {
    "$oid": "5e752ebe0de3e83210f51b69"
  },
  "resource": {
    "resourceType": "Observation",
    "subject": {
      "reference": "Patient/324342fc-cab1-4374-a58e-15b56e92b702"
    },
    "id": "ae0ee3a4-154e-41cf-b4f9-b891829c0847",
    "meta": {
      "versionId": "1",
      "lastUpdated": "2020-03-20T16:59:42.360-04:00"
    }
  },
  "subject": [
    "Patient/324342fc-cab1-4374-a58e-15b56e92b702"
  ],
  "patient": [
    "Patient/324342fc-cab1-4374-a58e-15b56e92b702"
  ],
  "patient-identifier": [
    "https://example.com/identifiers/alt_id|12345"
  ]
}

Note the extension with the URL https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-refchain. It has two children:

  • The code contains the name of the search parameter on the target resource. So, in the example above it refers to the identifier search parameter on the Patient resource.

  • The element-name determines the name of the element in the source collection (the Observation collection in the example above) where the index value will be placed.

  • Currently, only Search Parameters with a code which corresponds to a Search Parameter of type TOKEN or STRING is allowed. e.g. Patient.identifier for token, or Patient.given for string.

8.0.7.1Uplifting Reference Chains with Token Uplift

Uplifted Reference Chains can be combined with Uplifted Tokens.

Consider the following SearchParameter resource:

{
  "resourceType": "SearchParameter",
  "id": "observation-patient",
  "extension": [ {
    "url": "https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-refchain",
    "extension": [ {
      "url": "code",
      "valueCode": "identifier"
    }, {
      "url": "element-name",
      "valueCode": "patient-identifier"
    } ]
  }, {
    "url": "https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-token",
    "extension": [ {
      "url": "system",
      "valueUri": "https://example.com/identifiers/alt_id"
    }, {
      "url": "token-to-element",
      "valueCode": "alt_id"
    } ]
  } ],
  "status": "active",
  "description": "The subject that the observation is about (if patient)",
  "code": "patient",
  "base": [ "Observation" ],
  "type": "reference",
  "expression": "Observation.subject.where(resolve() is Patient)",
  "target": [ "Patient" ]
}

With this in place, the following document will be produced:

{
  "resource": {
    "resourceType": "Observation",
    "subject": {
      "reference": "Patient/68794ee4-3826-4496-bd06-412895f82fb2"
    },
    "id": "092b5c5d-e5c9-4f1c-8b9d-2fa5f427d07c",
    "meta": {
      "versionId": "1",
      "lastUpdated": "2020-03-20T16:59:42.789-04:00"
    }
  },
  "patient": [
    "Patient/68794ee4-3826-4496-bd06-412895f82fb2"
  ],
  "alt_id": "12345",
  "subject": [
    "Patient/68794ee4-3826-4496-bd06-412895f82fb2"
  ]
}

8.0.8Configuration

 

8.0.8.1Database Configuration

Once you have selected your module type, the first set of configuration options will be the relational database configuration itself. A complete reference of options is found on the Database configuration page.

Most importantly, you will need to configure the following properties:

  • Database Connection URL

    Note that while it is possible to embed credentials in the URL according the MongoDB Connection String Format, we strongly recommend placing these separately in the Username and Password properties. Placing a password embedded in the connection URL can cause it to be logged and otherwise disclosed to administrative users, which is a security concern.

    An example MongoDB URL is shown below:

    mongodb://mongoserver.example.com:27017?authSource=admin
    
  • Username

  • Password

    Any extended characters in the username and password fields do not need to be escaped or encoded.

8.0.8.2Reference

The configuration options available for this module type are as follows: