When working with FHIR data from within an application, we must decide on how to write updates back to the FHIR server. Generally, there are two strategies for doing this:
- Inline Updates: Everytime a change is made, immediately save that change back to the server
- Bulk Updates: Track changes and save them all in bulk based on a certain event, such as a user pressing
Save
, or the application being closed
Inline Updates
Inline updates mean that any time a change is made, that change is immediately saved back to the server. In this case, the application is nearly always synchronized with the server and limits some risks, such as the application crashing and all changes being lost, or the data becoming stale.
This method also tends to be simpler as it doesn't require any additional code for tracking changes.
Example Code (with Plasma)
Take, for example, an application that allows users to manipulate a patient's allergies. Any time an action is performed (user adds a new allergy, user changes something about an allergy, etc.), the new data will be saved back to the FHIR server. Below is some sample Plasma code that may work for this case.
async function refreshData() {
this.data = await this.plasma.readAllergyIntolerance(patientId);
}
async function createAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.createResource<r4.AllergyIntolerance>(allergy);
await this.refreshData();
}
async function deleteAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.deleteResource(allergy);
await this.refreshData();
}
async function updateAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.updateResource(allergy);
await this.refreshData();
}
As you can see, the code is very simple. We have a function, refreshData
that loads data directly from the FHIR server and assigns it to this.data
. Any time any CRUD operations are performed, we simply call the appropriate function and then refresh the data back from the server.
Pros/Cons
Pros:
- Simple to implement
- Protect against unexpected crashes
Cons:
- User workflows are slower due to waiting for loading
- Server updates may not appear immediately
The second item in the Cons list, server updates may not appear immediately, is a major problem with this approach. Very often, you may submit a change to a FHIR server, and that change may not appear for several seconds or several minutes.
So, for example, in the code above, we may create an allergy and then call refreshData()
to overwrite this.data
with whatever is on the server. However, that new allergy may not appear on the first call to refreshData()
. This means that the allergy that was just created won't appear to the user and will result in a confusing user experience. Furthermore, it's not clear when the new allergy actually will show up, meaning our application will not know when it should call refreshData()
.
For this reason alone, we recommend using the Bulk Updates method for writing back FHIR data.
Bulk Updates
Bulk updates mean that changes are tracked locally within the application and then saved all at once after a triggering event. That event could be when the user presses a "Save" button. Or it could be right as the user closes the application.
This method results in a more responsive user experience compared with the Inline approach since there will be no network calls in between actions. However, this approach is more complex as it requires keeping track of some type of changes data structure.
FHIR provides a method to execute numerous actions as a transaction, which is great for this use-case.
One additional complexity with this approach is that there is a greater likelihood that the underlying FHIR data has been modified by another user and there may be data conflicts when attempting to save.
Example Code (with Plasma)
Take again, for example, an application that allows users to manipulate a patient's allergies. In this case, we will track changes made to the data throughout the course of the application, and then save those changes in bulk when the save()
function is called. Below is some sample Plasma code that may work for this case.
async function init() {
this.data = await this.plasma.readAllergyIntolerance(patientId);
this.deletedIds: string[] = [];
this.modifiedIds: string[] = [];
this.createdAllergies: r4.AllergyIntolerance[] = [];
}
function createAllergy(allergy: r4.AllergyIntolerance) {
this.createdAllergies.push(allergy);
}
function deleteAllergy(allergy: r4.AllergyIntolerance) {
this.deletedIds.push(allergy.id);
}
function updateAllergy(allergy: r4.AllergyIntolerance) {
this.modifiedIds.push(allergy.id);
}
async function save() {
let bundle = plasma.startBundle();
// Add all newly created allergies...
this.createdAllergies.forEach((allergy) => {
bundle = plasma.addWriteToBundle(bundle, "create", allergy);
});
// Add all deleted allergies...
this.deletedIds.forEach((id) => {
const allergy = this.data.find(x => x.id === id);
bundle = plasma.addWriteToBundle(bundle, "delete", allergy);
});
// Add all modified allergies...
this.modifiedIds.forEach((id) => {
const allergy = this.data.find(x => x.id === id);
bundle = plasma.addWriteToBundle(bundle, "update", allergy);
});
await plasma.executeBundle(bundle);
}
Notes:
- We track created, updated, and deleted records separately since they have different associated actions for each
- For updated and deleted records, we only need to track the ID since the IDs are unique. Newly created records do not yet have IDs, so we track the entire object itself
- We use a
Bundle
to perform all the actions at once as a transaction
Dealing with Conflicts
If data has been modified by another user in between the time that your application loaded data from the server and the time that your application's user wishes to save the data, you must account for any possible conflicting data.
Similar to how source code is merged, there may be some cases in which conflicts can be resolved programmatically. However, there may be some conflicts which must be resolved by a human being.
This can be a complicated process, but the Plasma Health framework has some guidelines and tools to assist. This will be the subject of a future blog post.
Conclusion
- To provide the best user experience, we recommend tracking all changes and submitting them at once when ready to save
- We recommend using a
Bundle
to submit all changes as an atomic transaction - Be cautious about data that may have changed as attempting to save data could result in a data conflict