Skip to content

direct://

Synchronous in-process messaging between two named endpoints in the same Helix context. The producer call lands in the consumer's processor on the same goroutine — no queue, no copy — so it's the fastest way to fan a route into reusable sub-routes.

Reach for direct:// when you want a named in-process entry point (e.g. a logic route that's invoked from multiple places, a sub-route called by an agent tool, an A2A handler). Use seda:// instead when the call should be asynchronous.

URI shape

direct://<name>[?block=true&timeout=30s&failIfNoConsumers=true&synchronous=false]

<name> is an identifier scoped to the running Helix context. Identical names refer to the same endpoint — a to: direct://my-logic resolves the consumer registered on from: direct://my-logic. Only one consumer per name is permitted.

Parameters

Param Type Default Side Description
name path — both Endpoint identifier (the path portion of the URI).
block boolean true producer Block until a consumer becomes active or the timeout elapses. If false, returns ConsumerNotAvailableError immediately when no consumer is registered.
timeout duration 30s producer Max time the producer waits for a consumer when block=true. Accepts Go durations (30s, 500ms) or integer milliseconds (30000).
failIfNoConsumers boolean true producer Return ConsumerNotAvailableError when no consumer is registered. When false the message is silently dropped (logged at DEBUG).
synchronous boolean false both Accepted for cross-framework compatibility; Helix is synchronous by default.

Body shape

Producer: any value — passed through unchanged on the same exchange. Consumer: receives the producer's In message verbatim. Out replies on InOut exchanges flow back to the caller via the same exchange.

Headers

direct:// itself sets no headers. Whatever's on the exchange when the producer calls it is what the consumer sees.

Examples

A reusable sub-route invoked from multiple places:

routes:
  - id: enrich-customer
    from: direct://enrich-customer
    steps:
      - to: postgres://customers/findOne?query=SELECT * FROM customers WHERE id = $1
      - setHeader:
          name: enriched-at
          starlang: now()

  - id: order-pipeline
    from: kafka://orders?groupId=svc
    steps:
      - to: direct://enrich-customer   # InOnly call
      - to: postgres://orders/insert

InOut request/reply over direct://:

- id: api
  from: http-server://0.0.0.0:8080/v1/customers
  steps:
    - to: direct://enrich-customer     # pattern InOut: caller's body is replaced by reply

Tolerant fan-out (drop if no consumer yet):

- to: direct://audit?failIfNoConsumers=false&block=false

Behavior notes

  • One consumer per name. Registering a second consumer on the same name returns an error from Component.addConsumer. Use seda:// with multipleConsumers=true if you need pub/sub.
  • Lifecycle. A consumer registers in Start(), unregisters in Stop(). Suspend() temporarily unregisters (producers block / fail per the block and failIfNoConsumers settings) and Resume() re-registers without losing the processor binding.
  • Producer cache. Each Producer caches the resolved consumer pointer keyed by a component-level state counter so the hot path doesn't take the consumer-registry mutex.
  • synchronous is a no-op. Helix routes are synchronous by default; the parameter is accepted for compatibility.
  • Error type. When failIfNoConsumers=true, the producer returns a *direct.ConsumerNotAvailableError. Use direct.IsConsumerNotAvailable(err) to test it; errors.As also works.

Source

  • pkg/component/direct/direct.go — Component, Endpoint, Producer, Consumer, error types.

See also

  • Asynchronous queues: seda://
  • Reusable logic routes guide: see docs/guides/