discovery

package module
v1.13.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 24 Imported by: 2

README

Forge Discovery, service registration and service discovery for Go applications built on Forge, with Consul, etcd, Kubernetes, mDNS and in-memory backends

Forge Discovery: service registration and discovery for Go microservices

Forge Discovery is the service discovery extension for Forge, the Go backend framework. When your app starts it registers itself in a service registry (Consul, etcd, mDNS/Bonjour, an HTTP seed list or an in-memory registry), keeps its health status up to date, and deregisters on shutdown. You get a Service in the container for finding other services by name or tag, with round-robin or random load balancing, and an optional FARP manifest that tells an API gateway about your routes and OpenAPI or AsyncAPI schemas.

Go CI Go Reference Apache 2.0

Documentation · Quick start · Features · Backends · Finding services · FARP · Configuration · FAQ

Quick start

You need Go 1.26 or later. Add Forge and the extension to your module:

go get github.com/xraph/forge@latest
go get github.com/xraph/forge/extensions/discovery@latest

Save this as main.go. It registers an orders service in the in-memory registry and adds a route that looks services up:

package main

import (
	"log"
	"net/http"

	"github.com/xraph/forge"
	"github.com/xraph/forge/extensions/discovery"
)

func main() {
	app := forge.NewApp(forge.AppConfig{
		Name:        "orders",
		Version:     "1.0.0",
		HTTPAddress: ":8080",
		Extensions: []forge.Extension{
			discovery.NewExtension(
				discovery.WithBackend("memory"),
				discovery.WithServiceName("orders"),
				discovery.WithServiceID("orders-1"),
				discovery.WithServiceTags("api", "v1"),
			),
		},
	})

	err := app.Router().GET("/instances/:service", func(ctx forge.Context) error {
		svc, err := forge.Inject[*discovery.Service](app.Container())
		if err != nil {
			return err
		}

		instances, err := svc.Discover(ctx.Context(), ctx.Param("service"))
		if err != nil {
			return err
		}

		return ctx.JSON(http.StatusOK, instances)
	})
	if err != nil {
		log.Fatal(err)
	}

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

Run it with go mod tidy && go run ., then ask for the instance you just registered:

curl http://localhost:8080/instances/orders
# [{"id":"orders-1","name":"orders","version":"1.0.0","address":"my-host.local",
#   "port":8080,"tags":["api","v1"],"metadata":{},"status":"passing","last_heartbeat":...}]

The memory registry lives inside one process. Use it for development and tests. Swap WithBackend("memory") for WithConsul("127.0.0.1:8500", token) or another backend from the table below and the rest of the code stays the same.

Always give each replica a stable instance ID, with WithServiceID or the FORGE_INSTANCE_ID environment variable. Without one, the extension makes up a new ID (<name>-<unix nanoseconds>) each time it builds the instance, and the health check loop, which re-registers every health_check.interval, adds a fresh entry to the registry on every tick. Shutdown then deregisters an ID that was never registered.

Features

You need Forge Discovery gives you
Services that announce themselves Registration on start and deregistration on stop, using the app's name, version and HTTP port when you don't set them
A registry that matches your platform Consul, etcd, Kubernetes, mDNS/DNS-SD, HTTP seed polling and in-memory backends
Health in the registry A loop that runs your app's health checks and re-registers with passing or critical
Client-side lookups Discover, DiscoverWithTags, DiscoverHealthy, ListServices and Watch
Load balancing SelectInstance and GetServiceURL with round-robin or random picks
Gateway integration FARP endpoints and an optional push to a gateway, carrying your OpenAPI and AsyncAPI schemas
Containers and Kubernetes Advertised address from FORGE_ADVERTISE_ADDR or POD_IP, identity from FORGE_SERVICE_ID and FORGE_INSTANCE_ID

Backends

backend Option Registers this app Notes
memory WithBackend("memory") yes, in process Default. One process only
consul WithConsul(address, token) yes ACL token, datacenter, namespace and TLS from consul.*
etcd WithEtcd(endpoints) yes Keys under etcd.key_prefix (default /services). The tls_* keys aren't applied yet
kubernetes WithKubernetes(namespace, inCluster) no, Kubernetes owns that Discover returns one instance per matching Kubernetes Service
mdns WithMDNS(domain) yes Bonjour on macOS, Avahi on Linux, DNS-SD on Windows. Service type defaults to _<name>._tcp
http WithHTTPSeeds(urls...) no Polls each seed's /_farp/discovery endpoint
eureka WithEureka(urls) Not implemented. Startup fails with "eureka backend not yet implemented"

The Consul backend attaches a 30-second TTL check to the registration, which the health loop keeps passing, and tells Consul to drop the instance after a minute in a critical state. The mDNS backend has its own options for multi-service browsing (WithMDNSServiceTypes), the watch interval, the browse timeout, TTL, network interface and IPv6.

Find other services

The service is in the container as *discovery.Service, also under the names discovery and discovery.Service:

svc, err := forge.Inject[*discovery.Service](app.Container())
if err != nil {
	return err
}

// Every instance, or only those with all of these tags.
all, err := svc.Discover(ctx, "payments")
v2, err := svc.DiscoverWithTags(ctx, "payments", []string{"v2"})

// Only instances whose status is passing.
healthy, err := svc.DiscoverHealthy(ctx, "payments")

// One healthy instance, picked round-robin, as a base URL.
url, err := svc.GetServiceURL(ctx, "payments", "http", discovery.LoadBalanceRoundRobin)

If you want the instance and not a URL, call SelectInstance. Two strategies work: LoadBalanceRoundRobin and LoadBalanceRandom. You'll also see LoadBalanceLeastConnections and LoadBalanceWeightedRoundRobin in the package, but they fall back to round-robin today.

To react when the instances of a service change, either pass a callback when you build the extension:

discovery.NewExtension(
	discovery.WithBackend("consul"),
	discovery.WithWatch([]string{"payments"}, func(instances []*discovery.ServiceInstance) {
		log.Printf("payments now has %d instances", len(instances))
	}),
)

or call svc.Watch(ctx, "payments", onChange) yourself. Each ServiceInstance has URL(scheme), HasTag, HasAllTags, IsHealthy and GetMetadata helpers.

Where the address comes from

You can set service.address and service.port directly. When you don't, the extension works it out:

Field Taken from, in order
Name FORGE_SERVICE_ID, service.name, the app's name
Version service.version, the app's version
Port service.port, then the port in the app's HTTPAddress
Address service.address, FORGE_ADVERTISE_ADDR, POD_IP, the host in the app's HTTPAddress, the machine's hostname, then localhost
Instance ID FORGE_INSTANCE_ID, <name>-<hostname> when FORGE_SERVICE_ID is set, service.id, a generated one

A wildcard listen address such as 0.0.0.0 or :: is never advertised. In a Kubernetes pod, expose POD_IP through the downward API and you get the pod's IP.

Publish routes to a gateway with FARP

FARP is the Forge API gateway registration protocol, implemented in xraph/farp. Turn it on and the extension adds four routes to your app:

Route What it returns
GET /_farp/manifest The service manifest: routes, endpoints, capabilities and schema references
GET /_farp/health FARP health for this instance
GET /_farp/schemas/:type The OpenAPI or AsyncAPI schema
GET /_farp/discovery Instance ID, name, version, address, port, tags and metadata

The manifest is built after every extension has started (in a PhaseAfterStart hook), so it sees all of your routes, including the ones other extensions add during their own start. Until then the first three answer 503. With gateway_url set, the extension also posts its registration to <gateway_url>/_farp/v1/register once the HTTP server is listening, retries with backoff until that works, and calls /_farp/v1/deregister/<name> on shutdown.

discovery.NewExtension(
	discovery.WithBackend("mdns"),
	discovery.WithServiceName("users"),
	discovery.WithServiceID("users-1"),
	discovery.WithFARPEnabled(true),
	discovery.WithFARPGatewayURL("http://localhost:7903"),
	discovery.WithFARPPathRules(
		farp.PathRule{Pattern: "/internal/**", Action: farp.PathRuleExclude},
	),
)

Internal paths (/_/*, /_farp/* and similar) are left out of the published schemas unless you call WithFARPExcludeInternalPaths(false). Path rules are checked in order and the first match wins. When farp.schemas is empty and farp.auto_register is on (the default), the extension publishes whichever of the OpenAPI and AsyncAPI specs your app has turned on.

Configuration

The extension reads extensions.discovery from your config, falling back to discovery. Options you pass in code are applied on top of the file. Here's a Consul setup:

extensions:
  discovery:
    enabled: true
    backend: consul
    service:
      name: orders
      id: orders-1
      version: 1.0.0
      port: 8080
      tags: [api, v1]
      metadata:
        team: checkout
      enable_auto_deregister: true
    health_check:
      enabled: true
      interval: 10s
      timeout: 5s
    watch:
      enabled: true
      services: [payments]
    consul:
      address: 127.0.0.1:8500
      token: ""
      datacenter: dc1
Key Default What it does
enabled true Turns the extension off without removing it. Must be a boolean
backend memory memory, consul, etcd, kubernetes, mdns, http or eureka
service.name, id, version, address, port empty This instance. See where the address comes from
service.tags, service.metadata empty Stored with the registration
service.enable_auto_deregister true Deregisters on shutdown when FARP is off
health_check.enabled true Runs the health loop
health_check.interval, timeout 10s, 5s How often to check, and how long a check may take
watch.enabled, watch.services false, [] Services to watch from start
consul.address, token, datacenter, namespace 127.0.0.1:8500 Consul agent and ACL settings
consul.tls_enabled, tls_ca_file, tls_cert, tls_key off Consul TLS
etcd.endpoints, username, password, dial_timeout, key_prefix 127.0.0.1:2379, 5s, /services etcd client
kubernetes.namespace, in_cluster, kubeconfig_path, service_type, label_selector default, true, ClusterIP Kubernetes API access and filtering
mdns.domain, service_type, service_types, watch_interval, interface, ipv6, browse_timeout, ttl local., 3s, 120 mDNS registration and browsing
http.seeds, poll_interval, timeout [], 10s, 5s HTTP seed polling
farp.enabled, auto_register, gateway_url false, true, empty FARP endpoints and gateway push
farp.schemas, endpoints, capabilities, path_rules What the manifest publishes
farp.exclude_internal_paths, collapse_service_tags true, false Schema filtering and tag grouping

The common settings have With... options in config.go: WithServiceName, WithServiceID, WithServiceAddress, WithServiceTags, WithServiceMetadata, WithHealthCheck, WithWatch, WithConsul, WithEtcd, WithKubernetes, WithMDNS, WithHTTPSeeds, the WithFARP... family and more. WithConfig replaces the whole config.

A few keys are read but have no effect yet: health_check.http, health_check.tcp, health_check.grpc and health_check.deregister_critical_service_after (the loop always uses your app's health checks, and Consul's deregistration delay is fixed at one minute), watch.tags, and farp.strategy.

Documentation

Topic Where
What the extension does Features
Every config key Configuration
Exported functions and types Functions, pkg.go.dev
A FARP and mDNS config file examples/farp-mdns.yaml
The FARP protocol xraph/farp
Extensions in general Forge extension system

FAQ

How do I register a Go service with Consul?

Add discovery.NewExtension(discovery.WithConsul("127.0.0.1:8500", token), discovery.WithServiceID("orders-1")) to your Forge app. It registers on start with the app's name and HTTP port, reports health every ten seconds and deregisters on shutdown.

Can Go services find each other without Consul or etcd?

Yes. On one machine or a local network, use the mdns backend, which needs no server. Across hosts without a registry, use the http backend with a list of seed URLs that run the extension with FARP on.

Does Forge Discovery do client-side load balancing?

It does simple client-side picks. SelectInstance and GetServiceURL choose a healthy instance round-robin or at random. Connection counting and weights aren't implemented.

Why does my service show up many times in the registry?

You haven't set an instance ID. Set WithServiceID, service.id or FORGE_INSTANCE_ID, and the health loop updates one entry.

Contributing

The extension is its own Go module inside the Forge repository. Test it from its directory:

cd extensions/discovery
GOWORK=off go test -race ./...
GOWORK=off golangci-lint run ./...

AGENTS.md has the full build, lint and release rules for the repository, and CONTRIBUTING.md has the contribution guidelines.

License

Forge Discovery is part of Forge and is available under the Apache License 2.0.

Documentation

Index

Constants

View Source
const (
	// ServiceKey is the DI key for the discovery service.
	ServiceKey = "discovery"
	// ServiceKeyLegacy is the legacy DI key for the discovery service.
	ServiceKeyLegacy = "discovery.Service"
)

DI container keys for discovery extension services.

View Source
const (
	// HealthStatusPassing indicates the service is healthy.
	HealthStatusPassing = backends.HealthStatusPassing

	// HealthStatusWarning indicates the service has warnings.
	HealthStatusWarning = backends.HealthStatusWarning

	// HealthStatusCritical indicates the service is unhealthy.
	HealthStatusCritical = backends.HealthStatusCritical

	// HealthStatusUnknown indicates the health status is unknown.
	HealthStatusUnknown = backends.HealthStatusUnknown
)

Variables

This section is empty.

Functions

func NewExtension

func NewExtension(opts ...ConfigOption) forge.Extension

NewExtension creates a new service discovery extension.

func NewExtensionWithConfig

func NewExtensionWithConfig(config Config) forge.Extension

NewExtensionWithConfig creates a new extension with complete config.

Types

type Backend

type Backend = backends.Backend

Backend defines the interface for service discovery backends.

type Config

type Config struct {
	// Enabled determines if service discovery is enabled
	Enabled bool `json:"enabled" yaml:"enabled"`

	// Backend specifies the service discovery backend
	// Options: "consul", "etcd", "kubernetes", "eureka", "memory"
	Backend string `json:"backend" yaml:"backend"`

	// Service configuration for this instance
	Service ServiceConfig `json:"service" yaml:"service"`

	// Health check settings
	HealthCheck HealthCheckConfig `json:"health_check" yaml:"health_check"`

	// Watch settings
	Watch WatchConfig `json:"watch" yaml:"watch"`

	// Consul-specific settings
	Consul ConsulConfig `json:"consul" yaml:"consul"`

	// Etcd-specific settings
	Etcd EtcdConfig `json:"etcd" yaml:"etcd"`

	// Kubernetes-specific settings
	Kubernetes KubernetesConfig `json:"kubernetes" yaml:"kubernetes"`

	// Eureka-specific settings
	Eureka EurekaConfig `json:"eureka" yaml:"eureka"`

	// MDNS-specific settings
	MDNS MDNSConfig `json:"mdns" yaml:"mdns"`

	// HTTP polling-based discovery settings
	HTTP HTTPConfig `json:"http" yaml:"http"`

	// FARP (Forge API Gateway Registration Protocol) settings
	FARP FARPConfig `json:"farp" yaml:"farp"`
}

Config holds service discovery extension configuration.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default service discovery configuration.

type ConfigOption

type ConfigOption func(*Config)

ConfigOption configures the service discovery extension.

func WithAppConfig

func WithAppConfig(appConfig forge.AppConfig) ConfigOption

WithAppConfig sets the app config for auto-detecting service info This allows the extension to read Name, Version, and HTTPAddress from the app config when Service config is not fully provided.

func WithAutoDeregister added in v0.9.9

func WithAutoDeregister(enabled bool) ConfigOption

WithAutoDeregister sets whether the service is automatically deregistered on shutdown.

func WithBackend

func WithBackend(backend string) ConfigOption

WithBackend sets the service discovery backend.

func WithConfig

func WithConfig(config Config) ConfigOption

WithConfig sets the complete config.

func WithConsul

func WithConsul(address, token string) ConfigOption

WithConsul configures Consul backend.

func WithEnabled

func WithEnabled(enabled bool) ConfigOption

WithEnabled sets whether service discovery is enabled.

func WithEtcd

func WithEtcd(endpoints []string) ConfigOption

WithEtcd configures etcd backend.

func WithEureka

func WithEureka(urls []string) ConfigOption

WithEureka configures Eureka backend.

func WithFARP added in v0.9.9

func WithFARP(farp FARPConfig) ConfigOption

WithFARP sets the full FARP configuration.

func WithFARPAutoRegister added in v0.9.9

func WithFARPAutoRegister(autoRegister bool) ConfigOption

WithFARPAutoRegister sets whether schemas are automatically registered on startup.

func WithFARPCapabilities added in v0.9.9

func WithFARPCapabilities(capabilities ...string) ConfigOption

WithFARPCapabilities sets the FARP capability tags.

func WithFARPCollapseServiceTags added in v1.6.0

func WithFARPCollapseServiceTags(collapse bool) ConfigOption

WithFARPCollapseServiceTags groups all routes under a single tag matching the service name instead of individual tags.

func WithFARPEnabled added in v0.9.9

func WithFARPEnabled(enabled bool) ConfigOption

WithFARPEnabled sets whether FARP schema registration is enabled.

func WithFARPEndpoints added in v0.9.9

func WithFARPEndpoints(endpoints FARPEndpointsConfig) ConfigOption

WithFARPEndpoints sets the FARP endpoint configuration.

func WithFARPExcludeInternalPaths added in v1.6.0

func WithFARPExcludeInternalPaths(exclude bool) ConfigOption

WithFARPExcludeInternalPaths sets whether internal framework paths are filtered from published schemas. Default is true.

func WithFARPGatewayURL added in v1.4.0

func WithFARPGatewayURL(url string) ConfigOption

WithFARPGatewayURL sets the gateway URL for push-based service registration. When set, the service will POST its registration to the gateway on startup and send deregistration on shutdown.

func WithFARPPathRules added in v1.6.0

func WithFARPPathRules(rules ...farp.PathRule) ConfigOption

WithFARPPathRules sets path include/exclude rules for filtering which API paths are published to the gateway. Supports glob wildcards.

func WithFARPSchemas added in v0.9.9

func WithFARPSchemas(schemas ...FARPSchemaConfig) ConfigOption

WithFARPSchemas sets the FARP schema configurations.

func WithFARPStrategy added in v0.9.9

func WithFARPStrategy(strategy string) ConfigOption

WithFARPStrategy sets the FARP registration strategy (push, pull, or hybrid).

func WithHTTPHealthCheck

func WithHTTPHealthCheck(endpoint string, interval time.Duration) ConfigOption

WithHTTPHealthCheck sets HTTP health check.

func WithHTTPPollInterval added in v1.4.0

func WithHTTPPollInterval(d time.Duration) ConfigOption

WithHTTPPollInterval sets the HTTP backend poll interval.

func WithHTTPSeeds added in v1.4.0

func WithHTTPSeeds(seeds ...string) ConfigOption

WithHTTPSeeds configures the HTTP backend with seed URLs.

func WithHTTPTimeout added in v1.4.0

func WithHTTPTimeout(d time.Duration) ConfigOption

WithHTTPTimeout sets the HTTP backend request timeout.

func WithHealthCheck

func WithHealthCheck(config HealthCheckConfig) ConfigOption

WithHealthCheck sets the health check configuration.

func WithKubernetes

func WithKubernetes(namespace string, inCluster bool) ConfigOption

WithKubernetes configures Kubernetes backend.

func WithMDNS

func WithMDNS(domain string) ConfigOption

WithMDNS configures mDNS backend.

func WithMDNSBrowseTimeout added in v0.9.9

func WithMDNSBrowseTimeout(d time.Duration) ConfigOption

WithMDNSBrowseTimeout sets the mDNS browse operation timeout.

func WithMDNSIPv6 added in v0.9.9

func WithMDNSIPv6(enabled bool) ConfigOption

WithMDNSIPv6 sets whether mDNS uses IPv6.

func WithMDNSInterface added in v0.9.9

func WithMDNSInterface(iface string) ConfigOption

WithMDNSInterface sets the network interface for mDNS operations.

func WithMDNSServiceType added in v0.9.9

func WithMDNSServiceType(serviceType string) ConfigOption

WithMDNSServiceType sets the mDNS service type for registration.

func WithMDNSServiceTypes added in v0.9.9

func WithMDNSServiceTypes(types ...string) ConfigOption

WithMDNSServiceTypes sets the mDNS service types to browse for discovery.

func WithMDNSTTL added in v0.9.9

func WithMDNSTTL(ttl uint32) ConfigOption

WithMDNSTTL sets the mDNS DNS record time-to-live.

func WithMDNSWatchInterval added in v0.9.9

func WithMDNSWatchInterval(d time.Duration) ConfigOption

WithMDNSWatchInterval sets the mDNS watch polling interval.

func WithService

func WithService(service ServiceConfig) ConfigOption

WithService sets the service configuration.

func WithServiceAddress

func WithServiceAddress(address string, port int) ConfigOption

WithServiceAddress sets the service address and port.

func WithServiceID added in v0.9.9

func WithServiceID(id string) ConfigOption

WithServiceID sets the unique service instance identifier.

func WithServiceMetadata

func WithServiceMetadata(metadata map[string]string) ConfigOption

WithServiceMetadata sets the service metadata.

func WithServiceName

func WithServiceName(name string) ConfigOption

WithServiceName sets the service name.

func WithServiceTags

func WithServiceTags(tags ...string) ConfigOption

WithServiceTags sets the service tags.

func WithServiceVersion added in v0.9.9

func WithServiceVersion(version string) ConfigOption

WithServiceVersion sets the service version string.

func WithWatch

func WithWatch(services []string, onChange func([]*ServiceInstance)) ConfigOption

WithWatch enables service watching.

type ConsulConfig

type ConsulConfig = backends.ConsulConfig

ConsulConfig holds Consul-specific configuration.

type EtcdConfig

type EtcdConfig = backends.EtcdConfig

EtcdConfig holds etcd-specific configuration.

type EurekaConfig

type EurekaConfig = backends.EurekaConfig

EurekaConfig holds Eureka-specific configuration.

type Extension

type Extension struct {
	*forge.BaseExtension
	// contains filtered or unexported fields
}

Extension implements forge.Extension for service discovery. The extension manages route registration and self-registration, while Service lifecycle is managed by Vessel.

func (*Extension) Dependencies

func (e *Extension) Dependencies() []string

Dependencies returns extension dependencies.

func (*Extension) Health

func (e *Extension) Health(ctx context.Context) error

Health checks the extension health. Service health is managed by Vessel through Service.Health().

func (*Extension) LocalServiceID added in v1.6.2

func (e *Extension) LocalServiceID() string

LocalServiceID returns the host process's own registered service ID, or "" when the extension hasn't started or didn't register an instance. Consumers (e.g. the dashboard discovery integration) use this to filter the host's self-registration out of cross-service polling, since memory- backed discovery surfaces the local registration alongside any peers.

func (*Extension) Register

func (e *Extension) Register(app forge.App) error

Register registers the extension with the app.

func (*Extension) Run added in v1.6.0

func (e *Extension) Run(ctx context.Context) error

Run executes background tasks after the app server is running. Implements forge.RunnableExtension.

func (*Extension) Service

func (e *Extension) Service() *Service

Service returns the service discovery service.

func (*Extension) ServiceNode added in v1.6.0

func (e *Extension) ServiceNode() *farpdiscovery.ServiceNode

ServiceNode returns the FARP ServiceNode if active, or nil.

func (*Extension) Shutdown added in v1.6.0

func (e *Extension) Shutdown(ctx context.Context) error

Shutdown gracefully stops background tasks before the app shuts down. Implements forge.RunnableExtension.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start starts the extension and handles self-registration.

func (*Extension) Stop

func (e *Extension) Stop(ctx context.Context) error

Stop stops the extension gracefully.

type FARPConfig

type FARPConfig struct {
	// Enabled determines if FARP schema registration is enabled
	Enabled bool `json:"enabled" yaml:"enabled"`

	// AutoRegister automatically registers schemas on service startup
	AutoRegister bool `json:"auto_register" yaml:"auto_register"`

	// Strategy for schema storage: "push" (to registry), "pull" (HTTP only), "hybrid"
	Strategy string `json:"strategy" yaml:"strategy"`

	// Schemas to register
	Schemas []FARPSchemaConfig `json:"schemas" yaml:"schemas"`

	// Endpoints configuration
	Endpoints FARPEndpointsConfig `json:"endpoints" yaml:"endpoints"`

	// Capabilities advertised by this service
	Capabilities []string `json:"capabilities" yaml:"capabilities"`

	// GatewayURL is the base URL of the API gateway to push registrations to.
	// When set, the service will POST its registration on startup and
	// send deregistration on shutdown.
	// Example: "http://localhost:7903"
	GatewayURL string `json:"gateway_url" yaml:"gateway_url"`

	// ExcludeInternalPaths filters out internal framework paths (/_farp/*, /_/*, /docs/*, etc.)
	// from the OpenAPI/AsyncAPI schemas published to the gateway.
	// Default: true
	ExcludeInternalPaths *bool `json:"exclude_internal_paths,omitempty" yaml:"exclude_internal_paths,omitempty"`

	// CollapseServiceTags groups all routes under a single tag matching the service name
	// instead of preserving individual tags (e.g., "TwinOS" instead of "TwinOS_Agents",
	// "TwinOS_Query", etc.). This is passed to the gateway's merger via the manifest.
	// Default: false
	CollapseServiceTags bool `json:"collapse_service_tags" yaml:"collapse_service_tags"`

	// PathRules defines include/exclude rules for controlling which API paths
	// are published to the gateway. Rules are evaluated in order; first match wins.
	// Supports glob patterns: "*" matches one path segment, "**" matches zero or more.
	// Paths not matching any rule are included by default.
	//
	// Example:
	//   PathRules: []farp.PathRule{
	//       {Pattern: "/internal/**", Action: farp.PathRuleExclude},
	//       {Pattern: "/api/users/*", Action: farp.PathRuleExclude},
	//       {Pattern: "/api/**", Action: farp.PathRuleInclude},
	//   }
	PathRules []farp.PathRule `json:"path_rules,omitempty" yaml:"path_rules,omitempty"`
}

FARPConfig holds FARP (Forge API Gateway Registration Protocol) configuration.

func (*FARPConfig) ShouldExcludeInternalPaths added in v1.6.0

func (c *FARPConfig) ShouldExcludeInternalPaths() bool

ShouldExcludeInternalPaths returns whether internal paths should be filtered. Defaults to true when not explicitly set.

type FARPEndpointsConfig

type FARPEndpointsConfig struct {
	// Health check endpoint
	Health string `json:"health" yaml:"health"`

	// Metrics endpoint
	Metrics string `json:"metrics,omitempty" yaml:"metrics,omitempty"`

	// OpenAPI spec endpoint
	OpenAPI string `json:"openapi,omitempty" yaml:"openapi,omitempty"`

	// AsyncAPI spec endpoint
	AsyncAPI string `json:"asyncapi,omitempty" yaml:"asyncapi,omitempty"`

	// gRPC reflection enabled
	GRPCReflection bool `json:"grpc_reflection,omitempty" yaml:"grpc_reflection,omitempty"`

	// GraphQL endpoint
	GraphQL string `json:"graphql,omitempty" yaml:"graphql,omitempty"`
}

FARPEndpointsConfig configures service endpoints.

type FARPLocationConfig

type FARPLocationConfig struct {
	// Type: "http", "registry", "inline"
	Type string `json:"type" yaml:"type"`

	// URL for HTTP location type
	URL string `json:"url,omitempty" yaml:"url,omitempty"`

	// RegistryPath for registry location type
	RegistryPath string `json:"registry_path,omitempty" yaml:"registry_path,omitempty"`

	// Headers for HTTP authentication
	Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`
}

FARPLocationConfig configures where the schema is located.

type FARPSchemaConfig

type FARPSchemaConfig struct {
	// Type of schema: "openapi", "asyncapi", "grpc", "graphql"
	Type string `json:"type" yaml:"type"`

	// SpecVersion is the schema specification version (e.g., "3.1.0" for OpenAPI)
	SpecVersion string `json:"spec_version" yaml:"spec_version"`

	// Location configuration
	Location FARPLocationConfig `json:"location" yaml:"location"`

	// ContentType is the schema content type
	ContentType string `json:"content_type" yaml:"content_type"`
}

FARPSchemaConfig configures a single schema to register.

type HTTPConfig added in v1.4.0

type HTTPConfig = backends.HTTPConfig

HTTPConfig holds HTTP polling-based discovery configuration. Polls known service URLs at their /_farp/discovery endpoints.

type HealthCheckConfig

type HealthCheckConfig struct {
	// Enabled determines if health checks are enabled
	Enabled bool `json:"enabled" yaml:"enabled"`

	// Interval is how often to perform health checks
	Interval time.Duration `json:"interval" yaml:"interval"`

	// Timeout is the health check timeout
	Timeout time.Duration `json:"timeout" yaml:"timeout"`

	// DeregisterCriticalServiceAfter is when to deregister unhealthy services
	DeregisterCriticalServiceAfter time.Duration `json:"deregister_critical_service_after" yaml:"deregister_critical_service_after"`

	// HTTP health check endpoint (if using HTTP)
	HTTP string `json:"http" yaml:"http"`

	// TCP health check address (if using TCP)
	TCP string `json:"tcp" yaml:"tcp"`

	// gRPC health check address (if using gRPC)
	GRPC string `json:"grpc" yaml:"grpc"`
}

HealthCheckConfig holds health check configuration.

type HealthStatus

type HealthStatus = backends.HealthStatus

HealthStatus represents service health status.

type KubernetesConfig

type KubernetesConfig = backends.KubernetesConfig

KubernetesConfig holds Kubernetes-specific configuration.

type LoadBalanceStrategy

type LoadBalanceStrategy string

LoadBalanceStrategy defines load balancing strategies.

const (
	// LoadBalanceRoundRobin uses round-robin selection.
	LoadBalanceRoundRobin LoadBalanceStrategy = "round_robin"

	// LoadBalanceRandom uses random selection.
	LoadBalanceRandom LoadBalanceStrategy = "random"

	// LoadBalanceLeastConnections uses least connections selection.
	LoadBalanceLeastConnections LoadBalanceStrategy = "least_connections"

	// LoadBalanceWeightedRoundRobin uses weighted round-robin.
	LoadBalanceWeightedRoundRobin LoadBalanceStrategy = "weighted_round_robin"
)

type MDNSConfig

type MDNSConfig = backends.MDNSConfig

MDNSConfig holds mDNS/DNS-SD-specific configuration Works natively on macOS (Bonjour), Linux (Avahi), and Windows (DNS-SD).

type SchemaPublisher

type SchemaPublisher struct {
	// contains filtered or unexported fields
}

SchemaPublisher handles FARP schema registration.

func NewSchemaPublisher

func NewSchemaPublisher(config FARPConfig, app forge.App) *SchemaPublisher

NewSchemaPublisher creates a new schema publisher.

func (*SchemaPublisher) Close

func (p *SchemaPublisher) Close() error

Close closes the schema publisher.

func (*SchemaPublisher) GetManifest

func (p *SchemaPublisher) GetManifest(ctx context.Context, instanceID string) (*farp.SchemaManifest, error)

GetManifest retrieves the manifest for an instance.

func (*SchemaPublisher) GetMetadataForDiscovery

func (p *SchemaPublisher) GetMetadataForDiscovery(baseURL string) map[string]string

GetMetadataForDiscovery returns metadata that should be added to service discovery This allows mDNS/Bonjour and other backends to advertise FARP endpoints in TXT records.

func (*SchemaPublisher) Publish

func (p *SchemaPublisher) Publish(ctx context.Context, instanceID string) error

Publish generates and publishes schemas for the service.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service provides high-level service discovery operations. It implements di.Service for lifecycle management.

func NewService

func NewService(config Config, backend Backend, logger forge.Logger) *Service

NewService creates a new service discovery service.

func (*Service) Backend

func (s *Service) Backend() Backend

Backend returns the underlying discovery backend. This allows the extension to access the backend for self-registration.

func (*Service) Deregister

func (s *Service) Deregister(ctx context.Context, serviceID string) error

Deregister deregisters a service instance.

func (*Service) Discover

func (s *Service) Discover(ctx context.Context, serviceName string) ([]*ServiceInstance, error)

Discover discovers service instances by name.

func (*Service) DiscoverHealthy

func (s *Service) DiscoverHealthy(ctx context.Context, serviceName string) ([]*ServiceInstance, error)

DiscoverHealthy discovers only healthy service instances.

func (*Service) DiscoverWithTags

func (s *Service) DiscoverWithTags(ctx context.Context, serviceName string, tags []string) ([]*ServiceInstance, error)

DiscoverWithTags discovers service instances by name and tags.

func (*Service) GetServiceURL

func (s *Service) GetServiceURL(ctx context.Context, serviceName string, scheme string, strategy LoadBalanceStrategy) (string, error)

GetServiceURL gets a service URL using load balancing.

func (*Service) Health

func (s *Service) Health(ctx context.Context) error

Health checks backend health.

func (*Service) ListServices

func (s *Service) ListServices(ctx context.Context) ([]string, error)

ListServices lists all registered services.

func (*Service) Name

func (s *Service) Name() string

Name returns the service name for Vessel's lifecycle management.

func (*Service) Register

func (s *Service) Register(ctx context.Context, instance *ServiceInstance) error

Register registers a service instance.

func (*Service) SelectInstance

func (s *Service) SelectInstance(ctx context.Context, serviceName string, strategy LoadBalanceStrategy) (*ServiceInstance, error)

SelectInstance selects a single service instance using load balancing strategy.

func (*Service) Start

func (s *Service) Start(ctx context.Context) error

Start initializes the backend and starts background processes.

func (*Service) Stop

func (s *Service) Stop(ctx context.Context) error

Stop stops the service gracefully.

func (*Service) Watch

func (s *Service) Watch(ctx context.Context, serviceName string, onChange func([]*ServiceInstance)) error

Watch watches for changes to a service.

type ServiceConfig

type ServiceConfig struct {
	// Name is the service name
	Name string `json:"name" yaml:"name"`

	// ID is the unique service instance ID (auto-generated if empty)
	ID string `json:"id" yaml:"id"`

	// Version is the service version
	Version string `json:"version" yaml:"version"`

	// Address is the service address
	Address string `json:"address" yaml:"address"`

	// Port is the service port
	Port int `json:"port" yaml:"port"`

	// Tags are service tags for filtering
	Tags []string `json:"tags" yaml:"tags"`

	// Metadata is arbitrary service metadata
	Metadata map[string]string `json:"metadata" yaml:"metadata"`

	// EnableAutoDeregister enables automatic deregistration on shutdown
	EnableAutoDeregister bool `json:"enable_auto_deregister" yaml:"enable_auto_deregister"`
}

ServiceConfig holds service registration configuration.

type ServiceEvent

type ServiceEvent struct {
	// Type is the event type
	Type ServiceEventType

	// Service is the service instance
	Service *ServiceInstance

	// Timestamp is when the event occurred
	Timestamp int64
}

ServiceEvent represents a service change event.

type ServiceEventType

type ServiceEventType string

ServiceEventType represents types of service events.

const (
	// ServiceEventRegistered indicates a service was registered.
	ServiceEventRegistered ServiceEventType = "registered"

	// ServiceEventDeregistered indicates a service was deregistered.
	ServiceEventDeregistered ServiceEventType = "deregistered"

	// ServiceEventHealthChanged indicates service health changed.
	ServiceEventHealthChanged ServiceEventType = "health_changed"

	// ServiceEventMetadataChanged indicates service metadata changed.
	ServiceEventMetadataChanged ServiceEventType = "metadata_changed"
)

type ServiceInstance

type ServiceInstance = backends.ServiceInstance

ServiceInstance represents a registered service instance.

type ServiceQuery

type ServiceQuery struct {
	// Name is the service name to query
	Name string

	// Tags are tags to filter by
	Tags []string

	// OnlyHealthy returns only healthy instances
	OnlyHealthy bool

	// LoadBalanceStrategy is the load balancing strategy
	LoadBalanceStrategy LoadBalanceStrategy
}

ServiceQuery represents a service discovery query.

type ServiceRegistration

type ServiceRegistration struct {
	// Service is the service configuration
	Service ServiceConfig

	// HealthCheck is the health check configuration
	HealthCheck HealthCheckConfig
}

ServiceRegistration represents a service registration request.

type WatchConfig

type WatchConfig struct {
	// Enabled determines if service watching is enabled
	Enabled bool `json:"enabled" yaml:"enabled"`

	// Services are service names to watch for changes
	Services []string `json:"services" yaml:"services"`

	// Tags are tags to filter services
	Tags []string `json:"tags" yaml:"tags"`

	// OnChange is called when watched services change
	// This is set programmatically, not via config
	OnChange func(services []*ServiceInstance)
}

WatchConfig holds service watch configuration.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL