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
| Backend | Infrastructure | Multi-Node | Watch | Health Checks | Best For |
|---|---|---|---|---|---|
| Memory | None | No | No | No | Development, testing |
| Consul | Consul cluster | Yes | Yes | Yes | Production, full-featured |
| etcd | etcd cluster | Yes | Yes | No | Kubernetes environments |
| mDNS | None (LAN) | Yes (LAN) | Yes | No | Local development, zero-config |
| Kubernetes | K8s cluster | Yes | Yes | Yes | Kubernetes-native deployments |
| Eureka | Eureka server | Yes | No | Yes | Spring/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:
| Key | Value | Description |
|---|---|---|
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
| Strategy | Behavior |
|---|---|
push | Service pushes schema registration on startup (default) |
pull | Gateway pulls schema from service on demand |
hybrid | Push 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.jsonThe 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
| Option | Description |
|---|---|
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?