Architecture kata · K—03
Regional Health Information Exchange
Architecture kata · design study on a fictional brief
- Sector
- Healthcare
- Topic
- Reliable data
- Technologies
- Apache Camel, Apache Kafka, HAPI FHIR, PostgreSQL, Splink, Open Policy Agent, Keycloak, Apache APISIX, immudb, Ceph, Apache Flink, Trino

In short
Eight EHR systems into one FHIR record per patient, reversible patient matching, consent on every read, ten-year audit trail.
Problem
A regional health authority runs 40 hospitals and 600 clinics for 4 million patients. Eight different electronic health record (EHR) systems emit HL7 v2 messages, FHIR, or only CSV and PDF files. A clinician in one hospital cannot see history from another. A new law requires a shared patient summary and citizen-controlled consent. About 15% of patients have no national identifier. The volume is about 20 million messages per day, and no message may be lost.
Approach
Two constraints shape the design: eight heterogeneous sources, and consent that must hold on every read, including bulk reads. The design keeps the raw message unchanged and converts it to one canonical FHIR R4 model. Identities are linked reversibly, and consent is enforced as a policy at read time. One Apache Camel adapter per EHR writes raw messages to Kafka. The adapter acknowledges the sender only after Kafka confirms the write.
Architecture
- Ingestion: Camel adapters for HL7 v2, FHIR and files. Kafka keys are source plus record number, so order is kept per patient.
- Canonical store: a transformation service maps to FHIR profiles and writes to HAPI FHIR on PostgreSQL with idempotent transactions. Raw bytes stay in Ceph with Object Lock.
- Identity: Splink probabilistic matching feeds a link table, so a merge is a row that can be undone.
- Access: Keycloak for SMART on FHIR, APISIX as the only door, Open Policy Agent for consent.
- Audit and research: every read becomes an audit event in immudb. A Flink job de-identifies data for the university.

Why these components
| Need | Choice | Why |
|---|---|---|
| Source adapters | Apache Camel with HL7, FHIR and FTP components | One stateless adapter per vendor, with HL7 v2 over MLLP, FHIR and file handling ready to use. Vendor quirks stay in the adapter. Mirth Connect moved to a closed licence in 2024. |
| Event backbone | Apache Kafka on Strimzi | A durable, replayable log ordered by source plus record number, so order holds per patient. The acknowledgement goes back only after commit. Redpanda was ruled out because of its Business Source License. |
| Schema and profile contract | Apicurio Registry and a FHIR Implementation Guide | Raw envelope schemas and regional FHIR profiles are versioned, and incompatible changes fail in CI. Confluent Schema Registry was ruled out because of its Community License. |
| Canonical clinical store | HAPI FHIR JPA Server on PostgreSQL | The FHIR R4 reference implementation, with search, versioning and interceptors ready. Consumers never see a vendor format. IBM FHIR Server is no longer maintained; Smile CDR offers commercial support. |
| Patient matching | Splink with an own matching service and a PostgreSQL link table | Probabilistic matching is required for the 15% without a national ID. A merge is a row with its evidence and can be undone. Commercial master patient indexes such as IBM Initiate and Verato are closed. |
| Consent policy | Open Policy Agent with Rego | Consent, identity, organisation and purpose of use in one policy bundle, run by the API, bulk export and research alike. Consent tables inside the data layer leak data in bulk reads. |
| Identity and authorisation | Keycloak with SMART on FHIR and OpenID Connect | Hospital identity federation, national e-ID brokering, SMART scopes and a purpose-of-use claim. Apache 2.0 with Red Hat support. Ory Hydra is the alternative. |
| API gateway | Apache APISIX with an OPA plugin | Token validation, the policy call, rate limits and audit headers. The FHIR server is reachable only through the gateway. APISIX, because Kong OSS moved features out of its open edition. |
| Audit ledger | immudb | A cryptographically verifiable, append-only record of who saw what and why, kept ten years and shown in the patient app. Amazon QLDB was ruled out: closed and tied to one cloud. |
| Cache | Valkey | Sub-millisecond reads for consent data and the precomputed patient summary. The cache key includes the consent version. Valkey over Redis because of the Redis 7.x licence change. |
| Raw archive and documents | Ceph RGW with S3 Object Lock | Raw HL7 and FHIR messages and PDFs as immutable objects for ten years, and the source for replay. Ceph, because the MinIO community edition narrowed. |
| Research pipeline | Apache Flink, Apache Iceberg, Trino | De-identification in the stream, one Iceberg table per project, and the university queries through Trino. Raw data never goes to the university. |
| Secrets and keys | OpenBao | The re-identification key is reachable only under the trusted third party’s policy. Also PKI and database secrets. HashiCorp Vault was ruled out because of its Business Source License. |
| Observability | OpenTelemetry, Prometheus, Grafana, Loki, Tempo | Flow health per vendor, end-to-end freshness, matching rates, API p95 and audit write delay on one dashboard. |
Decisions and trade-offs
- Consent in OPA, not inside the FHIR server. One policy bundle must run in the gateway, the FHIR search interceptor and the Flink research job. Authorisation embedded in HAPI would have to be rewritten for research. OPA returns a filter, not allow or deny, so bulk export is filtered per record. The cost is another component to operate. If consent applied only at the API, HAPI’s own interceptor would do.
- Matching favours false negatives. Scores of 0.99 and above link automatically. Between 0.85 and 0.99 the records wait for a human. A false merge puts another patient’s allergies into the summary. A false negative means missing history, which is today’s state anyway. The cost is a human review queue. A real-time national ID lookup would shrink the probabilistic share.
- Patient summary precomputed at write time. One read keeps p95 under 500 ms; the cold path needs eight FHIR searches. The cost is recomputation on every change, plus cache invalidation. A consent change clears the cached summary within seconds, and the cache key carries the consent version, so an old version never hits.
- Emergency access is not blocked. Break-the-glass needs a justification, a privacy officer review within 48 hours and a weekly check, and the patient sees it in the app. Blocking would harm patient safety. Not logging would harm trust. The cost is that control happens after the fact.
- Keep raw bytes and replay, never ask the hospital again. The adapter sends its acknowledgement only after the Kafka commit. Raw messages stay 30 days in Kafka and ten years in Ceph. When a transformation bug appears, I fix the adapter and replay. Writes are conditional updates by source identifier, so replay is safe. The cost is storage.
- Warm standby, not active-active. Active-active PostgreSQL would need multi-master writes and would strain FHIR versioning. A warm standby with streaming replication meets the two-hour recovery target and is far simpler. The cost is a small window: acknowledged messages not yet copied are re-requested from vendors by message ID range.

What I would revisit
- Vendor delivery behaviour. Do the five HL7 v2 vendors really wait for the acknowledgement and retry, or fire and forget? The no-loss guarantee and the re-request by message ID depend on it.
- National ID registry. Is there a real-time lookup, and with what service level? Without it, matching stays fully probabilistic.
- Sensitive data categories. Which categories does the law define beyond mental health, sexual health and genetics? Mapping them to FHIR security labels needs clinical sign-off.
- Consent default. Opt-out or opt-in does not change the architecture, but it sets day-one behaviour and communication.
Outcome notes (design assumptions, not measurements)
This is a design exercise, not a running system. The output is a four-page diagram set, a one-page whiteboard version and a 15-page Turkish PDF. Capacity figures, such as about 230 messages per second on average, are arithmetic from the kata brief, not measurements.
Sources
- Architecture PDF (Turkish, 15 pages).
- draw.io diagrams (English labels): four-page set and a one-page 30-minute version.
Real projects on this topic
P—01
Completed
Featured
Enterprise Data Warehouse Migration to Google BigQuery
80 TB warehouse moved from SQL Server and a 15-node Cloudera cluster to BigQuery: $550K annual savings, 75% lower infrastructure cost, 7x faster workflows.
Role
Design and lead · Senior Data Engineering & Data Architecture Group Manager
P—08
Completed
Data Governance, Metadata Management and MDM
Data governance, metadata management and master data management (MDM) across 100+ source systems, with data catalog and lineage tooling.
Role
Implementation · Big Data Management & Data Architecture Manager