Rationale — Externalized Functions¶
Foundations: the thinking before the how.
At a glance
- What — why Python functions join Mercury flows and graphs over Event-over-HTTP, and why the wrapper refuses to become a second orchestrator.
- For architects deciding where a piece of logic should live.
The composable theme does not stop at the JVM boundary¶
Mercury's core promise is that functions are self-contained and coupled only by route names and event envelopes. A flow that reads
does not know — and must not care — whether that route resolves to a Java method on a virtual thread, a Rust function, or a Python handler in another process. The route name is the contract; everything else is deployment detail.
Externalized functions take that promise literally. The Event-over-HTTP seam already existed for engine-to-engine calls: the same event envelope, serialized in a language-neutral wire format, carried over HTTP. A Python function host is simply another peer speaking that protocol — nothing new crosses the wire.
Why you would externalize a function¶
Ecosystem gravity. Python's strength is its libraries: requests-style
integrations, NumPy/pandas, scikit-learn and the ML inference stacks, domain SDKs.
Porting that logic into Java rarely adds value; wrapping it as a route does. The
function stays where its ecosystem lives; the orchestration stays where the
architecture lives.
Team reality. Enterprise installations are polyglot for the long haul. A data science team ships Python; the platform team runs engine applications. Route names are the only interface the two must agree on — a one-line YAML entry, not a shared codebase.
Independent lifecycles. A Python host deploys, scales and restarts on its own cadence as an ordinary Kubernetes pod — with the engines' own actuator surface, so operations sees one shape everywhere.
Why the wrapper owns nothing but functions¶
The tempting mistake is to grow the wrapper into a mini-engine — local flows, retries, queues, persistence. Mercury's architecture deliberately says no:
- Orchestration lives in the engines. Event Script and MiniGraph carry the retry logic, branching, exception routing and state. A leaf that re-implements recovery fights the tier that owns it.
- Back-pressure belongs to the tier that owns recovery. The host's internal event bus has no spill tier and no queue cap: a call that cannot be served by its deadline fails fast with the standard 408 envelope, and the engine's flow decides what happens next. A leaf that hoards work merely hides congestion from the layer designed to handle it.
- Sync-over-async across services is an anti-pattern by default. The engines' own documentation warns that superimposing synchronous coupling on distributed parts builds a distributed monolith. The wrapper inherits that judgment: local composition is for simple leaf-side helpers; workflow processing belongs in flows and graphs.
The result is a package small enough to audit in an afternoon — an envelope codec, a host, a primitive bus, a thin client, and the engines' operational conventions — with the entire orchestration brain kept where it already is.
When not to externalize¶
- Orchestration-shaped logic — sequencing, branching, compensation: write a flow or a graph, not a Python function that calls other functions in a loop.
- Latency-critical hot paths — an in-engine function call is an in-memory event; an externalized one is an HTTP round trip. Microseconds versus milliseconds.
- Logic that is really data mapping — Event Script's data mapping and MiniGraph's built-in skills often eliminate the function entirely.
Where to go next¶
Design — The Wrapper Anatomy shows how these principles become the five small components of the package.