Documentation

adrs/038-permission-discovery-service-references.md

ADR 038: Permission Discovery Requires Service References in AppHost

Status

Accepted

Context

The Identity component needs to discover permissions from all other components by calling their /.well-known/permissions endpoints. This discovery happens via the PermissionSyncService, which is triggered manually via POST /permissions/sync or can be configured to run automatically on startup.

The Problem

When attempting to call other component endpoints from Identity, we encountered DNS resolution failures:

System.Net.Http.HttpRequestException: nodename nor servname provided, or not known (catalog:443)

Despite Aspire being specifically designed for service discovery, Identity couldn't resolve component service names like catalog, spatial, etc.

Investigation

The issue was that Aspire's service discovery works through environment variables that are injected when one component explicitly references another. The environment variables follow the format:

services__{sourceComponentName}__{endpointName}__{index}=http://localhost:PORT

These environment variables are only injected when you call WithReference() on the component builder in the AppHost.

Key Insight: In Dynaplex, components are registered in the ComponentRegistry, and by default they only reference their dependencies (via DependsOn()). The Identity component has minimal dependencies (core-data, prism, events), but it needs to discover ALL components for permission synchronization - including components it doesn't depend on (catalog, spatial, transport, workflow, iot, bbu, etc.).

Decision

Every AppHost must explicitly configure Identity to reference all components that expose permissions, using WithReference() without WaitFor().

Pattern

// After all components are registered
var identity = registry.Get("identity");

// Add references to all components (for service discovery)
// NOTE: Use WithReference() WITHOUT WaitFor() to avoid circular dependencies
if (registry.TryGet("catalog", out var catalog))
{
    identity.Component.WithReference(catalog.Component);
}
if (registry.TryGet("spatial", out var spatial))
{
    identity.Component.WithReference(spatial.Component);
}
// ... repeat for all components ...

Why WithReference() Without WaitFor()

  • WithReference(): Injects service discovery environment variables (enables DNS resolution)
  • WaitFor(): Creates startup ordering dependency (wait for component to be ready)

We use WithReference() alone because:

  1. Components already have correct startup order via DependsOn() in their registrations
  2. Adding WaitFor() creates circular dependencies (e.g., catalog depends on identity, but identity waiting for catalog = deadlock)
  3. We only need the environment variables for service discovery, not startup coordination

Why Not Use DependsOn()?

We cannot add all components as dependencies of Identity because:

  1. It would create massive circular dependencies (most components depend on Identity for auth)
  2. Identity doesn't actually depend on these components - it just needs to discover them
  3. The permission discovery is optional and best-effort (components can fail to respond)

Consequences

Positive

  • ✅ Service discovery works correctly - Identity can resolve all component service names
  • ✅ Permission synchronization succeeds for all running components
  • ✅ No circular dependencies or startup deadlocks
  • ✅ Components can be added/removed from AppHost without breaking Identity startup

Negative

  • ⚠️ Each AppHost must remember to add these WithReference() calls
  • ⚠️ Adding a new component requires updating the permission discovery block
  • ⚠️ The pattern is not obvious - developers might forget this step

Mitigation

  • Document pattern clearly in this ADR and reference documentation
  • Add comments in AppHost code explaining why this is needed
  • Create a checklist for adding new components
  • Consider future enhancement: automatic discovery of all registered components

Implementation Notes

Required for Every AppHost

Any project that uses the Identity component and needs permission discovery must include this pattern. Examples:

  • Acsis.Dynaplex.Projects.BbuRfid
  • Acsis.Dynaplex.Projects.AssetTrakClassic

Components to Reference

Reference ALL components that might expose permissions:

  • core-data
  • system-environment
  • spatial
  • catalog
  • transport
  • workflow
  • events
  • iot
  • bbu
  • Any new components added to the system

Error Symptoms

If this pattern is not followed, you'll see:

  • DNS resolution errors: "nodename nor servname provided, or not known (component-name:443)"
  • Empty permissions table in Identity database
  • Permission-based UI elements missing (buttons, menu items)
  • Permission sync endpoint returning incomplete results

References

  • ADR-036: Database Project Separation - Explains component structure
  • ADR-007: Aspire Microservices Migration - Context for service discovery