Concepts
How Brio-IO is built: channels and connectors, the pipeline, canonical HL7v2 XML, Git as the configuration source — and branches with shadowing and comparison, including today's limit.
Last updated: Examples verified against brio-server fa237ec7ad
This chapter explains the building blocks every other chapter refers to. It describes what runs today — not the plan.
Channel and connector
A channel is a processing path. It has a source connector, a chain of processing steps and one or more destinations:
Source connector
↓
Filter (per message: process or drop)
↓
Transformer (rewrite the message)
↓
Destination connector(s)
Connectors are first-class objects, not sub-fields of a channel. They are
created separately and referenced by the channel — so the same http-listener
can serve several channels, and a connector can be tested and changed
independently of the channel.
Every connector has a protocol (mllp-listener, file-reader,
http-sender, …) and a set of typed properties. Which protocols exist and
which properties they take is in the
Connector reference.
The message passes through stages — and all of them stay visible
Brio-IO does not store a message only in its final state, but in stages: RAW
(as it arrived), TRANSFORMED (after filter and transformer), SENT (what went
out). On top of that come states that usually stay invisible:
FILTERED— a message a filter dropped. It shows up as its own row instead of disappearing without trace.ERROR— processing failed; the reason is in the message notes.SHADOW_SENT— the message ran in a shadow branch (see below).
Every message also carries an origin, saying which branch it came from. That is why shadow traffic does not distort the live channel’s counters.
Canonical XML: one message, one shape
As soon as an HL7v2 message arrives, Brio-IO converts it into a canonical XML representation. Scripts work on that shape, not on the raw ER7 string with its pipes and carets.
Two properties of that shape matter, because both correct historical mistakes:
Underscores instead of dots. In the classic Mirth world the field is called
PID.5 — and the dot is the property access operator in every programming
language, which is why you have to use string brackets there. Brio-IO separates
levels with an underscore (PID_5), so element names are ordinary identifiers.
Components numbered positionally. A component is named after its position, not after its HL7 data type:
<PID>
<PID_3>
<PID_3_1>12345</PID_3_1>
<PID_3_4>MRN</PID_3_4>
</PID_3>
<PID_5>
<PID_5_1>Schmidt</PID_5_1>
<PID_5_2>Hans</PID_5_2>
</PID_5>
</PID>
PID_5_1 is the family name — field PID-5, component 1. Exactly the numbering
HL7 describes and a Mirth practitioner has in their head.
Important for existing scripts: an interim version of Brio-IO named components after their data type (
PID_5→XPN_1→FN_1). Those paths no longer work — they silently return an empty string. When a channel is deployed, Brio-IO warns by name if a script still contains such data-type paths. The reason for a loud warning instead of a silent alias: two simultaneously valid spellings were exactly the problem this rework ended.
Escape sequences (\S\, \T\, …) stay verbatim in the XML. To resolve or
produce them in a script, use escapeHl7/unescapeHl7 — see the
Transformer handbook.
Three script engines, one set of built-ins
Filters and transformers are scripts. Brio-IO runs three engines in parallel; you choose the engine per script, not per channel:
| Engine | Identifier | Language | Role |
|---|---|---|---|
| Groovy | groovy | Apache Groovy | default for new scripts |
| GraalJS | js-modern | modern JavaScript | for those who prefer JS |
| Rhino | js-legacy | JavaScript with E4X | only for imported Mirth scripts |
The built-ins are the same in all engines: msg, xml, channelMap,
sourceMap, responseMap, globalMap, globalChannelMap, tmp, logger,
xslt. Scripts are compiled at deployment, not per message.
Details and examples: Transformer handbook.
Git is the configuration source
A Brio-IO server’s configuration — channels, connectors, scripts, code templates — does not live in a database table but in a Git repository the server maintains itself (embedded JGit; an optional remote is possible).
That is not cosmetic. It is the reason for several properties you would otherwise have to build:
- Every change has a commit. Who, when, what — without a separate audit feature.
- Rolling back means rolling back, not “restoring a backup”.
- Branches are real Git branches — and therefore the basis of the next section.
Where the repository lives is controlled by brio.config.git.local-path; see
Operations.
Branches: shadowing and comparison
A configuration change is rarely harmless in integration work: a modified transformer meets real traffic, and classically you only find out afterwards whether it did the right thing. Brio-IO addresses this with branches.
Shadowing. A branch can observe the running channel’s real traffic and
execute the pipeline on a live copy — without calling a real destination.
The results land as SHADOW_SENT in the message browser, cleanly separated from
live traffic by origin.
Comparison. The comparison puts what main and the branch produced side by
side, message by message, and reaches a verdict:
| Verdict | Meaning |
|---|---|
IDENTICAL | the branch produces the same result as main |
DIFFERENT | the results diverge — viewable side by side |
NO_PAYLOAD_STORED | the channel stores no payloads (storage policy MINIMAL), so a content comparison is not possible |
The last verdict is deliberate: a channel that stores no payloads for data protection reasons does not get an invented “identical” but the honest statement that there is nothing to compare here.
On merge, Brio-IO writes a summary to audit/branch-summaries/ in the
configuration on main — including a bounded number of concrete divergence
examples (default: 20). When a branch is deleted, Brio-IO clears both its
runtime routes and its data rows.
Today’s limit: no branch deploy
You cannot deploy a branch-scoped channel today. The server rejects it:
HTTP 409 Conflict
Branch deployment is not available while branches share the runtime context.
Validate branches via Shadowing; branch deploy becomes available with isolated
contexts.
In the web UI the deploy switch is disabled in a branch context and explains itself via tooltip. This is not a defect. The reason is concrete: all branches share one runtime context today. A branch-scoped deploy would therefore overwrite the shared route — that is, change the live channel while you believe you are only testing a branch. An honest block beats a function that does something other than what its name promises.
So the validation path is: create a branch → observe shadowing → compare → merge. The isolated per-branch context that would unlock branch deploy is being worked on.
What Brio-IO does not do today
For the same reason branch deploy is blocked, here is a short, honest list:
- No channel-to-channel dispatch in scripts. There is no
router.routeMessage()as in Mirth; the way is a destination on the channel. The Mirth importer warns about exactly this. - No LIVE mode for branches (two branches in parallel production).
- No public download. Brio-IO is in Early Access; access is set up personally.
This page is currently available in German and English. A Spanish version will follow.