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.