Misc thoughts on FHIR

This year we completed a project in the medical space where we leveraged FHIR as the data storage mechanism. FHIR is the Fast Healthcare Interoperability Resources specification and defines a data model, interchange format (xml or json) and REST API specification for disparate systems to exchange healthcare data. Previously, every medical system would expose its own APIs and data interchange mechanisms, and so to write some middleware syncing things together, you needed to speak a lot of protocols and translate between them. With FHIR, each system should ostensibly speak the same protocol and simplify a lot of work for the implementer.

Here’s some miscellaneous observations from working with it.

Complex

Medical data is complicated, who would have thought!

It’s pretty tough for someone new to the medical domain to know where to store pieces of data.

How is an Observation, Specimen, ServiceRequest related? The spec describes the relationships in pretty mechanical terms (is-a, has-a relationships) but the domain knowledge behind that understanding takes time to develop. It can be confusing to figure out where you’re going to stick the information.

The complexity of the data model, however, forces you to think about how things are actually complicated in the real world! For example, a Patient does not have a name, as I might naively model it. They have a list of names, given names, common names, nick names, family names. You’re forced to acknowledge this and handle your data accordingly.

Be prepared to dig around in lists, a lot

Speaking of names, almost everything in FHIR is a list. There seemed to be very view 0..1 relationships between entities, almost everything was 0..n, which meant the client library modeled it as a list. There was a lot of hunting through lists looking for the element with the expected system or id identifiers.

Extensions

One of the most common list elements we had to search through were “Extensions”. FHIR recognizes the need to store additional data about its resources that they couldn’t plan ahead for. Imagine app-specific metadata that is not a standard medical domain concept: the most obvious place to store that is in each Resource’s extension data which is basically a key-value store of arbitrary data. But remember, everything is a list, so you don’t get a nice Dictionary<string, string> interface into extensions, you get a List, and you’ll write a lot of code looping through items.

The value of an extension can be any core datatype, a Reference to another resource, or even another nested Extension or (of course) a list of extensions. Turtles all the way down!

RESTy goodness

FHIR has some good ideas on REST API design. POST, PUT, PATCH, GET, DELETE all have well defined semantics. For example, if you have a client-provided identifier (like a GUID you know ahead of time) use PUT, but if you want the server to generate an identifier, use POST.

Everything is URL identified, so when there are links between resources its not just like "patientId": "abcdef", but "patient": "https://host/Patient/abcdef".

I’d never seen this before in other REST APIs, but FHIR defines a TransactionBundle for making multiple transactional operations. Imagine you need to create and upsert a set of different resources, and you want all that to happen under the same database transaction: either all succeeding or failing together. In most REST apps I’ve worked on you’re just out of luck. But FHIR has a Resource for a bundle of related relationships, like cramming multiple API calls into a single giant JSON blob and the server can run it as a database transaction. Nifty!

Similarly, Search returns a Bundle, which can have lots of different resources in them. However, it’s up to app code to join them back together.

DDD - Value Objects

FHIR defines a lot of primitive types, which I thought was neat. Instead of just having a number type, they have PositiveInt, Quantity, FhirString, FhirDate, Range, and other primitives that are constrained to important domain concepts. Sometimes they were hard to work with, but they forced us to store valid data and to consider the real complexity of modeling healthcare data.

Document Storage makes the most sense

The specification is very document-oriented, it considers a Patient and its ServiceRequests as different resources with different APIs. Semantically (in our domain at least), a ServiceRequest is nested under a Patient (Patient has-many ServiceRequests) but the shape of the actual JSON does not reflect that, they are separate resources and are linked together with the Reference primitive type.

It would be hard to implement this spec against a relational database.

The provider we were using did use relational database under the hood though it just stored compressed JSON in a single Resources table. This design did cause us some issues in search performance, but gave us access to the TransactionBundle mentioned above.

It’s neat to see it in the wild

Here’s Apple Health showing my COVID booster raw FHIR data it got from my local health provider’s system. This is an Immunization resource, you can see how it has reference to my Patient record, and some Coding about what vaccine it was.