Why Verily uses FHIRPath
Welcome to Verily’s Tech Blog — a look under the hood at product engineering, and into the key learnings, thought processes and capabilities that enable building Verily’s platform for making healthcare data AI-ready, along with powering other innovations to realize the promise of precision health.
Verily uses FHIR as the foundational data model across our work. FHIR, which stands for Fast Healthcare Interoperability Resources, is the data standard that enables secure, efficient exchange of electronic health data between healthcare systems and applications. But it isn’t just about data exchange — it’s about shared understanding. At Verily, we’ve seen firsthand how FHIR and specifically, FHIRPath, enables real collaboration across expertise and roles. From clinicians to engineers, product teams to data informaticists, FHIRPath ensures everyone can speak the same healthcare data language.
In this first post, we explore how FHIRPath supports that collaboration to accelerate product development and enable scaling that’s smarter, not just faster.
Interoperability is
collaboration
Largely, FHIR is a way for machines to talk to each other about healthcare data. Once in use, it’s also a way for people to talk to each other about healthcare data. With FHIR, medical doctors, software engineers, scientists, and product managers can collaborate using the same set of definitions to build systems that help make people healthier.
For the vast majority of cases, the simplest way to describe a matching record is to use FHIRPath, an expression language based on FHIR. Given this is a universal language used in health technology, its detailed nuances aside, many new Verily employees know what this means before their first day: `Patient.name.given = Jane`.
At Verily, we build software. Ironically, a great way to scale and build products faster, is to remove the need for a software engineer to interpret the intention of domain experts, such as medical, data, or product experts. If these experts are independently able to configure a software system without engineer support to translate their ideas, they can roughly double the pace of implementation. And as expected, we like going faster.
What FHIRPath is, and isn’t —
in brief
At its core, FHIRPath is a filtering language. It takes resources, evaluates an expression on them, and returns any FHIR data type. This makes it perfect for quickly evaluating complicated sets of conditions on collections of resources, and iterating on that set of conditions. It can do other things, such as return booleans, but essentially, this is the same as filtering language.
Be careful!
We’ve had FHIRPath users try to specify queries. And certainly, it seems like it should work. They may ask themselves, “Can’t `Patient.name.given = Jane` return all the Janes in my database?” But in fact, although it’s possible in specific cases to create a query from a FHIRPath expression (and interesting implementations exist), like “Patient’s given name”, it isn’t possible in the general case.
Take this simple modification on the previous example: `name.given`. Evaluated on an array of resources, that expression returns an array of given names. Most databases wouldn’t support this kind of query.
So you can map some FHIRPath expressions to queries. And for such sets, offering support may be useful in your system. But in general, it can’t be done.
More than a language — FHIRPath is a healthcare product development catalyst
Our stack: GCP Healthcare,
pub/sub, protocol buffers, golang
In order to explain why we use FHIRPath the way we use it, we’ll need to give a brief overview of our platform stack.
At Verily, the entire system revolves around a central storage solution with foundations in the GCP Healthcare API (Google’s FHIR API). Because GCP is at our core and our Alphabet DNA runs deep, we define all our network API (and indeed lots of internal APIs) through protocol buffers. This is great news for our native FHIR support, though, because Google maintains an excellent open-source proto-based FHIR implementation. This means that all our language-specific (Golang, Typescript, Swift, Kotlin, etc) internal representations of FHIR objects are all created from a shared definition.
We have dozens of sub-systems that perform independent and parallel data transformations directly in the shared central FHIR storage layer. This goes from internet-fundamentals such as Authz, to fancy algorithms running on unstructured Patient data. Our architecture is all event-driven, resulting in a fire-hose of events that all systems drink from. Each asynchronous sub-system routinely filters through ~10M (and growing…) events per hour.
We leverage the built-in Pub/Sub functionality to publish all database changes on shared publishers. System components are then free to create subscriptions to these publishers, and trigger processes according to their own rules. This is where the filtering capabilities of FHIRPath really become useful for us.
How we use FHIRPath
Event filtering
In our event-driven system, components need to filter out most of the events that come their way. Using FHIRPath, we configure them to listen only to a specific set of events. Essentially, we get a set of input resources, each with their set of FHIRPaths, and process events only if the required conditions match.
For example, take a look at a Golang filter for some Questionnaire Responses that might be relevant to us.
Go
for _, extension := questionnaireResponse.GetExtension() {
if extension.GetUrl() == “http://foo/url” {
if extension.GetValue().GetReference().GetValue() == “foo-value” {
// do something
}
}
}
return fmt.Errorf("extension not found")
It isn’t too complicated, right? Here is its FHIRPath analog.
yaml
fhir_event_conditions:
- path: "meta.extension('http://foo/url').value.reference.value = 'foo-value'"
The differences themselves aren’t that stark. But imagine that you want to modify your existing filter, adding something like, `.meta.tag.code = 'foo-bar-baz'`, which is a very simple idea to express in FHIRPath.
In the Golang case, or any other programming language, you’d need to communicate it to an engineer, who would have to invoke the relevant accessor functions one-by-one, not to mention they’d have to get on their roadmap or iteration/sprint plan.
However, in the FHIRPath case, you simply add an item in the configuration file, and you’re good to go. It might even be de-coupled from your code’s release and deploy cycle. This means an informaticist, for example, could create, modify, test, and deploy configuration changes without needing to coordinate with an engineering team. This is a huge win for productivity.
Testing
FHIRPath has also made our general testing strategy more powerful and clear. Much of our end-to-end automatic tests begin and end in FHIR, meaning that some FHIR event caused a process to run and the resulting artifacts are written back in FHIR. We use FHIRPath to specify the input and output criteria, which gives us 99% of what we need.
Consider the following example:
Scenario: Process Foo happy path: create Foo Task
Given FHIRPath environment variables:
| taskUrl | "'http://fhir.verily.com/workflow/ActivityDefinition/foo'" |
When resources are created in FHIR store:
| patient.json |
And 1 Patient resource satisfies FHIRPath conditions:
| Patient.name.given.exists() |
| Patient.name.family.exists() |
| Patient.address.exists() |
Then 1 Task resource satisfies search conditions matching "Task.instantiatesCanonical = %taskUrl":
| subject | "'Patient/'+%patient.id" |
We benefit from a test that anybody who knows FHIR, Verily-wide, can understand, modify, and even create.
Powerful corollaries
FHIRPath also serves as a basic building block that unlocks a breadth of interesting functionality. Indeed, we’ve built other FHIR-standard tooling on top of FHIRPath, such as FHIRPath Patch, FHIR Graph, and more. And at Verily, these tools have broad adoption and constant use.
Putting pressure on the
implementation
Our FHIRPath package is routinely used to filter multiple millions of events per minute by a dozen sub-systems. It was clear from the start that we would need to trade an expensive compile-time implementation for an economical run-time implementation. Beyond this trade-off, we put in significant energy to ensure complicated FHIRPath expressions would be fast.
Beside processing pressure, FHIRPath also has use-case pressure. Clinical data managers, informaticists, and product managers are becoming FHIRPath native, and use it to define requirements. It nudges us to make our implementation extensible, and build GUIs around it. Building our implementation from the grammar up was instrumental to making this happen.
What we shared, what’s to
come
FHIRPath is more than a language — it’s a healthcare product development catalyst. It generates speed by removing translation layers. It unlocks clarity by making rules transparent. And it fosters collaboration by making healthcare data accessible and malleable to everyone who needs to work with it. For Verily, it's the foundation products are built on — not just for cleaner code or faster testing, but for creating more human-centered health solutions. For example, once a clinical database has conformed to FHIR, any other machines that speak FHIR are able to apply existing analysis to it, accelerating the generation of potential insights.