Discovery

Features

Discovery extension capabilities

Multiple Backends

All backends implement the same Backend interface, making it easy to switch between providers without changing application code:

type Backend interface {
    Name() string
    Initialize(ctx context.Context) error
    Register(ctx context.Context, instance *ServiceInstance) error
    Deregister(ctx context.Context, serviceID string) error
    Discover(ctx context.Context, serviceName string) ([]*ServiceInstance, error)
    DiscoverWithTags(ctx context.Context, serviceName string, tags []string) ([]*ServiceInstance, error)
    Watch(ctx context.Context, serviceName string, onChange func([]*ServiceInstance)) error
    ListServices(ctx context.Context) ([]string, error)
    Health(ctx context.Context) error
    Close() error
}

Backend Comparison

BackendInfrastructureMulti-NodeWatchHealth ChecksBest For
MemoryNoneNoNoNoDevelopment, testing
ConsulConsul clusterYesYesYesProduction, full-featured
etcdetcd clusterYesYesNoKubernetes environments
mDNSNone (LAN)Yes (LAN)YesNoLocal development, zero-config
KubernetesK8s clusterYesYesYesKubernetes-native deployments
EurekaEureka serverYesNoYesSpring/Netflix ecosystems

Self-Registration

The discovery service automatically registers the current application as a service instance on startup and deregisters it on shutdown:

svc := discovery.MustGet(app.Container())

// Manual registration (automatic if SelfRegister is enabled in config)
err := svc.Register(ctx, &discovery.ServiceInstance{
    ID:      "my-service-1",
    Name:    "my-service",
    Version: "1.0.0",
    Address: "10.0.0.5",
    Port:    8080,
    Tags:    []string{"http", "v1"},
    Metadata: map[string]string{
        "region": "us-east-1",
        "env":    "production",
    },
})

The ServiceInstance struct provides utility methods:

instance.URL("http")         // "http://10.0.0.5:8080"
instance.HasTag("http")      // true
instance.HasAllTags([]string{"http", "v1"}) // true
instance.IsHealthy()         // true if Status == HealthPassing
instance.GetMetadata("region") // ("us-east-1", true)

Service Discovery

Find service instances by name or tags:

svc := discovery.MustGet(app.Container())

// Discover all instances of a service
instances, err := svc.Discover(ctx, "user-service")

// Filter by tags
instances, err := svc.DiscoverWithTags(ctx, "user-service", []string{"http", "v2"})

// Get only healthy instances
healthy, err := svc.DiscoverHealthy(ctx, "user-service")

// List all registered service names
services, err := svc.ListServices(ctx)

Instance Selection

Built-in load balancing selects a single instance from the discovered set:

// Select using a load balancing strategy
instance, err := svc.SelectInstance(ctx, "user-service", discovery.StrategyRoundRobin)

// Get a ready-to-use URL for a service
url, err := svc.GetServiceURL(ctx, "user-service", "http", discovery.StrategyLeastConnections)
// Returns: "http://10.0.0.5:8080"

Service Watching

Subscribe to real-time service change events. When instances are added, removed, or their health changes, your callback is invoked:

err := svc.Watch(ctx, "payment-service", func(instances []*discovery.ServiceInstance) {
    log.Printf("payment-service instances changed: %d available", len(instances))
    for _, inst := range instances {
        log.Printf("  - %s (%s:%d) healthy=%v", inst.ID, inst.Address, inst.Port, inst.IsHealthy())
    }
})

Watching is supported by Consul, etcd, mDNS, and Kubernetes backends.

Health Checks

The discovery extension supports health checking of registered services:

  • Active probes -- periodic HTTP health checks at configurable intervals (default 10s) with timeout (default 5s).
  • Failure threshold -- mark as unhealthy after consecutive failures.
  • Critical deregister -- automatically deregister services that remain unhealthy beyond the critical threshold (default 1m).
  • Service status -- HealthPassing, HealthWarning, HealthCritical.

Health check behavior varies by backend. Consul provides the most comprehensive health check integration.

FARP Protocol

The Forge API Registration Protocol (FARP) enables schema-driven API discovery. When enabled, the extension exposes two HTTP endpoints and injects FARP metadata into the service's backend registration (mDNS TXT records, Consul metadata, etcd keys). The API gateway reads this metadata to auto-generate proxy routes.

How FARP Works

┌─────────────────────┐       ┌──────────────────────┐       ┌───────────────────────┐
│   Upstream Service  │       │   Discovery Backend  │       │      API Gateway      │
│   (discovery ext)   │       │  (mDNS/Consul/etcd)  │       │    (gateway ext)      │
├─────────────────────┤       ├──────────────────────┤       ├───────────────────────┤
│ 1. Start            │──────▶│ 2. Register service  │       │                       │
│ 2. Expose /_farp/*  │       │    + FARP metadata   │       │                       │
│                     │       │    (farp.enabled,     │       │                       │
│                     │       │     farp.manifest,    │◀──────│ 3. Discover services  │
│                     │       │     farp.openapi)     │       │ 4. Read FARP metadata │
│                     │◀──────│                       │       │ 5. Fetch manifest     │
│ 5. Return manifest  │──────▶│                       │──────▶│ 6. Generate routes    │
└─────────────────────┘       └──────────────────────┘       └───────────────────────┘

GET /_farp/manifest

Returns the service's API manifest including metadata and schemas:

{
  "name": "user-service",
  "version": "1.0.0",
  "schemas": [
    {
      "type": "openapi",
      "specVersion": "3.0",
      "location": {
        "type": "url",
        "url": "/openapi.json"
      },
      "contentType": "application/json"
    }
  ],
  "capabilities": ["http", "websocket"]
}

GET /_farp/discovery

Returns discovery information including instance address and available endpoints.

FARP Metadata in Discovery

When a service registers with a backend, FARP metadata is injected automatically. The key metadata fields are:

KeyValueDescription
farp.enabled"true"Signals that FARP is active on this service
farp.manifest"http://host:port/_farp/manifest"URL to fetch the full API manifest
farp.openapi"http://host:port/openapi.json"OpenAPI spec endpoint
farp.asyncapi"http://host:port/asyncapi.json"AsyncAPI spec endpoint (if configured)
farp.graphql"http://host:port/graphql"GraphQL endpoint (if configured)
farp.strategy"push"Registration strategy
farp.capabilities"[http rest]"Advertised capabilities

The gateway extension checks for farp.enabled=true in discovered service metadata and uses these URLs to generate proxy routes for each protocol.

Schema Publishing

Publish API schemas (OpenAPI, AsyncAPI, GraphQL, gRPC) via FARP manifests for the gateway and other services to discover:

discovery.NewExtension(
    discovery.WithFARPEnabled(true),
    discovery.WithFARPSchemas(
        discovery.FARPSchemaConfig{
            Type:        "openapi",
            SpecVersion: "3.0",
            Location: discovery.FARPLocationConfig{
                Type: "url",
                URL:  "/openapi.json",
            },
            ContentType: "application/json",
        },
        discovery.FARPSchemaConfig{
            Type:        "graphql",
            SpecVersion: "2023",
            Location: discovery.FARPLocationConfig{
                Type: "url",
                URL:  "/graphql",
            },
        },
    ),
)

Auto-Register with FARP

When AutoRegister is enabled (the default), the FARP manifest is automatically registered with the discovery backend, allowing the API gateway to discover and proxy requests without manual configuration:

discovery.NewExtension(
    discovery.WithFARPEnabled(true),
    discovery.WithFARPAutoRegister(true),  // default: true
)

FARP Strategies

StrategyBehavior
pushService pushes schema registration on startup (default)
pullGateway pulls schema from service on demand
hybridPush on startup with pull-based refresh

mDNS Discovery

Zero-configuration service discovery on local networks using multicast DNS. Ideal for local development environments where no external infrastructure (Consul, etcd) is available.

Basic mDNS Setup

discovery.NewExtension(
    discovery.WithBackend("mdns"),
    discovery.WithServiceName("my-service"),
)

mDNS with FARP

mDNS is the recommended backend for local development with FARP. FARP metadata is stored in mDNS TXT records, so the gateway can discover services and their API schemas over the local network without any infrastructure:

// Service app (e.g. user-service)
discovery.NewExtension(
    discovery.WithEnabled(true),
    discovery.WithBackend("mdns"),
    discovery.WithServiceName("user-service"),
    discovery.WithFARPEnabled(true),
    discovery.WithFARPAutoRegister(true),
    discovery.WithFARPEndpoints(discovery.FARPEndpointsConfig{
        OpenAPI: "/openapi.json",
        Health:  "/health",
    }),
)

When the service registers via mDNS, the TXT records include:

version=1.0.0
id=user-service-abc123
farp.enabled=true
farp.manifest=http://192.168.1.10:8080/_farp/manifest
farp.openapi=http://192.168.1.10:8080/openapi.json

The gateway extension browsing for mDNS services reads these TXT records, detects farp.enabled=true, and auto-generates proxy routes.

mDNS Service Types

By default the service type is derived from the service name: _<name>._tcp. You can override this or configure which service types to browse for:

discovery.NewExtension(
    discovery.WithBackend("mdns"),
    discovery.WithServiceName("user-service"),
    // Custom service type for registration
    discovery.WithMDNSServiceType("_myapp._tcp"),
    // Browse for multiple service types during discovery
    discovery.WithMDNSServiceTypes("_myapp._tcp", "_api._tcp"),
)

mDNS Configuration Options

OptionDescription
WithMDNSServiceType(t)Set the mDNS service type for registration
WithMDNSServiceTypes(t...)Set mDNS service types to browse during discovery
WithMDNSBrowseTimeout(d)Timeout for browse operations (default: 3s)
WithMDNSTTL(ttl)DNS record TTL
WithMDNSWatchInterval(d)Watch polling interval
WithMDNSInterface(iface)Bind to a specific network interface
WithMDNSIPv6(enabled)Enable IPv6 support

How is this guide?

On this page