Discovery
Service discovery and registry with multiple backends and FARP integration
Overview
github.com/xraph/forge/extensions/discovery provides service discovery and registration for
Forge applications. It registers a Service in the DI container that manages service instances
across pluggable backends (memory, Consul, etcd, mDNS) and optionally exposes FARP endpoints
for schema-based API discovery.
What It Registers
| Service | DI Key | Type |
|---|---|---|
| Discovery service | discovery | *Service |
The service is registered with Vessel and supports the legacy alias discovery.Service.
Quick Start
package main
import (
"context"
"fmt"
"github.com/xraph/forge"
"github.com/xraph/forge/extensions/discovery"
)
func main() {
app := forge.NewApp(forge.AppConfig{Name: "user-service", Version: "1.0.0"})
app.RegisterExtension(discovery.NewExtension(
discovery.WithEnabled(true),
discovery.WithBackend("consul"),
discovery.WithService(discovery.ServiceConfig{
Name: "user-service",
Version: "1.0.0",
Port: 8080,
Tags: []string{"api", "v1"},
Metadata: map[string]string{"team": "platform"},
}),
discovery.WithConsul(discovery.ConsulConfig{
Address: "localhost:8500",
}),
))
ctx := context.Background()
app.Start(ctx) // auto-registers with Consul
defer app.Stop(ctx) // auto-deregisters
svc, _ := forge.InjectType[*discovery.Service](app.Container())
// Discover other services
instances, _ := svc.Discover(ctx, "order-service")
for _, inst := range instances {
fmt.Printf("Found: %s at %s\n", inst.ID, inst.URL("http"))
}
// Discover healthy instances only
healthy, _ := svc.DiscoverHealthy(ctx, "order-service")
// Select one with load balancing
selected, _ := svc.SelectInstance(ctx, "order-service", "round-robin")
fmt.Printf("Selected: %s\n", selected.URL("http"))
// Watch for changes
svc.Watch(ctx, "order-service", func(instances []discovery.ServiceInstance) {
fmt.Printf("Service updated: %d instances\n", len(instances))
})
}FARP Integration
FARP (Forge API Registration Protocol) lets your service publish API schema metadata so the gateway
can automatically discover and proxy traffic to it. When FARP is enabled, the extension registers
two HTTP endpoints (/_farp/manifest and /_farp/discovery) and injects FARP metadata into the
service's discovery registration (mDNS TXT records, Consul metadata, etcd keys, etc.).
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",
}),
)
// Exposes: GET /_farp/manifest and GET /_farp/discovery
// mDNS TXT records now include farp.enabled=true, farp.manifest=..., farp.openapi=...FARP with mDNS (Zero-Config Local Development)
The most common local-development setup uses mDNS with FARP. No external infrastructure is needed -- services and gateways discover each other over the local network automatically:
package main
import (
"context"
"github.com/xraph/forge"
"github.com/xraph/forge/extensions/discovery"
)
func main() {
app := forge.NewApp(forge.AppConfig{Name: "order-service", Version: "1.0.0"})
app.RegisterExtension(discovery.NewExtension(
discovery.WithEnabled(true),
discovery.WithBackend("mdns"),
discovery.WithServiceName("order-service"),
// Enable FARP so the gateway can auto-discover this service.
discovery.WithFARPEnabled(true),
discovery.WithFARPAutoRegister(true),
discovery.WithFARPEndpoints(discovery.FARPEndpointsConfig{
OpenAPI: "/openapi.json",
Health: "/health",
}),
discovery.WithFARPCapabilities("http", "rest"),
))
ctx := context.Background()
app.Start(ctx)
defer app.Stop(ctx)
}When this service starts it:
- Registers itself via mDNS on the local network (service type
_order-service._tcp). - Includes FARP metadata in mDNS TXT records (
farp.enabled=true,farp.manifest=http://.../_farp/manifest,farp.openapi=http://.../openapi.json). - Exposes
GET /_farp/manifestreturning the service's API schema manifest. - The gateway extension on another Forge app discovers this service, reads the manifest, and auto-generates proxy routes.
FARP with Consul (Production)
For production deployments, use Consul as the backend. FARP metadata is stored in Consul service metadata:
discovery.NewExtension(
discovery.WithEnabled(true),
discovery.WithConsul("consul.internal:8500", ""),
discovery.WithServiceName("payment-service"),
discovery.WithServiceTags("api", "v2"),
discovery.WithFARPEnabled(true),
discovery.WithFARPAutoRegister(true),
discovery.WithFARPSchemas(
discovery.FARPSchemaConfig{
Type: "openapi",
SpecVersion: "3.1",
Location: discovery.FARPLocationConfig{
Type: "url",
URL: "/openapi.json",
},
ContentType: "application/json",
},
),
discovery.WithFARPEndpoints(discovery.FARPEndpointsConfig{
Health: "/health",
Metrics: "/metrics",
OpenAPI: "/openapi.json",
}),
discovery.WithFARPCapabilities("http", "rest", "webhooks"),
)Key Concepts
- Backends -- choose between
memory(default),consul,etcd,mdns,kubernetes, oreureka. - Service registration -- register the current service with the backend on startup.
- Service discovery -- discover healthy instances by name or tags with load balancing.
- Health checks -- periodic checks with configurable interval and timeout.
- Watching -- watch for service changes in real-time.
- FARP -- expose
/_farp/manifestand/_farp/discoveryfor API schema-based discovery. - mDNS + FARP -- zero-config local development where FARP metadata is carried in mDNS TXT records.
Important Runtime Notes
- The
memorybackend is local-only and does not share state across nodes. - Consul and etcd backends require running infrastructure.
- FARP endpoints enable the gateway extension to auto-discover and proxy routes.
- When using mDNS, the service name determines the mDNS service type (
_<name>._tcp). UseWithServiceNameorWithMDNSServiceTypeto control it.
Detailed Pages
How is this guide?