A FHIR facade answers FHIR-shaped REST requests by querying legacy systems underneath - clients see FHIR, the legacy side stays untouched. When to build one and how.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class PatientFacade(Service):
name = 'demo.facade.patient'
def handle(self) -> 'None':
# A FHIR-shaped request arrived over REST,
# the answer comes from a legacy system
mrn = self.request.payload['identifier']
record = self.get_legacy_record(mrn)
self.response.payload = {
'resourceType': 'Patient',
'identifier': [
{'system': 'urn:mrn', 'value': mrn},
],
'name': [{'family': record['family_name']}],
}
A FHIR facade is a service layer that exposes a FHIR-style REST API while translating each request into queries against systems that do not speak FHIR - HL7 v2 interfaces, databases, proprietary APIs - and translating the answers back into FHIR resources. Clients get the API they expect, and the legacy system is not modified, migrated or even aware the facade exists.
The facade holds no clinical data of its own - every answer is assembled from the source systems at request time, which is what distinguishes it from a FHIR server with a store behind it.
Build a facade when the data already lives in systems that must remain the source of truth, when you need FHIR read access quickly, or when standing up and operating a clinical data repository is out of scope. The facade is a set of services - no data migration, no synchronization, no second copy of the record to govern.
Build or buy a full FHIR server when consumers need the full breadth of FHIR search semantics, history and versioning, profile validation, subscriptions, or persistence of resources that have no other home. A facade that keeps growing FHIR server features is a sign the project needs a full FHIR server.
Zato's role is the facade - it exposes REST APIs in front of anything, but it does not host or persist a FHIR server, does not validate against profiles and does not generate narrative. Both patterns also combine - a FHIR server for the resources you own, facades for the systems you wrap.
Legacy systems push their data as HL7 v2 feeds, so the facade is two services. The first receives the ADT feed over an MLLP channel and keeps a queryable copy of the demographics. The second answers FHIR-shaped reads over a REST channel from that copy, with the v2-to-FHIR field mapping following the mapping reference.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class FeedIntake(Service):
""" Receives the legacy ADT feed over an MLLP channel
and keeps a queryable copy of the demographics.
"""
name = 'demo.facade.feed-intake'
def handle(self) -> 'None':
# The channel already parsed the raw ER7 bytes ..
msg = self.request.input
# .. index the demographics by the MRN.
mrn = msg.pid.patient_identifier_list.id_number
self.cache.set(f'patient:{mrn}', {
'family_name': msg.pid.patient_name.family_name,
'given_name': msg.pid.patient_name.given_name,
'date_of_birth': msg.pid.date_time_of_birth,
})
class PatientRead(Service):
""" Answers FHIR-shaped Patient reads, exposed through a REST channel
as GET /facade/Patient?identifier=...
"""
name = 'demo.facade.patient-read'
def handle(self) -> 'None':
# The MRN the FHIR client is asking about
mrn = self.request.payload['identifier']
# The copy maintained from the ADT feed
record = self.cache.get(f'patient:{mrn}')
date_of_birth = record['date_of_birth']
birth_date = f'{date_of_birth[0:4]}-{date_of_birth[4:6]}-{date_of_birth[6:8]}'
# Answer with a FHIR-shaped Patient
self.response.payload = {
'resourceType': 'Patient',
'identifier': [{'system': 'urn:mrn', 'value': mrn}],
'name': [{
'family': record['family_name'],
'given': [record['given_name']],
}],
'birthDate': birth_date,
}Where the legacy system must receive messages too - orders, updates - the facade writes back through an outgoing MLLP connection, with self.mllp['legacy-pas'].send(data) returning the acknowledgment result that says whether the legacy side accepted the message.
The facade itself stays stateless - every request contains what is needed to answer it, so instances scale horizontally and restarts lose nothing. Latency comes from the legacy side, and caching absorbs it - demographics that change rarely can be cached for minutes, with the scheduler refreshing hot entries in the background instead of making a client wait for a slow legacy round trip.
Acknowledgment handling matters too - the AckResult returned by outgoing MLLP calls says whether the legacy system accepted the query, so failures surface as proper HTTP errors instead of empty resources.
Yes - that is what a facade provides. The limits are a matter of scope - a facade answers the reads and searches you implement, it is not a general-purpose FHIR endpoint with full search semantics, history or validation.
It is not a persistent store - no resource history, no versioning, no subscriptions, and search is only as capable as the legacy interface underneath. When consumers need those, put a full FHIR server behind the integration layer.
The REST channel has a security definition - Basic Auth, API keys or OAuth - like any other Zato REST API, independently of how the facade talks to the legacy systems behind it.
Facades provide FHIR access now, migrations remove the legacy system later. The two compose - the facade gives consumers a stable FHIR API, and when the legacy system is eventually replaced by a FHIR-native one, the facade's consumers do not notice.
Get started with Zato and expose your first FHIR-shaped endpoint in minutes.