Skip to main content

ADR-0013: JIT Federated Discovery (The Oracle)

  • Status: Implemented
  • Date: 2026-05-15

Context

Previously, Framework M relied on a Broadcast Discovery model where every service unconditionally published its entire schema and metadata to a shared bus (NATS) or local file. While simple, this approach suffered from:

  1. Noise: Constant "heartbeat" broadcasts created high CPU/network overhead in local dev.
  2. Scalability: The "Global Schema" became too large to synchronize reliably across many services.
  3. Monolith/Macroservice Friction: A monolith pretending to be many services would broadcast overlapping data, causing discovery conflicts.

We needed a system that was Lazy, Batch-capable, and Mode-aware.

Decision

Implement JIT (Just-In-Time) Federated Discovery, centralizing resource ownership resolution into a gateway-backed "Oracle" pattern.

1. The Frontend Oracle: DiscoveryClient

The DiscoveryClient singleton in framework-m-plugin-sdk is the primary interface for all discovery.

  • It maintains a local cache of DiscoveryUnit objects (Service URL, ApiUrl, MetaUrl).
  • It performs Batch Queries to resolve missing resource owners before any data fetching occurs.
  • It is initialized during the Shell's prewarmCore phase to resolve essential services (Auth, Settings).

2. The JIT Handshake (Protocol)

When a resource (e.g., sales.Invoice) is requested but its owner is unknown:

  1. Fuzzy Match: The PluginRegistry checks if Invoice is a suffix of any known registered resource.
  2. Batch Query: If unknown, the DiscoveryClient pings /api/gateway/discovery/query (the Oracle).
    • Request: {"resources": ["sales.Invoice"], "services": {"finance": 0}}
    • Response: {"resources": {"sales.Invoice": "finance"}, "services": {"finance": {"url": "http://...", "apiUrl": "/api/finance/v1"}}}

3. Local Development: The Shared-File Protocol

To enable macroservice discovery locally without NATS:

  • All services write their manifests to ~/.framework_m_discovery.json on startup.
  • The EnvDiscoveryAdapter in framework-m-standard watches this file.
  • This creates a decentralized discovery bucket that is zero-conf for developers.

4. Production: NATS KV & Real-time Resource Maps

  • Services publish their manifests to a NATS Key-Value store.
  • The NatsDiscoveryAdapter maintains a subscription to manifest updates.
  • It rebuilds a high-performance _resource_map (DocType -> ServiceName) in memory to serve JIT queries with sub-millisecond latency.

5. DocType Ownership Filtering

To prevent "over-claiming":

  • BroadcastSchema now uses get_installed_apps() to filter DocTypes.
  • In MACROSERVICE mode, a service only broadcasts DocTypes whose module prefix matches an app it explicitly owns.

Consequences

Positive

  • Efficiency: Services only broadcast once on startup; clients only query what they need.
  • Robustness: Monoliths no longer conflict with macroservices in the same environment.
  • Transparency: Every API resolution is now traceable through the DiscoveryClient logs.

Negative

  • Test Isolation: Because the DiscoveryClient is a singleton, all tests MUST now call DiscoveryClient.reset() and PluginRegistry.reset() in afterEach hooks to prevent state leakage.
  • Latency: The first request for a new resource type incurs a discovery round-trip (mitigated by batching and pre-warming).

References