Build the pipeline for speed, but trust the pipeline for correctness
The first time I wired Kafka into a clinical analytics stack, I made the same mistake I see teams make over and over: I optimized for throughput before I solved semantics. The dashboards looked alive. The care managers were not impressed. We were counting dropped updates, duplicated encounters, and late-arriving observations like they were the same thing. They are not.
If you want real-time clinical analytics to hold up in care operations, you need a pipeline that understands clinical data as events, not just rows. That is where AWS HealthLake and Apache Kafka fit cleanly together. Kafka gives you durable event flow and consumer decoupling. HealthLake gives you FHIR-native storage, indexing, and queryability. The trick is designing the seam between them so you can ingest fast, index correctly, and serve dashboards without turning every screen into a custom data project.
Start with the clinical event model, not the database
Most teams start by asking, “What tables do we need?” I start with, “What clinical event are we representing, and what must be true when it lands?” A blood pressure reading, a medication administration, an encounter update, and a discharge summary all have different latency, deduplication, and provenance requirements. If you flatten them too early, you lose the operational meaning.
In practice, I structure the pipeline around three layers:
- Ingestion events from source systems, usually in FHIR R4 resources or mapped to FHIR.
- Stream processing for validation, enrichment, and routing.
- Clinical serving through HealthLake and downstream analytics stores or APIs.
This separation matters because the stream is not the truth. The canonical clinical resource is the truth. Kafka carries the event; HealthLake stores the resource; the dashboard consumes a derived view.
Kafka topic design that does not collapse under clinical load
Topic design is where I see teams sabotage themselves. They create one giant topic named clinical-events and call it architecture. That works right up until a lab result floods the same channel as a care-plan update.
I split topics by clinical domain and operational behavior:
fhir.patient.v1fhir.encounter.v1fhir.observation.v1fhir.condition.v1fhir.medicationrequest.v1fhir.audit.v1
That is not just naming hygiene. It gives me:
- domain-specific retention policies
- clear consumer ownership
- predictable partitioning keys
- isolated backpressure when one stream gets noisy
Partition by the clinical entity you need to order. For observation streams, I usually partition on patientId or a stable encounter identifier, depending on how the dashboard needs to display chronology. If the UI is patient-centric, partition by patient. If the workflow is visit-centric, partition by encounter. I have seen teams partition by message ID and then wonder why the timeline is scrambled.
HealthLake belongs at the center of the FHIR truth layer
AWS HealthLake is useful because it understands FHIR as more than JSON decoration. It stores and indexes FHIR resources so you can query operationally, not just archive clinically. For this architecture, I treat HealthLake as the indexed clinical system of record for the streaming layer.
The flow I prefer looks like this:
- Source system emits a FHIR resource, or a transformer maps its payload into FHIR.
- The event lands in Kafka with metadata for source, facility, tenant, and resource version.
- A stream processor validates the resource against expected shape and routing rules.
- The processor writes the canonical FHIR resource into HealthLake.
- HealthLake indexes the resource for retrieval by patient, encounter, resource type, and search parameters.
The awkward truth is that indexing is not free. If you push every possible enrichment into the write path, you create latency spikes and operational noise. We learned that the hard way when a well-meaning enrichment step dragged external reference lookups into the critical path. The fix was simple: write the clinical resource first, enrich after, and update secondary views asynchronously.
How I handle real-time dashboard delivery
Care operations dashboards do not need raw streams. They need fast answers to specific questions: which patients need review, which orders are pending, which encounters are stalled, which alerts are aging out. That means the dashboard layer should read from purpose-built projections, not query Kafka directly and not hammer HealthLake for every render.
I use one of two patterns:
- Materialized operational views in a low-latency analytics store for repeated dashboard queries.
- API aggregation over HealthLake for smaller workflows where freshness matters more than heavy filtering.
For care ops, I usually build a projection service that listens to Kafka, maintains current state, and serves the dashboard through an internal API. HealthLake stays the canonical indexed FHIR layer. The projection is the operational face of the data. That keeps the UI snappy and lets me change dashboard logic without rewriting ingestion.
What the dashboard should never do
- poll raw topics for each widget
- recompute state from scratch on every page load
- depend on batch exports for “near real-time” visibility
- mix source-of-truth writes with read-heavy UI traffic
Where streaming clinical pipelines usually break
The common assumption is that streaming solves freshness automatically. It does not. Streaming only moves the problem earlier. You still have to solve identity, versioning, late arrival, and duplicate suppression.
I use three controls that matter more than fancy tooling:
- Idempotency keys based on source system identifiers and resource version.
- Clinical identity resolution so patient and encounter references stay stable.
- Dead-letter handling with human-readable error reasons, not just stack traces.
For FHIR specifically, versioning is the sleeper issue. A resource can be valid and still be semantically stale. If a new Observation supersedes an old one, the dashboard must know which one to show. That is why I keep event metadata explicit in Kafka and do not rely on payload inspection alone.
Reference architecture I actually trust
If I were implementing this today, I would use this flow:
- Source systems emit FHIR or mapped clinical events.
- API Gateway / ingestion service validates auth, tenant context, and schema.
- Kafka brokers clinical domain topics with partitioning by patient or encounter.
- Stream processor validates, deduplicates, enriches, and routes.
- AWS HealthLake stores canonical FHIR resources and maintains searchable indexes.
- Projection service builds operational views for care dashboards.
- Dashboard/API layer serves clinicians and operations teams in near real time.
That architecture is not flashy. It is durable. It lets you scale ingestion independently from serving, and it gives the clinical side a stable source of truth instead of a constantly mutating dashboard cache.
AST and the difference between “integrated” and “assembled”
I care a lot about who owns the full chain because clinical analytics fails in the seams. At AST, the Integrated Engineering Pod model matters because the same team thinking through FHIR ingestion is also thinking through dashboard latency, cloud security, and how a care team will act on the data. I have seen this in real deployments: once the pod owns both the stream and the user experience, the design gets simpler, not more complicated.
The pattern is straightforward: Kafka for event movement, HealthLake for FHIR truth, projections for operational speed, and strong clinical ownership for every data contract. When those are aligned, you get a pipeline that feels real-time to the people using it, not just to the engineers watching lag metrics.
If you are building this now, do not start with a generic data platform. Start with a clinical question, model the event, and make every hop earn its place.
Want to design a real-time clinical analytics pipeline that holds up in production? Book a discovery call with AST and we will map the architecture, FHIR flow, Kafka topics, and HealthLake serving layer together.





Comments
Comments are warming up. Live, no-sign-in discussion will appear here shortly.
Have a question now? Email info@allstartech.net.