I have been exploring this a little more. And I figured that there should be an easy way forward for people trying to build an “app ecosystem” for healthcare. In fact, I’m working on bundling open-source projects like EHRBase, HAPI, Keycloak, Hermes, medblocks-ui and ORY to implement a base layer of microservices on top of which unified, interoperable healthcare apps can be built upon. It’s definitely FHIR and openEHR.
Long post ahead
Here is how I think everything might fit together:
1. Clinical Content
Data capture:
OpenEHR is excellent for clinical data capture. There are hundreds of archetypes to choose from and putting them together is easy. Automated generation of forms from these templates is also easy.
FHIR on the other hand has the Questionnaire resource for data capture and along with the Structured Data Capture IG. A QuestionnaireResponse, however, cannot be directly mapped to FHIR resources, since they don’t have a 2 level modelling layer. They are trying to fix this in FHIR R5 with Modular Forms, by extracting out reusable chunks into modules - they’re basically reinventing archetypes and the multi-level modelling approach. Even after that, querying data from resources with value[x] polymorphism is not easy.
openEHR has a huge advantage because of the mature RM and all the archetypes that took years of modelling work. There is also a CKM with tooling for open collaborative modelling. FHIR does not have this.
Querying
Unified querying across CDRs is possible using AQL and it meets the need for building any complex view in an EHR. Although queries as complex can’t be executed on a FHIR server, it is much easier to get started with. As a result, developers will be more familiar with querying a server using the FHIR search API as compared to using AQL for which they need to learn more about openEHR archetypes and templates.
Almost everything in FHIR is represented as a resource and there is not much nesting involved. So figuring out the context of an Observation for example might be hard.
So FHIR querying is good for: Displaying a graph of blood pressure or pulse readings in a graph on a patient-facing app. Any data with a simple, non-hierarchical structure.
AQL is good for: Rendering a print-out for a patient discharge summary with sections like History of Presenting Illness and Examination compiled across multiple departments. Note that doing the above with AQL is also trivial.
2. Demographics, Terminology and Business Logic
openEHR limits itself to doing one thing, and does it well. FHIR is extremely versatile and can handle most of the business logic of a hospital. This includes demographics, billing, employee management, insurance claims and appointments. Most of these data models are not as complicated as clinical data, and FHIR does a decent job here. Resources can be extended and contained using profiles to model the business requirements of the hospital.
Most terminology servers also provide a FHIR interface, and multiple CodeSystems like SNOMED, LOINC, ICDx, RxNorm can be deployed as separate services, using the same FHIR interface.
So openEHR for clinical data and a FHIR server for everything business-related, including demographics and terminology.
3. Mapping openEHR ↔ FHIR
FHIR Facade
Represent all clinical data in an openEHR CDR and create a FHIR API that will map between these compositions and convert them into FHIR resources. Similarly, FHIR resources posted, should be converted to an openEHR composition and persisted. There is only one source of truth for clinical data and that’s the openEHR CDR. This is how EHRBase FHIR Bridge does it (although the FHIR resources might be persisted in the logs).
Synchronised Servers
Run and configure an openEHR CDR and a FHIR service separately. Have a subscription mechanism running on both services, that trigger when a change is made.
openEHR → FHIR: Whenever compositions of importance are posted or changed, it executes an AQL that is used to generate a set of FHIR resources. The ids of the FHIR resources can be generated based on deterministic hashes from the compositions so that the FHIR version can be updated properly when the composition updates. Most of the time, the FHIR resources will have less information than the openEHR composition.
FHIR → openEHR: Ideally, the FHIR server should be used to write only the business and demographic data, and clinical data reflected from openEHR must be read-only. However, there might be times when a patient-facing app and other apps (using SMART on FHIR) want to write clinical content of importance to the FHIR server. For these resources, openEHR templates needs to be made in advance and can be committed to the openEHR CDR using a similar hash-based uuid generated from the FHIR resource so that the composition can be updated when the FHIR resource changes. If some FHIR resources are deemed not important enough to show the physician, but an app still wants to write and read to the FHIR server, templates are just not created and no mapping is done. The data still lives in the FHIR server.
4. Unified Platform
The apps deployed on the platform must be able to securely and uniformly log in and access the resources they need for the “app ecosystem” to take off.
Authentication: Oauth2
When developing an array of apps that will be used for different purposes, they all need to have a Single-Sign-On (SSO) mechanism. SMART on FHIR seems to be the most well developed in this regard, and there are 100s of apps already built on this. However, some of the specifications in SMART on FHIR like launch_context do not follow the Oauth2 protocol properly. This requires that SMART on FHIR be implemented by extending an Oauth2 implementation like Keycloak/Ory Hydra or implement it from scratch. This unconventional use of Oauth2 also means that client apps need to use the smart-on-fhir client libraries to make these flows.
Sticking to just Oauth2, without any deviation might be better in the long run since it can be used to directly solve most of the requirements for healthcare applications. Solutions like ORY Hydra or Keycloak can be used to implement the Oauth2 flow directly on top of an openEHR and FHIR stack, with Bearer tokens provided that contains the scope and the identity of the user. The scope can be used to represent most things in the SMART-on-FHIR non-standard parameters. The same token can be used by other microservices like terminology etc too.
For apps that depend on SMART-on-FHIR, migrating to just Oauth2 will be easy and will in fact mean removing code from their app. There are client libraries for Oauth2 in almost every language.
Authorization: Centralized Policy Enforcement
Fine-grained access control is essential to healthcare applications.
Both the FHIR and the openEHR server have to figure out “Can this user do this action on this resource?”
Although on the surface it seems simple, this is very tricky in some cases. A lot of things need to be taken into consideration and the requirements keep changing. Therefore, the question: “Can this user do this action on this resource?” should NOT be answered by either the openEHR or FHIR server. It should be deligated to a Policy Enforcement endpoint.
Projects like ORY Oathkeeper and ORY Keto when used together can provide extremely scalable and configurable authorization options. Keycloak’s Authorization Service also provides very good coverage with a lot of nice to have features.
Both the FHIR serve and openEHR server must implement an API for the following:
- Policy Enforcer Endpoint: When a user X tries to do an action Y on a resource Z, make an API call to a policy enforcement endpoint with the details of X, Y and Z. The user can be allowed or denied permission based on the response from the endpoint.
- Protection API/Relationship Tuple Write endpoint: An API call to write new policy/permission tuples (X can do Y on Z) after creation or modification of new resources. For eg: After every composition, the openEHR server must make an API call to this endpoint stating that the practitioner who created the composition has permissions to read, modify and delete on the composition. This can easily be configured in a HAPI FHIR server using the Authorization Hook. Still doubtful if EHRBase can be extended in any way to get this functionality. @birger.haarbrandt
Sometimes, the authorization process can be done at the level of the reverse proxy server/Ingress controller directly by using the URL and the HTTP verb along with other information. However, at other times, it’s really complex. Consider executing an AQL on compositions that the user does not have permission to for example, or a Conditional update on a FHIR server. These are complex operations and need integration at the level of the application to provide uniform authorization.
The advantage of this approach is that the administrative work of policy enforcement can be managed using a simple GUI in one place and applications and users can be given the permissions that they need. Sharing resources among users is also possible using Keycloak’s UMA-compliant endpoint. More advanced authorization mechanisms like Zero Trust / BeyondCorp can also be implemented easily.