This page is part of the Da Vinci Health Record Exchange (v1.0.0: STU1) based on FHIR R4. This is the current published version in its permanent home (it will always be available at this URL). For a full list of available versions, see the Directory of published versions
Page standards status: Informative |
Contents:
This content will be moving into the FHIR core specification as part of R5.
FHIR offers numerous architectural approaches for sharing data between systems. Each approach has pros and cons. The most appropriate approach depends on the circumstances under which data is exchanged. This page provides an overview of each approach, with pros, cons and a decision tree to help guide choices about which approach best suits a specific set of circumstances.
The recommendations expressed here reflect the general beliefs of the community based on experience to date. Even so, there are other considerations that impact implementation choices, including legacy infrastructure, the existing capabilities of communication partners, etc. Following the guidance here is not required to claim FHIR conformance but adhering to these recommendations is likely to result in lower long-term implementation costs, more interoperability partners, etc..
Not all the options listed here were defined in early versions of FHIR and it is likely that new exchange alternatives will continue to be identified by the FHIR community over time. As these approaches become more broadly used, more standardized and experience accumulates within the community this documentation will continue to be updated. Feedback on the advice offered here is welcome - simply use the Propose a Change button at the bottom of any FHIR specification page.
This page focuses on data access mechanisms that are either defined within the core FHIR specification or are otherwise implementable using the interfaces defined by the FHIR specification without requiring any additional standardization. Additional access mechanisms may be defined in other FHIR implementation guides or within other specifications. There is, for example, a community effort outside the HL7 standardization process to allow a relational query interface to access FHIR information (see SQL-on-FHIR).
This guidance is provided as part of the HRex specification to help future Da Vinci implementation guides select an architectural approach. To encourage consistency across implementation guides, each guide should identify the path(s) taken when navigating the tree to support their choices. This will also provide a basis for justifying situations where approaches vary. (Note: Implementation guides SHOULD provide an explanation of the considerations (whether drawn from this document or otherwise) that drove the selection of the interoperability approach chosen for the guide and the use cases it is trying to solve.)
The guidance provided in this section is focused on the choice of technical exchange mechanism. It does not cover higher level design decisions, such as what sorts of exchanges should occur, what parties they should occur between, what points in the workflow the exchange should happen in, etc. These decisions should be made prior to trying to apply the guidance here. Also, Implementation guides SHOULD provide an explanation of the considerations (whether drawn from this document or otherwise) that drove the selection of the interoperability approach chosen for the guide and the use cases it is trying to solve.
This section focuses on the technical alternatives that enable exchange between data sources and data consumers. All such exchanges, and the decisions about the mechanisms used, will also need to take into account the creation of and adherence to regulations, contracts, business arrangements and other conventions and ensure that the information shared is 'appropriate' to the use-case. Implementers and designers may want to consult legal counsel as part of their design process.
This section does not address expectations around security or authentication (except where specific exchange mechanisms have specific tradeoffs around what security or authentication mechanisms are possible). Designs will need to ensure that appropriate security mechanisms are in place for whatever data is shared. (Refer to the [security checklist] for additional considerations in this space.)
This section is also intended to have international scope and the terminology used here is used in its traditional English usage, not necessarily as it might be defined in particular jurisdictions. For example, the term 'sensitive data' in some jurisdictions has a specific legal meaning. When used here, it simply means that the data may need some additional level of protection, whether for privacy, business or other reasons.
NOTE: This document focuses on exchanging information using FHIR but, in theory, the principles described here would apply equally well to equivalent exchange mechanisms using other standards. For example, FHIR messaging would be like HL7 v2 and FHIR documents would be like HL7 CDA. If using other standards, the set of architectural approaches to exchange are likely to be more limited than those available for FHIR.
This section is broken into several components:
The guidance provided here focuses on specific architectural trade-offs between different FHIR exchange approaches and provides guidance on what approaches are best used in particular circumstances, all other things being equal. However, the reality is that it is rare for "all other things" to be equal. Designers will need to take into account other factors, including:
The following table provides an overview of several different approaches to data exchange. Each approach includes a short description and a link to a page providing more detail about the approach, including an interaction diagram demonstrating the flow pattern between the different systems. Each approach also includes a rating that reflects the degree of community support for the approach and the long-term reusability associated with implementing the approach. Both are ranked as either 'low', 'medium' or 'high'.
Rankings are approximate and may not hold in all environments. Also, in environments where FHIR adoption is low, even a 'high' ranking may not result in much support in that implementation space.
Assertions around the degree of re-usability and community adoption are not Da Vinci specific and are not based on formal measures, but rather from an informal subjective evaluation based on what's been observed in the area of implementation, questions, global and regional IGs and feedback from implementers. There are no formal measures of these characteristics available (as yet). There may be variability in terms of adoption within specific subsets of the FHIR community. These considerations are intended to inform, but not constrain design decisions of individual implementation guides.
As a rule, use of less re-useable and/or less adopted communication mechanisms will require more negotiation to achieve interoperability, and therefore the costs associated with the solution are likely to be greater. That does not mean that architectural circumstances will not make these approaches necessary (and justify the increased cost), merely that if there is a choice between equally viable architectural alternatives, consideration should generally be given to the one with lowest overall long-term cost to the interoperability community. Reducing overall cost to industry (payers, providers, patients and care-givers, etc.) is expected to be beneficial to most, if not all participants in the long term.
The table below is ordered first by those that maximize long-term re-use and then by those that are most supported in real production systems. All things being equal, those appearing earlier in the table are likely to be better choices. However, all things aren't equal, so consider the ratings in the table in parallel with the decision tree found following the table, i.e. a 'High/High' rating does not imply that the solution will be the best option for a particular use-case. However, if after evaluating the options based on the decision tree, there are multiple possible options, their respective adoption and re-usability ratings may help to choose between them.
The size of the list of approaches (and the size of the decision tree diagram following) may seem overwhelming. Remember that there is no need to support all these approaches or even most of them. All of them are appropriate for use for some use cases. However, systems (and implementation guides) only need to worry about the approaches relevant for their own needs. Hopefully, this document will provide the necessary overview and guidance to allow the selection of the approach(es) that will best fit your implementation needs.
Approach | Re-use | Adoption | Description | Example |
---|---|---|---|---|
read | High | High | The data consumer uses the FHIR read operation to retrieve the current record of a resource with a known 'id'. | A payer has received a prior authorization request that includes a reference to a Practitioner resource that the payer does not have a local copy of. The payer executes a read to retrieve the referenced record. |
RESTful search | High | High | The data consumer uses the FHIR search to describe the desired data and, using _include and _revinclude, the desired resources and the data source returns the requested information if available. | A SMART app is guiding the completion of a questionnaire. It executes a query for recent patient medications to provide options for the user when filling out the form. |
batch search | High | High | The data consumer sends a FHIR batch request to the data source containing multiple search, _filter, query and/or operation requests. All the requests are executed, and the responses are returned in a batch response. | A payer system evaluating a claim needs specific information about specific lab results and medications, with different time periods relevant for different lab results. To save time, rather than executing separate searches for the medications and each category of lab information, it packages all the queries into a batch and transmits them in a single operation. |
Polling | High | High | The data consumer queries the data source at regular intervals checking to see if there is new data that matches a specific set of criteria. | An imaging center has submitted a prior authorization request and the initial response was 'pended'. The data consumer is now checking back on a regular basis to see if a decision has been made, and if so, what the answer is. |
REST create | High | High | The data source POSTs a single resource instance to the RESTful endpoint of the data consumer. | A payer places a new Insurance Plan in a central registry available to EHRs. |
REST update | High | High | The data source PUTs a single resource instance to the RESTful endpoint of the data consumer. | An EHR updates demographic information about a specific Practitioner on a shared registry. |
Batch Bundle | High | High | The data source creates a 'batch' Bundle requesting the creation and/or updating of various resources and posts it to the RESTful endpoint of the data consumer. | An EHR has a collection of updates and new providers they want to communicate to a registry. Each provider record is independent of the others. |
Transaction Bundle | High | High | The data source creates a 'transaction' Bundle requesting the creation and/or updating of various resources and posts it to the RESTful endpoint of the data consumer. | A payer wishes to post a Practitioner and all of their associated PractitionerRole instances to a registry. The PractitionerRoles point to the Practitioner. The payer does not want any of the records to be created unless they all are. |
vread | High | Moderate | The data consumer uses the FHIR vread operation to retrieve a specific version of a resource with a known 'id'. | A payer has received an Encounter containing a version-specific reference to a Condition as the 'reason for admission'. The payer retrieves that specific version because they need to understand "what was known/believed at the time of admission" and the current Condition record may have subsequently evolved. |
history | High | Moderate | The data consumer uses the FHIR history operation to retrieve a list of all (or a filtered subset) of the versions of a resource with a known 'id'. | A payer is synchronizing their local practitioner registry with a centralized registry. They regularly query to retrieve all updates that have occurred in the last hour. |
CDS Hooks | High | Moderate | The data source makes a CDS Hooks request to the data consumer who is acting as a CDS Hooks Service. The service than returns 0..* cards containing decision support content. | A provider is creating a new lab order. The EHR uses CDS Hooks to check whether prior authorization might be required for any of the ordered tests. |
CQL search | High | Low | The data consumer invokes an operation specifying CQL to be invoked on the data source that will select and return a set of desired data and receives back a search Bundle. | An EHR retrieves CQL that describes information to include when submitting a prior authorization. The EHR executes the CQL and, after review for accuracy, includes the package of information as part of the prior authorization request. |
_filter search | High | Moderate | The data consumer uses the FHIR search, _filter or GraphQL mechanisms to describe the desired data and the desired data and the data source returns the requested information - if available. | A payer is querying observations from an EHR. Minimum necessary rules mean that to filter to only receive the information to which they are entitled, the query must include complex logic of nested 'and' and 'or' clauses. _filter allows the desired data to be retrieved in a single call rather than multiple consecutive calls. |
Subscription | High | Moderate | The data consumer configures a subscription on the data source describing the type of data of interest and the events that should cause notification. When the described event type(s) are triggered on the data source system, the data source pushes a notification to the data consumer either containing the desired data or prompting for the data consumer to query for the desired data. | A payer has submitted a rquest to a provider for data in support of a claim. The payer creates a subscription on the provider's EHR that will notify the payer when the submitted Task has been updated - either with new status information or a link to the requested data. |
Task | High | Moderate | The data consumer creates a Task either on the data source's system or a system monitored by the data source requesting the sharing of data. The data source then updates the Task with agreement to perform, progress status, and eventually a link to the requested data. The data consumer monitors the Task by subscription or polling. | A payer needs the clinical data that 'supports' an emergency surgery. The payer does not know where the relevant supporting information is stored or in what form it might take (lab results, radiology reports, PDFs, etc.) To avoid looking at irrelevant data, the payer submits a Task to the EHR asking a clinician or administrator to locate and return the relevant documentation. |
GraphQL search | High | Low | The data consumer uses GraphQL to filter data from a resource instance, a RESTful query or an operation outcome, including information from related resources and selecting the specific elements desired and optionally flattening structures. | A web-based member-facing application needs to provide a summary of recent claims. It uses a GraphQL API to retrieve only needed elements organized in a manner tuned to the web page's layout, minimizing complexity in the design of the web client. |
REST patch | High | Low | The data source uses PATCH to revise the information in a single resource instance via the RESTful endpoint of the data consumer. | A payer maintains a List resource containing the patient's current problem list (as understood from the payer perspective). (They have many other Condition resources they maintain for other reasons, so the List is necessary to provide a filtered 'problem list' view. A patient submits a request to adjust one of the items on the List. The request comes as a differential rather than a complete replacement of the entire list. |
SPARQL search | High | Low | The data consumer uses a local SPARQL engine to manipulate triples accessed by hitting a data source's RDF endpoint | A payer has a knowledge repository that, together with various ontologies, allows the payer to reason about patients who would likely benefit from (and have better outcomes/long-term costs) if they were on a new treatment. The payer uses SPARQL to access the EHR's clinical data to identify such patients and make recommendations to the patient's capitated care provider (Note: This is an example of how this data exchange mechanism could theoretically be used. It is not intended to define how any particular IG will define data access.). |
FHIR Document | Moderate | Moderate | The data source assembles a collection of FHIR resources into a human-readable document and transmits it to the data consumer. | A member has shifted their coverage to a new payer. The new payer wants to retrieve a list of active treatments from the old payer. The old payer provides a 'transition of coverage' document that organizes relevant aspects of the patient's current claims, treatments, and other information in a contextual way to help the new payer maintain continuity of care. |
Collection Bundle | Moderate | Moderate | The data source assembles a collection of related resources into a 'collection' Bundle and creates or updates it on the data consumer's Batch endpoint | A payer has a collection of information that has been provided as part of an X12 claims submission. Internally, they wish to store the information as FHIR, but they want to retain the information in a 'package' representing all the information that came in a single submission. |
CommunicationRequest | Moderate | Moderate | The data consumer creates a formal CommunicationRequest order and uses one of the workflow communication patterns to ask the data source to fulfill the order. The results are then returned using one of the push mechanisms. | A provider creates a formal order authorizing (on a patient's behalf) the disclosure of clinical information from a third party to a payer. |
Query search | Low | Low | The data consumer invokes a custom query operation on the data source and receives back a search Bundle. | A group of payers wish to expose their claims data to patient applications, but their back ends do not support a full RESTful query interface. Only certain parameters can be used and only in specific combinations. The group defines a standard _query operation that allows retrieval of a collection of information related to a claim, with only a limited set of parameters. |
FHIR Retrieval Operation | Low | Moderate | The data consumer invokes a custom operation on the data source requesting information by parameters on the URL and/or in the body and the response to the operation (synchronous or asynchronous) contains the requested data. | A payer wishes to receive a summary of average inpatient stay durations for a particular type of service. It invokes the operation and the EHR calculates the current average for the past year and returns the result. |
FHIR 'Process' Operation | Low | Moderate | The data source invokes a custom operation on the data consumer passing information it wishes to convey in the body of the operation or as references, asking the consumer to 'process' the information. Processing may or may not result in some or all of the information being stored or otherwise consumed and acted upon. | An EHR wishes to submit a prior authorization via FHIR, but regulations require that the payer receive the information over X12. The EHR invokes an operation on an intermediary that passes relevant prior authorization resources to an intermediary that takes care of the X12 conversion |
FHIR Messaging Queries | Low | Moderate | The data consumer sends a FHIR message to the data source requesting information and the data source responds (synchronously or asynchronously) with a message containing the requested data. | A payer is interacting with an EHR financial system that uses a v2 interface. Data is converted from v2 into a FHIR message to ensure the data flows to the payer in a familiar form. |
FHIR Messaging Notifications | Low | Moderate | The data source sends a FHIR message to the data consumer providing information related to some sort of event that has occurred. | An EHR notifies a payer that a patient has been admitted and provides additional information, such as patient condition, etc. |
The project has committed to defining example scenarios showing the execution of each of these exchange mechanisms. However, work on rendering ExampleScenario instances is not yet fully developed in the IG publisher and there was insufficient time to complete this work prior to second ballot. The example scenarios and associated example instances are expected to be included in the final publication of this IG.
With so many possible approaches to exchanging data, implementers need guidance on how to choose the appropriate choice for a given use-case. In practice, the choice will be influenced by existing infrastructure, architectural preference, and other considerations. However, the following diagram and associated descriptions aim to provide "best practice" guidance about what approach to use in which circumstances. Adhering to the recommendations here will help reduce long-term implementation effort and cost across the full data sharing community. It will also help increase the likelihood that FHIR solutions that are designed independently will land on the same architectural approach. In all cases, if you're uncertain of the best architectural approach for a given exchange, raise your questions on the chat.fhir.org implementer's forum. The community will often be able to provide additional considerations, guidance about what solutions already exist in the space, and nudge you toward the solution that is most likely to successfully meet your requirements and integrate with other solutions.
The following diagram provides a decision tree to help guide the selection of a data exchange approach. Each decision branch includes a hyperlink to a section further below that provides a detailed description of the considerations in making the choice for the decision point. Each "exchange option" will link to a web page that provides a detailed walkthrough of the interoperability pattern and further guidance on its use - as well as pointing to the relevant portions of the FHIR spec that describe how to implement the approach. While it is possible to use the decision tree while only paying attention to the branches 'relevant' for your specific use-case, readers are encouraged to familiarize themselves with all the content so they can be sure they're not excluding options that may be relevant to their requirements.
The first consideration is whether the desired data should be 'pulled' or 'pushed'. In the data consumer-initiating 'pull' scenario, the event that initiates the exchange of data occurs in the data consumer system. This might be a user clicking a button, or some sort of internal event that triggers a need for data. The data consumer system is then responsible for determining what data to retrieve and when it should be returned as well as for initiating the flow by communicating with the data source.
In the data source-initiating (or 'push') exchange, the event that determines the need for data to flow occurs in the system that owns the data. This might be driven by human action, the creation or change of the data to be shared, or some other system event that makes it necessary to make the data consumer aware of a specific set of data.
Notes:
If the data consumer will initiate, the next choice is whether there is a direct connection between source and consumer. If the trigger for exchange will be driven by the data source, the next decision is whether the need to share the data is configurable by the data consumer .
The next question is whether the data consumer and data source can talk to each other directly. In some interoperability environments the two systems may not have any means of knowing the network address of the other and all communication must happen through an intermediary. In some cases, the communication can happen via a network proxy where a request is automatically forwarded to a secondary location (potentially determined by the content of the request). In some environments, there may even be multiple levels of proxying, though this can limit the ability of the system to perform in a synchronous manner. From the perspective of this decision tree, proxied connections are still treated as a 'direct' connection. Both direct and proxied communications proceed through the tree to the next decision point of whether human intervention might be required.
However, in other environments, information exchanges are 'routed'. The initiator of a communication specifies a logical identifier of the desired target system and passes the communication into a network of intermediaries which then route the communication so that it eventually reaches the appropriate network address. In this routed form of exchange, the only option for exchange is query-based messaging.
NOTE: There is work underway as part of the U.S. FAST initiative to define an additional layer on top of FHIR RESTful interfaces that will support indirect delivery over RESTful interfaces. More about the project can be found here. This may reduce the need for message-based approaches in the future.
When a data consumer asks for information from a data source, the next question is whether the request is purely at a system-to-system level where the request will be exclusively processed by software or whether there is the potential for humans to be involved in the preparation of the response. Human involvement means that the request must arrive in a form that can be persisted until a human can look at it and with sufficient contextual and descriptive information for a human to understand what is needed. Human involvement in responding to a request for data might occur for many reasons:
Allowance for human intervention does not necessarily mean there will always be human intervention. It is possible that some data sources will have an ability to handle certain requests in an automated fashion while others will be delegated to humans. It is also possible that a data source will evolve to be capable of handling certain requests automatically that it could not in the past. The key thing is that the interface is designed to allow for human intervention.
Interfaces that allow for human intervention are intrinsically asynchronous, though in some cases the asynchronous response may come quickly. In general, automated interfaces are preferred to those allowing for human intervention. First, if human intervention is required, that has a significant cost due to the expense of human time. Second, human-intervention mechanisms involve more technical overhead (more layers, more data structures) which creates higher implementation and testing costs. Finally, the lack of support for synchronous access means the solution is not a good fit for certain use-cases, which may force multiple parallel access mechanisms (one for synchronous with no human intervention and one asynchronous with human intervention).
If the exchange will potentially require human intervention, the next decision is whether or not formal authorization is required. If the exchange will be fully automated, the next decision is whether CDS Hooks is a candidate.
The CDS Hooks specification should be used if the driver for the exchange is a user action within the data consumer where the user is visually interacting with that system and the desired results will be clinical decision support guidance from the data source about that current action. The specific requests and responses are not expressed as FHIR, but the data describing the current action and additional contextual information generally will be. CDS Hooks also allows the data source to access contextual information from the data consumer to provide information necessary for the decision support logic. This internal data retrieval by the decision support engine would use RESTful search or possibly one of the search alternatives beneath it.
If hooks are to be used, the CDS Hooks spec provides details on the flow. The hook service (data source) may take on the role of data consumer and use automated means of retrieving data to support the decision support it provides. If CDS Hooks is not a good fit, the next question to consider is whether the data to be retrieved is pre-existing.
When requesting data to come back as resources, there are two possibilities - the data consumer is seeking data that is already available as existing records in the data source's system; or the data consumer is asking the data source to generate net new records - though potentially based on existing information. For example, retrieving previously recorded blood pressures for a patient would be "existing records", while asking for the average blood pressure for the last 24 hours would typically involve generating a record. (It is unlikely the system would already have a stored record for the average as of 'now'.) The first case is a type of query and can generally be handled by relatively 'standard' mechanisms in FHIR. The latter will require a custom operation or message and will typically mean writing code specifically to generate the desired data.
If searching for pre-existing data, the next decision point is whether the search mechanism will be returning resources. Otherwise, the choice is whether messaging is appropriate.
In FHIR, data is represented using resources and, in most cases, when data is exchanged, it will be transferred as collections of those resources. Generally, the complete resource is transmitted, and the same data is shared with all systems. This approach of sharing the full resource ensures that the contextual meaning of all the data elements is retained and provides a consistent framework for validating, parsing, and consuming the shared data. However, in some cases, information may be filtered out of the resources for reasons of state or federal privacy regulations, an individual’s own privacy preferences, or more efficient communication. In other cases, the use-case may call for returning an arbitrary collection of elements of interest, potentially joining content across multiple resources, with no need/desire to retain context. This latter is less interoperable - it will not be possible for downstream systems to consume the data as FHIR, nor for it to be persisted in a FHIR-based repository without treating it as a Binary. In general, the search mechanisms that return resources are more widely supported and will require less negotiation/modification of systems than those that return independent elements, so if either approach could work, it is best to take the 'return resources' branch in the decision tree.
If the data to be returned should be a resource or collection of resources, the next decision point is whether the retrieval is only a single identified resource. Otherwise, the next consideration is CDS Hooks
In some cases, the retrieval process is straightforward - the data consumer knows the id
of the resource it needs, and it only needs that specific resource. FHIR has specific mechanisms for doing this that are widely supported and very efficient to execute. The id might be known because the data consumer has previously retrieved a copy of the resource and is only looking for the most recent changes. Alternatively, the data consumer might be retrieving a resource that was referenced by another resource it has a copy of.
Obviously, if the data consumer needs more than one resource - even if it just wants a resource with a known id
as well as certain related resources, these special mechanisms will not work. They also will not work if the id
of the resource on the target server is not known.
If only a single resource with known id
needs to be retrieved, the next consideration is whether the retrieval is the current version. Otherwise, the next approach to evaluate is Resource history.
There are two main mechanisms in FHIR for requesting data from a human being (or potentially from a human being): CommunicationRequest and Task.
CommunicationRequest is used to represent proposals, plans and formal authorizations (orders) for data to be exchanged. A CommunicationRequest would be appropriate for use when there is a need for a formal order, along the same lines as a prescription, lab order, referral, etc., but in this case indicating that certain information must flow to a data consumer. CommunicationRequest, like other Request resources cannot ask for action on its own. Instead, it must leverage one of the FHIR workflow communication patterns to seek fulfillment of the specified request
Task is used to explicitly ask for an action to be performed. Sometimes it is used together with a Request resource, however it can also be used on its own to ask for execution of a simple action - such as requesting someone fill out a form or return a specified piece of information. Because Task can be used to ask for an action to be performed and can, itself, track acceptance or rejection of the request, progress status of the request and eventually be updated to point to the 'output' of the request, it saves on overhead compared with using CommunicationRequest which (for any of the afore-mentioned functions) would need to be paired with Task. As such, Task is generally preferred when there is not a need for a formal authorization and when the capabilities of Task to describe the data requested are as sufficient as those available on CommunicationRequest. Even when authorization is needed, Task may still be relevant as the authorization may span multiple exchanges covering a broad set of data, while Task allows initiation of a single transfer of very specific information.
Note that an authorization (e.g. CommunicationRequest) by itself is not sufficient for information to flow, and in some cases, a single authorization may be associated with numerous folows over an extended period of time. The trigger for actual exchange will some form of workflow initiation mechanism. Frequently, that will be Task, meaning that CommunicationRequest may end up being used in conjuction with Task.
Details on using Task can be found here. Details on using CommunicationRequest can be found here. Note that, regardless of whether Task or CommunicationRequest are selected, the asynchronous response providing the requested data (or some of the requested data or an outright refusal to deliver the requested data) will be handled as a Data source-initiated (Push) event and the architectural approach for that delivery will also need to be selected. (Generally, the mechanism will be one of the Configured by consumer options as the data consumer is the system that truly initiated the transfer and therefore should decide what happens to the data. However, if an operation or message is used to transmit the Task or CommunicationRequest, then the response would come back as a message or an operation response.
When retrieving a single resource by a known identifier, there are two options. The read operation retrieves the current version of the resource. The Version-specific read (vread) operation returns a specifically identified version of the resource (which might be the current version or could be a historical snapshot of the resource). The latter approach obviously involves knowing the specific version to be retrieved - generally because of a version-specific reference. It also means that the data source must support accessing historical versions. (Many systems, especially legacy systems, do not.)
Details about the data exchange ramifications of the 'read' operation are here. Details about 'vread' are here.
Unlike version-specific read, the history operation returns a Bundle containing a collection of versions - either all of them, or those since a specified date. History can be reported for a single resource id, for a specific resource type, or for all resources on an entire server. The first is useful if the data consumer needs to know what is happened with a specific resource over time. The other two allow complete synchronizing of the server held on the data consumer with the changes that have occurred on the data source - either for a single resource or for all resources.
Details about the data flow for performing history
are here. If history is appropriate the next question is to whether to use synchronous or asynchronous search. The latter may be necessary for large volumes. If history is not appropriate, the next option to consider is whether an ad-hoc query is needed.
In an ad-hoc query, the data source allows the data consumer to combine the search parameters or use a search expression language to filter the set of data they get back. The data source may have a few rules about certain minimum query expectations or disallowed parameter combinations, but in general the data consumer is free to construct their queries as needed to support their use-case. The benefit of this approach is that the same query interface can be used by data consumers to support a wide variety of use-cases. There is significantly less need to modify existing interfaces or stand up new interfaces when a new use-case arises.
However, ad-hoc query means that the data source must have a security model that allows arbitrary queries against data. That does not mean they must allow all data consumers to query whatever they like. However, it does mean that the data source must be able to evaluate a given ad-hoc query and determine whether it is "allowed" for that data consumer and if not, either reject the query or add additional filters to make it acceptable prior to execution. Also, because ad-hoc queries are use-case independent, the data source must make access control decisions without knowing the 'purpose' for which the data is being retrieved. (Though in some cases, the authorization layer might allow capturing an overall reason for whatever actions are taken within a given authorized session.)
If the data consumer does not have the security or technical abilities to perform ad-hoc queries, it might still be worth the investment to develop them, given that the cost can be defrayed over multiple use-cases. When designing the interoperability solution, an evaluation of the long-term costs of building a generic, re-useable query mechanism vs. building and maintaining multiple purpose-specific mechanisms should be evaluated. A focus on short-term costs may result in larger overall costs.
An additional consideration is that ad-hoc queries can create performance concerns for data sources. Allowing arbitrary search expressions means that it is possible that some queries will perform poorly or will overly tax the data source, potentially impacting performance of other services. And obviously, running ad-hoc queries means that the data source must have a data access layer that can execute ad-hoc queries. Systems that provide a facade over top of a non-FHIR system, that integrate data from numerous systems or have limited control over the indexing of their data source may not be appropriate for ad-hoc query interfaces.
A final consideration is whether the data desired can reasonably be described by a querying on specific properties of the relevant information. In some cases, the relevant information may require a complex site-specific algorithm or even human intervention to identify the record(s) desired, meaning that ad-hoc query would not meet the requirement.
If ad-hoc queries are appropriate, the next decision point is whether RESTful search is a good fit. If not, then the _query mechanism should be evaluated.
The most widely implemented search mechanism in FHIR is the RESTful search mechanism. It defines mechanisms for retrieving resources along with related resources, filtering what resources are returned, ordering the result-set, and allowing data to be returned in individual pages. It is a generic mechanism that is use-case independent. As such, once a data source can respond to a query, it may (if it wishes) provide that data to any data consumer, regardless of the purpose for which the information is needed.
Details on performing exchange using REST search are found here. If REST search is appropriate the next question is to whether to support synchronous or asynchronous search. Otherwise the next option to evaluate is batch search.
In some cases, an individual RESTful query is inappropriate because resources need to be retrieved from multiple resource endpoints and the search criteria for each endpoint must be different, so that a generic search against the base endpoint would be inappropriate. In other cases, even when hitting the same endpoint, different subsets of resources need to be retrieved using distinct combinations of search parameters. While _filter could potentially be used to specify various bracketed clauses to express all the constraints in a single filter, it may be easier (and more widely supported) to simply submit several independent queries. These can be done as independent calls; however, it can be more efficient to submit all of them at once. This can be done using a batch Bundle to invoke multiple queries simultaneously, and return a corresponding batch-response Bundle containing the results of each search. Note that if performing all queries simultaneously, the search parameters of the queries must be independent. It is not possible to base the parameters of one search on the results of a previous search. If this sort of dependency is needed, the searches will have to be invoked independently, with the second search launched only after the results of the first are back.
If batch searching is appropriate, details on the process can be found here. As well, the design needs to consider which resource retrieval mechanisms will be used to populate the batch. Finally, a decision is necessary about whether the Batch search should be synchronous or asynchronous. If batch not appropriate, the next architectural approach to consider is using _filter.
The _filter search mechanism provides additional capabilities beyond what can be expressed using basic REST search. Specifically, it provides full nested logical expression capabilities (and/or/not/parenthesis) and allows arbitrary filtering of chained parameters (e.g. Observations for patients where the legal name starts with 'Bob'). It also supports a slightly broader set of comparison operations (e.g. "ends with"). _filter can be used together with base REST search capabilities, meaning that _include, _revinclude, _sort, etc. This mechanism is treated as 'separate' from basic REST search because support for _filter tends to be less than other REST search parameters. Unlike other REST search parameters (for which support is defined on a per parameter basis), if _filter is supported, the expectation is that the full _filter expression language is available and supported.
If _filter searching is appropriate, details on the process can be found here. Like any other search mechanisms, implementers must also determine whether to invoke the request as synchronous or asynchronous. If _filter is not a good fit, the next architectural approach to consider is using CQL.
CQL stands for Clinical Quality Language. It is a high-level language that supports manipulation of healthcare data. CQL is a full-blown Turing-complete programming language that allows filtering and generation of data structures. It is not tightly bound to FHIR, though it does rely on FHIR data types. It includes the ability to execute sub-queries, define modules and functions, import external libraries, etc. However, it is a language specifically designed to manipulate data structures and includes rich terminology support. It builds on the FHIRPath language to allow easy navigation, filtering, and execution of mathematical and collection-based operations on hierarchical and linked data structures. Because it is a 'complete' programming language and has an internal capacity to execute sub-queries, it allows the definition of arbitrarily complex data searches.
The primary downside of CQL is its complexity. It is closer to using Java to retrieve data than SQL, though far more tuned for data manipulation than Java is. Because of its complexity, it is not widely supported in the FHIR implementation space, though it is supported by some of the FHIR reference implementations. Also, as a programming language, it can raise the risk of performance issues for systems that choose to allow execution of arbitrary CQL against their repositories. It is also much harder to analyze the 'safety' of a specific ad-hoc query in terms of whether it violates access permissions associated with a user's privileges.
At present, the only way to invoke a CQL search is by using operations. This can be done either by passing the CQL directly (the cpg-cql operation) or by referencing a library that contains the relevant CQL (the cpg-library-evaluate). The latter is more useful when the CQL is complex.
CQL appears in both the 'full resource' and 'individual data elements' side of this decision tree because it can return results in either form. If CQL is appropriate, discussion on its use can be found here. The CQL operations can be invoked using either a synchronous or asynchronous approach. If CQL is not appropriate, the next option to explore if returning resources is _query and SPARQL if returning individual data elements.
The _query search is an alternate way of invoking a custom operation. That operation inherits the general RESTful search capabilities in terms of paging and returning a search Bundle and thus can piggy-back on top of generic RESTful search infrastructure. However, the parameters used by the search are defined by a custom OperationDefinition. This approach is not as wide open as using the generic operation approach to data retrieval because all parameters must be expressible as URL parameters and the operation cannot be one that affects state.
However, beyond those constraints, the search can do anything the author can imagine so long as the result is a search-set. The downside of using 'query' is that the query operations are 'custom'. They will only be supported by implementers that add custom code to enable that specific query. Changes to the query definition need to be coordinated to with implementers and new requirements are likely to result in the need for a distinct 'query' - which again will require negotiation with and custom coding by other implementers. As a result, this approach offers limited re-use and scalability.
If a query operation will meet the need, details on the process can be found here. Using query also requires a decision on whether to use synchronous or asynchronous search. If query is not a good fit, the next option to evaluate is messaging vs. operations.
GraphQL is a query mechanism that returns a custom JSON structure that contains specific data elements of interest from a resource or set of related resources. It can be more efficient than a standard RESTful query response because it does not need to include the full resource structure, mandatory elements or have the overhead of a Bundle resource. It also allows data structures to be 'flattened', which can be useful for JSON used to drive user interfaces or statistical processes. Finally, it gives greater control over references allowing direct tracing across the specific references to include rather than relying on :iterate
to include referenced resources that might occur along multiple paths.
The downside of GraphQL is that it is still experimental (in FHIR) and is not widely implemented. As well, the result-set from a GraphQL query is not standard FHIR and cannot subsequently be stored or shared as a standard FHIR resource. In some cases, key meaning may be lost (e.g. failing to return status and failing to exclude 'entered-in-error' records). Because filters can be FHIRPath, indexing requirements are not as locked down as RESTful query where allowed search parameters are defined in advance.
If GraphQL is appropriate, details on its use are here. If using GraphQL, the underlying server would typically also use FHIR's read, search or operation capabilities to select the base resource(s) against which the Graph will be evaluated. As well, consideration will need to be given to whether the GraphQL calls should be synchronous or asynchronous. If GraphQL will not meet the need, the next option to explore is CQL.
SPARQL is a general-purpose query language for accessing data expressed in RDF (as tuples). Its primary benefit is that it can query across data using multiple ontologies - so rather than just relying on FHIR structures, the query language can rely on types and inferences from terminologies such as SNOMED and rules or knowledge sources that assert meanings to specific combinations of data. Properly designed, SPARQL queries can be exceptionally powerful. However, such powerful queries can rely on data being monotonic and consistent, which is not necessarily typical in the way FHIR exposes data or in the healthcare space in general. Also, SPARQL requires data to be expressed in RDF/Turtle, which is not as widely supported as XML and JSON. Finally, SPARQL skills are not widespread and support for SPARQL in existing FHIR interfaces is low to non-existent.
If SPARQL is appropriate, discussion on its use can be found here. Otherwise, the next options to explore are operations and messages.
If data is going to be 'pushed' from data source to data consumer, the next question is whether the consumer can use FHIR-based mechanisms to control what sorts of data will be shared and in what circumstances. Data consumer control adds complexity for both systems, but eliminates the need to hard-code rules for what data should be shared with whom or for human intervention to configure the data source to supply new data or to stop supplying old data. This elimination of human intervention allows data consumers to be more adaptive in their behavior. They might subscribe to special topics of rare interest because they can, where it would not make sense to contact the data source to configure a specific feed. Alternatively, the data consumer might adjust polling frequency for different types of data based on application or user priorities.
Another consideration is that all the consumer-configured data sharing approaches give the data consumer full control over what happens to the data once received (i.e. what, if anything, gets persisted and where). However, most of the sharing mechanisms that do not provide consumer control leave the decision of what data is persisted by the data consumer up to the data source.
If the pushed data will be configured by the data consumer, the next question is whether subscription is viable. If the data consumer will not be involved in determining what data gets pushed, the next question is whether there is a direct connection between data source and data consumer.
This consideration is similar to the one for pull interactions - can the data consumer and data source talk to each other directly. The decsion points are the same, however, the outcome of the choices is sligtly different. If direct or proxied communications are possible, the next consideration is whether the data source will direct persistence. If communications must be routed, the only option for exchange is notification-based messaging.
Data consumer-configured exchanges can take one of two forms - subscription or polling. Subscription offers several benefits:
However, subscription requires more sophisticated infrastructure on the part of both the data source and data consumer. The data source must expose a subscription end-point and must build in an event-detection mechanism into its processing or persistence layer such that it can trigger notifications when an event of interest occurs.
Subscription also involves an enhancement of the data source's security model because the authorization that is in place at the time the subscription is established will not necessarily be the same as what is in place when the subscription triggers a notification. For example, if a subscription is established with patient consent conveyed via an OAuth token, it is unlikely that the OAuth token will still be valid during the subsequent time-period when event notifications are triggered by the subscription. Consents may have changed, user privileges may have changed, etc. Also, the subscription notifications will be directed to a 'system', not necessarily a 'user'. The security design of the data source will have to take these differences into account.
On the data consumer side, the system must be able to receive notifications at any time. This imposes an availability and 'interrupt' capability that not all systems will possess. The system must also be able to route the received information to the appropriate user/module for the information to be used.
If subscriptions are appropriate, the next choice is whether the subscription notifications should include data. If subscriptions are not viable, the appropriate solution is polling, which is described here. If polling is used the data consumer must use one of the existing resource retrieval mechanisms to actually perform the polling.
There are three models of subscription behavior once an event that meets the criteria of the subscription has occurred:
(In all cases the notification indicates the subscription topic the notification is for and how many new events have occurred since the last notification and other metadata such as whether there have been errors.)
The first approach is most efficient because no subsequent action is required by the data consumer to retrieve the data. It is received directly. However, this approach comes with a few challenges:
The second approach means that the data consumer will have to perform a read or search to retrieve the actual record. This adds an extra interaction to the exchange, but - if the search mechanism is used - gives the consumer greater control of the data returned by using _elements, _include and/or _revinclude. It keeps the load on the data source low because the subsequent read/query is by id which imposes minimum retrieval costs. Because only the id is exposed, security requirements are much less - no personally identifying or sensitive health information is exposed.
The final approach is the least work for the data source initially - it does not even have to identify the impacted records, only that something has happened. However, it is more work subsequently as the data consumer must use appropriate query parameters to find the relevant data - it cannot search by id. Other benefits are like the second approach.
Data consumers should limit the request for information to that required to address the specific, stated purpose of the data exchange or prearranged, agreed- upon purposes. For instance, trading partners will use data use agreements (DUAs), business associate agreements (BAAs) and/or contracts per the Da Vinci Guiding Principles.
Details on how to use subscriptions with push notifications are here. Subscriptions where the initial notification must be followed by a query (whether the notification was a resource id or nothing at all) are described here.
When data is pushed from a data source to a data consumer, there are two possible modes of sharing. Either the data source specifically instructs the data consumer to create, update, or sometimes delete records; or the data is provided to the data consumer with no expectation as to what information, if any, the system will persist or where.
To be able to direct persistence, the data source needs to have a sense of what data is already persisted in the data consumer so that decisions as to whether to create or update can be made. For updates, the data source may also need to know the etags associated with the existing records to avoid collision control. In some cases, this knowledge will exist because the data source is the sole source of information for the data consumer and thus the source can track what information the consumer has - and where. Mechanisms such as conditional create and conditional update can handle some aspects of this as well. In other cases, the data source will need to query the data consumer to see what data exists.
In addition to an awareness of the existing data, the data source would also need a degree of authority to decide what information the data consumer should store and where. While the data consumer can always refuse a create or update request, this means that essentially the data source determines what gets created/updated or the data consumer does not get the data. Whether this is appropriate is dependent on the relationship between data source and data consumer.
If the data source will direct persistence in the data consumer, the next question is whether the data will be stored as a group. If the data source will not be directing persistence, then the next decision point is evaluating the appropriateness of messaging vs. operations.
In most cases, FHIR resources are managed and stored independently. This allows the resources to be queried, aggregated into documents or messages, and otherwise used to support a variety of use-cases. Reference elements refer to other resources that are independently stored (potentially on different servers). Clients traverse the references via query to access the information they happen to care about. However, in some cases, there is a need to 'package' a set of resources together and store it as a collection. This ensures that modifications are always done in the context of the collection and that a specific related set of resources are always accessed 'together.
Persisted collections tend to be use-case-specific because the decision about what resources should fall within the collection vs. what should be handled as external references is dictated by the nature of the use cases. Storing resources independently allows them to be used in whatever manner any arbitrary use-case requires.
If the need is for resources to be stored as a group, the next question is whether there is a need to support presentation and/or story-telling. If the resources will be stored individually, the next question is whether there is a need to transmit resources as a group.
Even when the expectation is that resources will be stored and potentially accessed independently, FHIR defines mechanisms for transmitting multiple resource actions (creates, updates, patches, deletes, operations, etc.) as a collection. This can reduce the amount of back-and-forth traffic between the data consumer and data source and reduce some of the communication overhead. The impact on bandwidth will be slight as the volume of 'resource' data will be the same (and in fact, slightly increased, due to the additional size of the Bundle resource needed to package the various requests) and there will be a reduction in repeated transmission of HTTP headers, security tokens etc. The primary savings is in the communication overhead associated with processing requirements that happen once for each transmission (e.g. authorization verification, SSL handshakes, etc.) This savings can be significant.
Obviously, sending multiple actions in a single Bundle can only happen if the use-case requires the transmission of multiple requests. However, each of the actions must be reasonably independent. Depending on the mechanism, it can be possible for resources to reference other resources contained within the same Bundle, but it is not possible to have conditional actions that depend on the results of other actions performed within the same Bundle - separate RESTful calls must be done and the data consumer must evaluate the first and decide what to do in a subsequent request.
One final consideration is that batch and transaction processing require additional technical capabilities, particularly transaction - which requires treating multiple actions as a single unit of work. As a result, support for batch and transaction is lower than support for individual RESTful actions.
If group transmission is needed, the next question is whether transactional behavior is required. If there is no need for group transmission, then the next decision is whether the record will be new to the data consumer.
When doing simple REST actions (on their own or as part of a batch or transaction, the data source needs to figure out whether the appropriate action is a create or update. That is driven by whether the data to be shared already exists as a record on the target system. In some cases, the data source will know this automatically (e.g. because the information in question is brand new and there is no chance the data consumer could have a copy; or because the data source is the data consumer's only source of information and the data source has not previously sent the record in question. In other situations, the data source may need to query the data consumer to see if the record is already present (and if so, what its identifier is).
If the record does not already exist and the data source does not need to assign the resource identifier, the data source will use POST
to create the record. Details on 'create' are found here.
If the record already exists or the data source will be assigning the resource id, the next choice is whether to transmit the whole resource.
Normally when transmitting an update (to revise a resource or create the resource with a specified id), the entire content is transmitted. However, if updating an existing resource, it is also possible to transmit only the changes made using patch. This could use considerably less bandwidth if the changes made are small in comparison with the overall size of the resource. Depending on the type of information changed, may also result in less processing by the data consumer. However, the 'patch' mechanism is less widely supported than update, so it may be necessary to fall back on 'update'.
Details on 'update' are found here. Details on 'patch are found here.
When performing a group of transactions, HL7 defines two mechanisms: batch and transaction:
If transactional behavior is required, then the exchange will use FHIR transactions, which are defined here. Otherwise, exchange will use FHIR batch, which is described here. In either case, the data source will need to determine what actions to include within the Bundle.
When a group of resources needs to be stored in FHIR, this is always done using the Bundle resource. There are two types of Bundle that are intended to allow a group of resources to be persisted together: collection and document.
A collection is quite flexible. It can store any arbitrary set of resources with no required organization and no semantic other than "this is a bunch of stuff someone decided to store together". It might be used to store a collection of examples, test cases, implementation guide source or any other set of resources that needs to be persisted as a group.
FHIR documents are different. The contents of the Bundle must adhere to a specific set of rules, both in terms of how they're constructed and how they should be handled when received. One of the main differences is that a FHIR document provides specific information about how the information should be presented to a human, including in what order. This is important when there is a need to "tell a story" - i.e. where a human reader needs to consume information in a specific way in order to have a consistent (and complete) understanding of what the collection of information represents. This is commonly found in artifacts such as pathology reports and discharge summaries. While it is possible to jump right to the 'recommendations' part of the document, those recommendations will not necessarily make much sense unless the reader had first gone through the content that comes before.
If there is a need to control presentation or provide storytelling, then the exchange will be using FHIR documents, the process for which is described here. Otherwise, the exchange will use 'collection' bundles and the process for that is described here.
The 'bottom' of most parts of the decision tree is a choice between operations and messaging. Both approaches rely on defining custom request structures that are passed in response to a specific event and responded to with a corresponding custom response. The requests and responses can be FHIR resources, or - through the use of the Parameters resource, individual data elements. These options fall lowest on the decision tree because they require custom development by both data consumer and data source. However, there are still use-cases for which they are most appropriate.
Messaging and operations are functionally equivalent. I.e. Both mechanisms can be used to accomplish identical functions. However, there are small differences that may make one more appealing than the other in specific architectural circumstances:
Note: It’s possible to replicate a messaging approach using a custom operation (i.e. passing in a Bundle of linked resources and getting back a Bundle of linked resources where one of the resources indicates what the 'event' of the operation is. This is discouraged as it essentially creates a ‘custom’ mechanism to do something where there is already a standard operation ($process-message).
If the principle reason for using messaging is to gain access to the routing behavior (because the data consumer and data source cannot talk to each other directly), it is possible to define custom messages that emulate the behaviors of the other exchange mechanisms, wrapped in the messaging infrastructure to allow routed delivery. I.e. If messaging is selected only for routing purposes, it may be relevant to loop through the tree again to determine an interoperability approach that should be wrapped by the messaging exchange mechanism.
There are separate pages that describe operations and messaging, however there are also sections within those that are related specifically to the use of operations and messaging in 'pull' vs. 'push' situations:
Note: With both messaging and operations there is also a need to determine whether support will be synchronous or asynchronous.
In synchronous queries, the results are determined and returned within the HTTP timeout period of the initiating GET. The will typically block and wait for a response before continuing with its action. While it is possible to have very long timeout periods (100 seconds plus), typically the intention with synchronous responses is to return in tens of milliseconds to a few seconds at most to ensure appropriate system performance and user experience. In cases where a search may take longer than this, the FHIR asynchronous search mechanism can be used to allow a query to be initiated and subsequently monitored until a response is available - even if this takes minutes, hours, or potentially even days. This may be necessary if the search is extremely complex or is slow for other reasons (e.g. if it involves accessing data sources with high latency or limited availability).
Details on invoking and processing RESTful requests in a synchronous manner is described separately for each major type of exchanges separately. For RESTful exchanges, the synchronous flows are here and here. The asynchronous flow is covered here. For search the synchronous process is here and the asynchronous process is here. For operations, synchronous is here and asynchronous is here. Finally, for messaging synchronous is here and asynchronous is here. For all except messaging, the asynchronous process is similar, however there are small differences - such as who initiates the process.
All these approaches have additional pre-requisites to their successful use:
The data consumer and data source must know each other exist and have a means of connecting. For most FHIR interactions, this means that at least one of the two systems must know the base URL of the other. In some cases (where the data flow involves initiation on both sides), both will need the base URL of the other. Even if using messaging or proxies where a direct web connection is not necessary, the initiator needs to know a) that an appropriate recipient exists; and b) where to initiate the action necessary to cause the intended recipient to receive it.
This awareness could be created through manual configuration, a registry of EndPoint or CapabilityStatement instances, or by a user providing the information directly
This page does not provide guidance about when SMART on FHIR would be appropriate for use because SMART on FHIR is about user interface integration and access control. The data exchange mechanisms it uses are those already described in this specification. However, implementers who are not familiar with SMART should certainly review its capabilities and decide if/where it is appropriate for use in their product(s).