Porting a Monolithic FHIR Datastore To a HAPI FHIR Microservice
In this blog post, I’ll be detailing our journey in implementing a full FHIR server on top of our existing FHIR infrastructure. In a previous blog post, Vignesh Venkataraman detailed our initial FHIR implementation and I’d recommend giving that a read if you’re interested in the gory details. For reasons that I’ll detail fully in the next section, we needed to evolve our implementation into a fully-fledged FHIR server. At the end of this post, I’ll also go over the technical implementation of our HAPI FHIR server, and some learnings and next steps if you’re interested in building your own FHIR server from scratch!
Why we needed a change
Since our initial FHIR implementation, our need for additional FHIR support has proliferated — in order to unlock things like FHIR data quality testing and connecting to the Health Information Exchange (HIE), we needed more elements of a working FHIR server, especially since several desirable external systems like these utilize FHIR APIs to communicate. More importantly, as our team and codebase scales, we’ve been experiencing an increasing need to evolve our FHIR implementation past the initial infrastructure we initially implemented. In particular:
- Our FHIR implementation was incomplete — intentionally so as to urgently unblock development, but still incomplete nonetheless. We were missing most of the operations that a FHIR server typically supports, instead relying heavily on exactly three endpoints for all our FHIR-related interactions. We wanted the capability to both validate and extend our FHIR implementation without having to build every piece from scratch ourselves.
- Python FHIR support is limited — we were relying on the SMART on FHIR python client-py library for parsing FHIR resources to and from the database, but there is no complete FHIR implementation library for python. Combined with some issues with getting the library on R4, it became clear that we needed a solution that was easy to maintain as the FHIR standard and our use cases evolved. For instance, earlier this year I shipped a feature that integrated historical versioning for FHIR resources, which was a new use of FHIR that I had to build from scratch.
- FHIR had a large learning curve — our scrappy implementation left some tribal knowledge and a good bit of crufty code that made learning FHIR a taller task than it needed to be. Having a dedicated FHIR server forces a decoupling (a la microservices) of FHIR from our backend services, meaning not only will development be well-defined (by strictly interacting with FHIR through an API), but we will have dedicated engineering resources toward FHIR infrastructure and teamwide FHIR literacy. Finally, decoupling should allow us to integrate with external FHIR data sources down the road.
Searching for a solution
It was a relatively short search. In addition to the considerations from above, we really valued:
- Ease of implementation and upkeep, since we are a small engineering team
- Object oriented language, preferably Python or Java
- Minimize (preferably eliminate) schema changes
- In-house data hosting
- Thorough documentation, active online community, and actively developed for the foreseeable future
HAPI FHIR checked all the boxes for us (particularly the Plain Server implementation). In short, the plain server provides a structure by which the developer can implement the FHIR REST operations on top of a pre-existing schema, and HAPI FHIR will handle wiring up the routes. In our case, we had an existing FHIR schema (detailed in a previous blog post), and we could build out the Java entities and HAPI FHIR infrastructure on top of that to get to a functional FHIR server. HAPI FHIR was perfect for us except that…
Our teamwide Java expertise was low, given that we had been developing our backend purely in Python since Curai’s inception. Compounding things, I could not find a single end-to-end, minimal HAPI FHIR implementation tutorial, which made it especially tough to get started. After a few weeks of Googling, coding, and cryptic errors, I was able to piece together several different tutorials to make a minimal example of a HAPI FHIR plain server. Obviously, our production implementation has a lot more bells and whistles, but these will vary from use case to use case so I think it’s most helpful to see a minimal example that you can eventually build on top of. When building your own server, three resources that I found particularly helpful are the FHIR community board, HL7 Confluence page, and DevDays presentations from years prior.
Implementation
Before we jump into the code, below is a diagram of how each part of our FHIR infrastructure exists in our backend right now (simplified for the Patient FHIR resource type). The “Python” box is what our current FHIR systems operate on as described by Viggy in his blog post.The “Java” box is what we’re implementing in this tutorial. As shown, both are simultaneous consumers of the postgres table fhir_patient
, at least until we migrate fully to HAPI FHIR (which will fully deprecate the “Python” box). Let’s get started with how to implement the “Java” box! Step-by-step instructions are below and the full code is here!
Author’s note: for each step, please click on the (Code) link in the title to view the code changes required for each step before running the commands specified in the description!
1. Setting up spring boot (Code)
The first step to setting up our FHIR server is to set up a base Java webapp. To do this, head to Spring Initializr and add “Spring Data JPA”, “Spring Web”, and “H2 Database” (the link provided should have all of them already). Generate and download the bundle, and you have a base web server that can connect to a real H2 database. Later we will connect it to the real Postgres database.
2. Adding in HAPI (Code)
Now we’ll configure our Spring Boot server to work with HAPI FHIR’s server infrastructure. Note that HAPI FHIR’s REST API is configured differently than the typical Java Spring REST API routes, which is why we need to do this step.
Open up the project in IntelliJ (or your favorite Java IDE) and apply the code changes in the section title.
Running ./mvnw spring-boot:run
from the project’s directory will pop up a server, and you should be able to download the server’s (empty) capabilities at localhost:8080/metadata
. Right now, it should indicate that there are no actual capabilities supported because we haven’t implemented anything yet! If you’re getting an error, make sure you have a JDK installed!
Passing around the HAPI FHIR Context (Code)
The HAPI FHIR Context is what we use to parse strings and bytes into Java objects (and back). It’s the core of HAPI FHIR, and we’ll need to use it in every piece of code that relates to HAPI FHIR. Since it is expensive to create, we want to create it just once, and access it from everywhere it is needed. Hence static methods!
3. Creating a Patient Provider (Code)
Let’s make our FHIR server do something! In order to create an endpoint for a resource, we need to implement a provider for the resource. Then, we need to create a method and annotate it with the appropriate HAPI FHIR decorator. This will automatically configure the server to call that method when the route corresponding to the method is hit via the API.
Even though we’re not connected to a real database yet, we can still return some fake data to test our HAPI FHIR server. In this case, we’ll create a fake Patient and implement the read endpoint so we can view the patient.
In this case, the @READ
annotation configured a GET
operation to the Patient endpoint by id, which is accessible at localhost:8080/Patient/1
(sub in any id — we just need a dummy one to trigger the read endpoint) and you should see an XML file with Lord Farquaad’s Patient data.
After applying the code, going back to localhost:8080/metadata
, we should now see that the server supports Read
for Patients. HAPI FHIR configured this automatically, and all future providers and methods will also be noted in this CapabilityStatement
automatically.
4. Connecting Patient to the database
Adding in Postgres (Code)
Now that we know how to interact with the HAPI FHIR to get some dummy data, we can connect the API to an actual Postgres Database. You’ll need to create a local Postgres database with a username and password to supply to the application.properties
config.
Supplying Patient Provider with actual data (Code)
As noted in Viggy’s FHIR post, our FHIR data follows a schema where the entire resource is stored as a jsonb
column. Create a fhir_patient
table in the database with a jsonb
column titled resource
(and an id
primary key column) and seed the table with a fake Patient JSON.
We need to add in support for jsonb
columns, since Hibernate (the Java ORM) does not support it natively — to do this, we’ll use a library called Hibernate-Types!
After applying this diff, when querying for the Patient resource at localhost:8080/Patient/1
, you should get a serialization error. Why? The Java object serialized by Jackson (the default serializer for Hibernate) cannot be cast into a HAPI FHIR Patient instance. We need to configure the serializer to use the HAPI FHIR parser instead. Good thing we can use the HAPI FHIR context that we configured in step 2!
Adding in custom serialization and deserialization (Code)
With this, we tell Hibernate + Jackson to serialize/deserialize Patient types with the custom ObjectMapper that we provide. Going back to localhost:8080/Patient/1
should result in the exact JSON that you seeded the table with!
Next Steps
As I mentioned before, this is an intentionally barebones implementation of a FHIR server meant for you to customize to your own needs — we’re only supporting one operation for one resource, and the code isn’t very generic (it would be tedious to implement the 19 other resources that Curai currently uses). If you’re building your own FHIR server on top of this, here are some areas of work that we’ve built on top of this code:
- Implementing other resources and methods
- Authorization and Authentication
- Resource Validation
- Integration with backend services as a microservice
You may even see some of these as blog posts from us in the coming months!
Kyle’s Takeaways
- Current open-source FHIR infrastructure is tightly coupled with programming language — making it tough to find a solution that completely satisfied our business and engineering needs (primarily python-based). As the health tech open source community grows, I would push for and expect this to change!
- I would also push for more knowledge sharing regarding implementing FHIR infrastructure: both for technical nitty-gritty details and for more transparency and collaboration regarding specific best practices and technical decision making. As an inexperienced health tech developer, it was difficult to scour Google for videos, presentations, and articles to figure out both what I even needed to do, and then also how to implement that!
- Strongly statically-typed languages like Java are fun :)
If you found the contents of this blog post interesting, and / or are interested in working at a health tech company that dreams of changing the world and bringing the world’s best healthcare to every human being on the planet, Curai is hiring! Check out our careers page here.