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:

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.

Schema Publishing

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

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

Auto-Register with FARP

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

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:

discovery.NewExtension(
    discovery.WithBackend("mdns"),
)

mDNS discovery works automatically on the local network and supports watching for service changes.

How is this guide?

On this page