The four-plane architecture
The domain controller is the centre of gravity, but on its own it is just identity. A real hospital is identity plus the clinical systems that consume it, the activity flowing through those systems, and the plumbing that connects them. The lab models all four as separate, reproducible Docker stacks, each its own repository, joined by shared networks.
The four planes
Section titled “The four planes”flowchart LR
subgraph identity["Identity plane"]
dc["Samba AD DC · dc1<br/>Kerberos · LDAP · DNS"]
end
subgraph application["Application plane"]
emr["OpenEMR<br/>emr.l.supported.systems"]
db[("MariaDB")]
emr --- db
end
subgraph activity["Activity plane"]
sim["Simulated Hospital<br/>sim.l.supported.systems"]
end
subgraph integration["Integration plane"]
oie["OpenIntegrationEngine<br/>integration.l.supported.systems"]
end
emr -. "LDAPS bind-as-user" .-> dc
sim -- "HL7v2 / MLLP" --> oie
oie -- "JDBC write" --> db
| Plane | Stack | Repo | What it is |
|---|---|---|---|
| Identity | Samba AD DC (dc1) | samba-domain-controller | The Kerberos/LDAP domain — users, groups, the PHI access boundary |
| Application | OpenEMR + MariaDB | gh-openemr | A real electronic medical record clinicians log into |
| Activity | Simulated Hospital | gh-simhospital | A synthetic HL7v2 generator — a hospital that is always “open” |
| Integration | OpenIntegrationEngine | gh-integration | The HL7 interface engine that routes activity into the record |
Each plane is disposable and defined in code. You can run just the DC, or the DC plus the EMR, or the whole ecosystem.
Why an interface engine
Section titled “Why an interface engine”The instinct is to point the HL7 feed straight at the EMR. Real hospitals do not do this, and neither do we. OpenEMR’s native HL7v2 inbound is weak; its strength is being the clinician-facing chart. So an interface engine sits in the middle to receive, acknowledge, transform, and route — exactly the role Mirth Connect plays in production hospitals. We use OpenIntegrationEngine, the FOSS fork that continues Mirth after it went proprietary at version 4.5.
Simulated Hospital ──MLLP (HL7v2)──▶ OpenIntegrationEngine ──JDBC──▶ OpenEMR (activity) (integration) (application)This is the faithful topology, and it means each piece stays good at one job: the simulator generates, the engine integrates, the EMR presents.
How activity becomes a chart
Section titled “How activity becomes a chart”The simulator follows YAML pathways — believable patient journeys (ED chest pain, acute kidney injury, surgical admission). Each step emits HL7v2 messages over MLLP on port 6661. The engine’s single channel parses every message and writes the corresponding OpenEMR records, all idempotent:
| HL7 segment / event | OpenEMR destination |
|---|---|
PID | patient_data (the patient) |
PV1 | form_encounter + forms (the visit, keyed on visit number) |
OBR / OBX | procedure_order → procedure_report → procedure_result (labs) |
AL1 | lists type allergy |
DG1 | lists type medical_problem |
PR1 | lists type surgery |
ADT^A02 | transfer — updates the encounter |
ADT^A03 | discharge — sets date_end + disposition |
ADT^A11 / A13 | cancel admit / cancel discharge |
The result is a chart that fills itself: open a synthetic patient and you see their allergy, their diagnosis, their surgery, and their lab panels with reference ranges and abnormal flags — none of it hand-authored, all of it arriving as live HL7 traffic.
One identity, one login
Section titled “One identity, one login”The application plane closes the loop back to identity: clinicians log into OpenEMR with their Active Directory credentials over an encrypted LDAP connection, binding as the user themselves. OpenEMR never stores the password — disable an account in AD and the chart login is gone too. See OpenEMR ↔ AD authentication.
Two ways data arrives
Section titled “Two ways data arrives”Both production paths land in the same tables and render through the same OpenEMR widgets, which is the mark of doing the integration correctly:
- The live stream — synthetic patients flowing in continuously through the HL7 pipeline.
- A curated caseload — a small, hand-seeded “diagnostic caseload” assigned to a specific physician, for a stable set of recognizable charts in a demo.
OpenEMR cannot tell them apart; both are just rows it would have written itself.
FHIR (experimental)
Section titled “FHIR (experimental)”The simulator can also emit FHIR R4 resources. In this lab the FHIR export
works for Patient, Encounter, AllergyIntolerance, Condition, Location,
and Practitioner, but not Observation — a google/fhir library-version
incompatibility in the from-source build breaks the lab-value (Quantity)
marshalling. HL7v2/MLLP, the path the lab actually uses, is unaffected. The full
root cause is in the gh-simhospital README.
Networks
Section titled “Networks”samba— the bridge sibling app servers use to reach the DC for LDAP.hl7-bus— a dedicated bridge carrying the MLLP feed (Simhospital → OIE) and the JDBC write (OIE → OpenEMR’s MariaDB). The MariaDB joins it but is never published to the host.caddy— the edge reverse proxy fronting every web UI on a*.l.supported.systemshostname.
To bring the planes up and wire them together, see Run the clinical ecosystem.