Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Destinations

Where an interface's messages go: one place with destination, or several, each by a name you choose, with destinations.

ConnectorSends each messageDelivered when
mllpOver MLLP to a receiver, one connection kept openThe receiver answers AA
httpAs an HTTP request to a URL, or a path under an API from resources.yamlA 2xx answer
fileAs a file in a folder of this server or a shareThe file is complete under its name
sftpAs a file on an SFTP serverThe same, on the server
interfaceInto another interface's queueIt's stored there, in the same transaction

One or several

# interfaces/adt-to-lab/interface.yaml, in part
destinations:
  lab:
    mllp: { host: ${LAB_HOST}, port: ${LAB_PORT} }
  billing:
    http: { api: billing, path: /charges }
    transform: toBilling
    output: json
  archive:
    file: { dir: ${OUTBOUND_DIR} }
  • Each destination gets every message and is delivered independently: its own queue, order, retries and pause. One that's down holds up only itself; the others carry on (nuance).
  • Each runs its own function on the original message, the one named by transform: (default transform), not on another destination's result: toLab and toBilling each shape it for their receiver (nuance). output: sets the format that destination's result is written in.
  • A message is finished once every destination is done with it: sent, filtered or quarantined. Filtering and quarantine are per destination too (nuance).
  • destination: (one) is the same as a single destination named default. Use one or the other, not both.
  • Adding a destination later gives it only messages that arrive from then on (and those still in flight), not older ones; removing one filters what was waiting for it (nuance).
  • Many destinations on one interface: past a handful, consider splitting (when).

Each destination can also set its own retry, rate_limit and monitor (in place of the interface's: delivery), its own application, vendor and owner when they differ from the interface's about.to (about), and custom: fields.

Keeping what was sent. By default only the original message is stored, and what a destination was sent is reproduced by running its code again when you look (nuance). Where you must be able to prove exactly what was sent, save_output: true on that destination keeps it with its delivery (about 1.15 GB per million deliveries, measured).

Each destination runs its own function, but code that runs away after an await can be blamed only on its interface: every call of that interface in flight on the thread is quarantined, whichever destination it was for (nuance).

MLLP

# interfaces/orders-to-lab/interface.yaml, in part
destination:
  mllp:
    host: ${LAB_HOST}
    port: ${LAB_PORT}
    timeout_ms: 10000       # the default: connect, send and wait for the ACK
    on_reject: quarantine   # the default: an AR is for a person to look at
    tls: true               # TLS, trusting this server's CAs

The engine sends a message and waits for the ACK before the next one, on one connection it keeps open. AA is delivered; AE is tried again; AR follows on_reject: quarantine (the default) or retry, for receivers that answer AR when they're busy. A send that takes longer than timeout_ms is dropped and tried again; the receiver may have it already, so receivers that dedupe on MSH-10 take care of the repeat (twice). On Windows keep timeout_ms above 2000 (why).

tls: true sends over TLS, trusting the certificates this server trusts; tls: { ca, certificate } also trusts a private CA and presents a client certificate: TLS to receivers. A refused handshake is a temporary error: the message waits, with the reason on the destination (nuance).

ack: none sends without reading an answer, for the rare receiver that never sends ACKs: nothing then tells the engine a message was refused or lost.

HTTP

# interfaces/results-to-portal/interface.yaml, in part
destination:
  http:
    api: portal             # an apis: entry: its URL, token, timeout and TLS
    path: /results
    method: POST            # the default
output: json

Each message is a request: to url, or to path under an apis: entry in resources.yaml (api:), which brings the base URL, headers (the token), timeout and TLS, set once for every interface and environment (APIs). Prefer api: for anything with a token. A 2xx answer is delivered; 408, 429, 5xx and network errors are tried again; any other 4xx is quarantined: the receiver refused this message, and sending it again won't change that.

With a url of its own instead, a token belongs in a secret: headers: { Authorization: "Bearer ${secret:PORTAL_TOKEN}" }.

Files and SFTP

# interfaces/adt-to-archive/interface.yaml, in part
destinations:
  local:
    file: { dir: ${OUTBOUND_DIR} }
  partner:
    sftp: { server: quest, dir: inbound/adt }

Each message is a file named by the message's id and the output format (1234.hl7), written under a hidden temporary name and renamed when complete, so a reader never sees half a file. A file destination's dir is relative to the interface's folder unless written whole, and is made if it's missing; the service must be allowed to write there (Linux: the sandbox; Windows: the service's folders). An sftp destination's server is an sftp: entry in resources.yaml.

To another interface

# interfaces/adt-in/interface.yaml, in part
destinations:
  lab:
    mllp: { host: ${LAB_HOST}, port: ${LAB_PORT} }
  billing:
    interface: adt-to-billing

interface: puts each message into the queue of an interface with source: internal, in the same database transaction that marks it sent here: no network hop, and nothing lost or sent twice between the two (nuance). Use it to split one feed into teams' interfaces, or to chain steps each with its own owner, code, retries and alerts. The receiving interface's format must be what this destination outputs, its own keys, dedupe and destinations apply (nuance), and routes can't form a loop: ductus validate checks both, and ductus impact follows routes (nuance).

A drain stops what comes in from outside; routes between interfaces keep moving, so what's already inside reaches its last interface before the engine stops (nuance).

Pausing one

A destination can be paused (from the web UI, the API or ductus pause adt-in lab), at once, with a reason now or later (--reason): its messages wait, in order, until it's resumed, and a pause never alerts. For a receiver's planned downtime (nuance).