io.Connect Patterns and APIs: Making the Right Choice

The previous article, App-to-app communication in io.Connect: practical guidance, looked at the main communication mechanisms in more detail and compared how they behave. This article is more decision oriented: when you are building a workflow, which mechanism should you actually choose?

The short answer is: start with the user interaction pattern, not the API. Once you know whether you need shared state, a direct service call, a workflow handoff, a live stream, or scoped launch state, the right io.Connect mechanism usually becomes much easier to pick.

io.Connect gives you several ways to move data, context, and actions between apps. They overlap intentionally, because different workflows need different levels of coupling, scope, persistence, and user control.

Here is the quick map before going into the decision rules.

The 8 Data Sharing Mechanisms

# Mechanism Pattern One-liner
1 Pub/Sub Legacy Topic-based broadcast. No persistence, no discovery - prefer higher-level APIs for new work
2 Shared Contexts Broadcast Named global key/value object - great for “selected client”, “current order”, “active instrument”
3 Color Channels Broadcast + User-Driven Shared Contexts plus a Channel Selector UI. Best when users decide which apps link together
4 Workspace Contexts Broadcast Apps inside a Workspace share state - scoped, portable, and persistable in layouts
5 Interop Methods Service Call Explicit, service-like capability. The caller knows exactly what it needs. Supports targeting
6 Intents Workflow Handoff Raise an action, not a target. Platform finds handlers, can launch an app, lets user choose
7 Streams Streaming Continuous real-time data push. Best for live alerts, telemetry, producer->many-subscribers
8 Window Context Scoped Per-window state and launch context. Best for restoring or initialising a specific window

Think in Patterns Before APIs

The most common mistake is to start from the API name instead of the workflow shape. In practice, the better question is not “Should I use Interop or Contexts?”, but “What kind of interaction am I trying to create for the user?”

Use the pattern first, then select the lightest mechanism that supports it.

Pattern What it means Mechanism(s)
Service Call Call a known function in another app and get a result back Interop Methods; Streams (for continuous updates)
Shared State A named object many apps observe Shared Contexts (developer-driven); Channels (user-driven grouping UI)
Workflow Handoff Raise “handle this action” - loose coupling, user choice, app launch Intents
Scoped Context Local state containers Window Context (per-window); Workspace Context (per-workspace)
Legacy Compatibility Topic bus for porting existing integrations Pub/Sub

Rule of thumb: prefer the highest-level mechanism that matches the UX. Start with the narrowest scope that satisfies the workflow.

Practical tips

We can say these mechanisms fall into two families: Broadcast and Invocation & Streaming. The first is about sharing state across apps, the second is about triggering actions or delivering data flows between them. The tables below cover good fit use cases for each.

Broadcast Family

Mechanism Summary Use when Avoid when
Pub/Sub One-shot topic messages, no persistence Migrating existing pub/sub apps; fire-and-forget events Building new integrations; late joiners need last value
Shared Contexts Global named state, late joiners get current value Syncing selected entity (developer-driven); late joiners must get state User controls grouping → Channels; need workspace isolation
Color Channels Shared Contexts + Channel Selector UI User decides which apps link; FDC3 User Channels needed Linking fixed in code → Shared Contexts; full global visibility needed
Workspace Contexts Workspace-scoped state, persists with Layout Parallel workflows must be isolated; context saves with layout Apps outside Workspace need data; global visibility required

Invocation & Streaming Family

Mechanism Summary Use when Avoid when
Interop Methods Direct service call, target must be running Need a direct result back; target app is already running Target may not be running → Intents; need shared state
Intents Workflow handoff, can launch handler App handoffs, launch workflows; user picks handler via Resolver UI Service contract well-known → Methods; streaming or broadcast needed
Streams Continuous push, branches for targeted delivery Continuous data feed; polling would be wasteful Occasional updates → Shared Contexts; single request/response → Methods
Window Context Launch-time parameters, window-scoped Passing initial payload to a window; context persists with Global Layout Dynamic messaging needed post-launch; multiple apps need the data

For a detailed head-to-head on Methods vs. Intents (the most confused pair), see Interop Methods vs Intents.

Real-World Scenarios

Below are common app-to-app scenarios. These are not hard rules, but they should give you a good idea before you optimize for special cases.

Scenario Best Fit Why Alternative
User clicks a client - all open apps should update Shared Contexts Global; late joiners get current value Channels if user controls grouping
Alert service pushes live notifications Streams Continuous push; branches for filtering Methods if one-off lookup
“View Chart” should open the right charting app Intents Loose coupling; launches app; user picks Methods if chart app always running
Two portfolios - each with independent context Workspace Contexts Isolated per Workspace Channels if Workspaces aren’t in use
User wants to link two specific apps Color Channels User assigns apps to same colour Shared Contexts for programmatic linking
Risk app exposes a calculation Interop Methods Precise service call; result returned Intents if callers shouldn’t depend on a specific app
Open chart pre-loaded with a ticker Window Context Pass launch params at open time Intents - raise “ViewChart” with context
Existing pub/sub app connecting Pub/Sub Compatibility bridge Migrate to Shared Contexts long-term
Interop with third-party FDC3 app Channels → FDC3 User Channels Direct FDC3 mapping Intents → FDC3 Intents if action-based

Decision Flowchart

You can use this flow from top to bottom. It intentionally starts with the most specific cases first, such as legacy migration or continuous streaming, and leaves Shared Contexts as the default for general global state synchronization.

Answer each question top-to-bottom - first “Yes” wins.

Legacy pub/sub migration?
  └─ YES → Pub/Sub
  └─ NO ↓

Continuous real-time data push?
  └─ YES → Streams
  └─ NO ↓

Need a response or trigger an action?
  └─ YES → Target known & running?
  │          └─ YES → Interop Methods
  │          └─ NO  → Intents (can launch app)
  └─ NO ↓

User controls which apps link together?
  └─ YES → Color Channels
  └─ NO ↓

Scoped to one Workspace only?
  └─ YES → Workspace Contexts
  └─ NO ↓

Launch payload or per-window state?
  └─ YES → Window Context
  └─ NO ↓

DEFAULT → Shared Contexts

Default path: All “No” → Shared Contexts - best global programmatic sync.

Mnemonic:

Need Mechanism
Action / handoff -> Intents
Service call -> Methods
Live feed -> Streams
Shared state -> Contexts / Channels
Legacy topic bus -> Pub/Sub

FDC3 Mappings: Channels → User Channels · Intents → FDC3 Intents · Streams → Private Channels

Key Takeaways

Most of the complexity comes from choosing the wrong level of abstraction. io.Connect gives you low-level and high-level mechanisms, but for new workflows you usually want the highest-level mechanism that matches the user experience.

These are the rules I would keep in mind when designing app-to-app communication.

  1. Everything flows through the Gateway: All mechanisms ride through the io.Connect Gateway over WebSockets. No direct app-to-app connections.

  2. Think in patterns first: Match the interaction pattern (service call, shared state, workflow handoff, scoped context, legacy bus) to the right mechanism.

  3. Start narrow, go wider: Window → Workspace → Shared Context / Channels. Start with the narrowest scope that satisfies the requirement.

  4. Prefer higher-level mechanisms: Reserve Pub/Sub for porting legacy systems. For new development, pick the mechanism that gives you persistence, discovery, and the best UX.

Related reading:

Documentation: