This document clarifies exactly what FARP provides versus what you must implement in your service framework or gateway.
FARP provides the protocol, schema tooling, service discovery, and HTTP handlers. You provide the HTTP router and gateway proxy. With ServiceNode and GatewayNode, most integration is a few lines of code.
New in v1.1.0: The
discovery/package provides complete service discovery with 6 backends (Consul, etcd, Kubernetes, Redis, mDNS, Push), auto-registration, health management, and FARP HTTP handlers. See DISCOVERY.md for the full guide.
import (
"github.com/xraph/farp"
"github.com/xraph/farp/providers/openapi"
"github.com/xraph/farp/providers/asyncapi"
)
// 1. Generate schemas from your app
openapiProvider := openapi.NewProvider("3.1.0", "/openapi.json")
openapiSchema, _ := openapiProvider.Generate(ctx, yourApp)
// 2. Create a manifest
manifest := &farp.SchemaManifest{
ServiceName: "user-service",
ServiceVersion: "v1.0.0",
InstanceID: "user-service-abc123",
Schemas: []farp.SchemaDescriptor{
{
Type: farp.SchemaTypeOpenAPI,
SpecVersion: "3.1.0",
Location: farp.SchemaLocation{
Type: farp.LocationTypeHTTP,
URL: "http://localhost:8080/openapi.json",
},
},
},
Endpoints: farp.SchemaEndpoints{
Health: "/health",
Metrics: "/metrics",
OpenAPI: "/openapi.json",
},
}
// 3. Validate it
if err := manifest.Validate(); err != nil {
log.Fatal(err)
}
// 4. Serialize it
manifestJSON, _ := json.Marshal(manifest)// In your HTTP router/server:
// Serve the FARP manifest
http.HandleFunc("/_farp/manifest", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write(manifestJSON)
})
// Serve the OpenAPI schema
http.HandleFunc("/openapi.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write(openapiSchema) // From provider
})
// Health endpoint (your existing implementation)
http.HandleFunc("/health", yourHealthHandler)With Consul:
import "github.com/hashicorp/consul/api"
// Register service with FARP metadata
client, _ := api.NewClient(api.DefaultConfig())
client.Agent().ServiceRegister(&api.AgentServiceRegistration{
ID: "user-service-abc123",
Name: "user-service",
Address: "10.0.1.5",
Port: 8080,
Meta: map[string]string{
"farp.enabled": "true",
"farp.manifest": "http://10.0.1.5:8080/_farp/manifest",
"farp.openapi": "http://10.0.1.5:8080/openapi.json",
},
})With mDNS/Bonjour:
import "github.com/grandcat/zeroconf"
// Register with FARP metadata in TXT records
txt := []string{
"version=1.0.0",
"farp.enabled=true",
"farp.manifest=http://192.168.1.100:8080/_farp/manifest",
"farp.openapi=http://192.168.1.100:8080/openapi.json",
}
server, _ := zeroconf.Register(
"user-service-abc123",
"_farp._tcp",
"local.",
8080,
txt,
nil,
)
defer server.Shutdown()With Kubernetes:
apiVersion: v1
kind: Service
metadata:
name: user-service
annotations:
farp.enabled: "true"
farp.manifest: "http://user-service:8080/_farp/manifest"
farp.openapi: "http://user-service:8080/openapi.json"
spec:
selector:
app: user-service
ports:
- port: 8080If using LocationTypeRegistry:
// Example with Consul KV store
client, _ := api.NewClient(api.DefaultConfig())
// Store OpenAPI schema
client.KV().Put(&api.KVPair{
Key: "/schemas/user-service/v1/openapi",
Value: openapiSchema,
}, nil)
// Update manifest to point to registry
manifest.Schemas[0].Location = farp.SchemaLocation{
Type: farp.LocationTypeRegistry,
RegistryPath: "/schemas/user-service/v1/openapi",
}import (
"github.com/xraph/farp"
"github.com/xraph/farp/merger"
)
// 1. Parse a manifest
var manifest farp.SchemaManifest
json.Unmarshal(manifestJSON, &manifest)
// 2. Validate it
if err := manifest.Validate(); err != nil {
// Handle invalid manifest
}
// 3. Merge multiple service schemas (optional)
mergerClient := merger.NewMerger(merger.DefaultMergerConfig())
mergedOpenAPI, _ := mergerClient.MergeOpenAPI([]merger.ServiceSchema{...})With Consul:
import "github.com/hashicorp/consul/api"
client, _ := api.NewClient(api.DefaultConfig())
// Watch for service changes
plan, _ := watch.Parse(map[string]interface{}{
"type": "service",
"service": "user-service",
})
plan.Handler = func(idx uint64, data interface{}) {
services := data.([]*api.ServiceEntry)
for _, svc := range services {
// Extract FARP manifest URL from metadata
manifestURL := svc.Service.Meta["farp.manifest"]
// Fetch manifest (see next step)
manifest := fetchManifest(manifestURL)
// Configure routes (see step 3)
configureRoutes(manifest)
}
}
plan.Run(client)With mDNS:
import "github.com/grandcat/zeroconf"
resolver, _ := zeroconf.NewResolver(nil)
entries := make(chan *zeroconf.ServiceEntry)
go func() {
for entry := range entries {
// Extract FARP manifest URL from TXT records
for _, txt := range entry.Text {
if strings.HasPrefix(txt, "farp.manifest=") {
manifestURL := strings.TrimPrefix(txt, "farp.manifest=")
// Fetch manifest
manifest := fetchManifest(manifestURL)
// Configure routes
configureRoutes(manifest)
}
}
}
}()
resolver.Browse(context.Background(), "_farp._tcp", "local.", entries)func fetchManifest(url string) (*farp.SchemaManifest, error) {
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var manifest farp.SchemaManifest
if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil {
return nil, err
}
return &manifest, nil
}
func fetchSchema(descriptor farp.SchemaDescriptor) ([]byte, error) {
switch descriptor.Location.Type {
case farp.LocationTypeHTTP:
return fetchHTTPSchema(descriptor.Location.URL)
case farp.LocationTypeRegistry:
return fetchRegistrySchema(descriptor.Location.RegistryPath)
case farp.LocationTypeInline:
return json.Marshal(descriptor.InlineSchema)
default:
return nil, fmt.Errorf("unsupported location type")
}
}
func fetchHTTPSchema(url string) ([]byte, error) {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}func configureRoutes(manifest *farp.SchemaManifest) error {
for _, descriptor := range manifest.Schemas {
schema, err := fetchSchema(descriptor)
if err != nil {
return err
}
switch descriptor.Type {
case farp.SchemaTypeOpenAPI:
routes := convertOpenAPIToRoutes(manifest, schema)
applyRoutes(routes)
case farp.SchemaTypeAsyncAPI:
routes := convertAsyncAPIToRoutes(manifest, schema)
applyRoutes(routes)
case farp.SchemaTypeGRPC:
// gRPC-specific handling
configureGRPCRoutes(manifest, schema)
}
}
return nil
}
func convertOpenAPIToRoutes(manifest *farp.SchemaManifest, schemaJSON []byte) []Route {
var openapi map[string]interface{}
json.Unmarshal(schemaJSON, &openapi)
paths := openapi["paths"].(map[string]interface{})
routes := []Route{}
// Get service instance address
serviceAddr := manifest.Instance.Address
servicePort := manifest.Instance.Port
baseURL := fmt.Sprintf("http://%s:%d", serviceAddr, servicePort)
// Apply routing strategy
basePath := ""
switch manifest.Routing.Strategy {
case farp.MountStrategyRoot:
basePath = "/"
case farp.MountStrategyService:
basePath = "/" + manifest.ServiceName
case farp.MountStrategyCustom:
basePath = manifest.Routing.BasePath
}
for path, methods := range paths {
routes = append(routes, Route{
Path: basePath + path,
TargetURL: baseURL + path,
Methods: extractMethods(methods),
})
}
return routes
}
func applyRoutes(routes []Route) {
// Apply to YOUR gateway's routing table
// This is gateway-specific (Kong, Traefik, Envoy, custom, etc.)
for _, route := range routes {
yourGateway.AddRoute(route)
}
}func monitorHealth(manifest *farp.SchemaManifest) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
healthURL := fmt.Sprintf("http://%s:%d%s",
manifest.Instance.Address,
manifest.Instance.Port,
manifest.Endpoints.Health,
)
for range ticker.C {
resp, err := http.Get(healthURL)
if err != nil || resp.StatusCode != 200 {
// Mark service as unhealthy
yourGateway.MarkUnhealthy(manifest.InstanceID)
} else {
// Mark service as healthy
yourGateway.MarkHealthy(manifest.InstanceID)
}
}
}| Component | Package | Purpose |
|---|---|---|
| Type definitions | types.go |
Data structures for manifests, routing, auth, etc. |
| Schema providers | providers/* |
Generate schemas from code (OpenAPI, gRPC, etc.) |
| Validation | manifest.go |
Ensure manifests are valid |
| Schema merging | merger/* |
Combine multiple schemas into unified docs |
| Registry interface | registry.go |
Abstract storage operations |
| Component | Who Implements | Where |
|---|---|---|
| HTTP servers | Service framework | Your service code |
| HTTP clients | Gateway | Your gateway code |
| Service discovery integration | Both | Your code + discovery backend |
| Registry backend implementations | External | Use Consul, etcd, K8s client libraries |
| Route configuration | Gateway | Your gateway's routing logic |
| Health monitoring | Gateway | Your gateway's health check system |
// In Forge framework's startup code:
// 1. Use FARP providers
openapiProvider := openapi.NewForgeProvider(forgeApp)
asyncapiProvider := asyncapi.NewForgeProvider(forgeApp)
openapi := openapiProvider.Generate(ctx, forgeApp)
asyncapi := asyncapiProvider.Generate(ctx, forgeApp)
// 2. Create manifest
manifest := farp.NewManifest(
"user-service",
"v1.0.0",
"user-service-abc123",
)
manifest.AddSchema(farp.SchemaTypeOpenAPI, "3.1.0", farp.SchemaLocation{
Type: farp.LocationTypeHTTP,
URL: "http://localhost:8080/openapi.json",
})
// 3. Expose via HTTP
forgeApp.GET("/_farp/manifest", func(c *forge.Context) error {
return c.JSON(manifest)
})
forgeApp.GET("/openapi.json", func(c *forge.Context) error {
return c.JSON(openapi)
})
// 4. Register with mDNS
mdnsBackend.Register(ctx, manifest)// In octopus-gateway's startup code:
// 1. Watch mDNS for services
let mut browser = mdns::Browser::new("_farp._tcp")?;
browser.on_service_discovered(|entry| {
// Extract manifest URL
let manifest_url = entry.txt_records
.iter()
.find(|txt| txt.starts_with("farp.manifest="))
.map(|txt| txt.strip_prefix("farp.manifest=").unwrap());
// Fetch manifest
let manifest = fetch_manifest(manifest_url)?;
// Fetch schemas
for descriptor in manifest.schemas {
let schema = fetch_schema(&descriptor)?;
// Convert to routes
let routes = convert_to_routes(&manifest, &schema);
// Apply to gateway
routing_table.apply(routes);
}
});
browser.start()?;You should implement:
- ✅ HTTP endpoints to serve
/_farp/manifestand schemas - ✅ Integration with discovery backend (Consul, mDNS, K8s)
- ✅ Call FARP providers to generate schemas
- ✅ Health and metrics endpoints
You should use from FARP:
- Type definitions (
SchemaManifest, etc.) - Schema providers (
providers/openapi, etc.) - Validation (
manifest.Validate())
You should implement:
- ✅ HTTP client to fetch manifests and schemas
- ✅ Watcher for discovery backend (Consul, mDNS, K8s)
- ✅ Schema-to-route conversion logic
- ✅ Gateway-specific route application
- ✅ Health monitoring
You should use from FARP:
- Type definitions (
SchemaManifest, etc.) - Schema merging (
merger/*) for unified docs - Validation (
manifest.Validate())
Service side:
- Implement HTTP endpoints
- Use FARP providers
Gateway side:
- Implement HTTP client
- Implement route conversion
- Use FARP merger (optional)
A: FARP is a protocol spec, not a framework. Including HTTP transport would:
- Create unnecessary dependencies
- Limit flexibility (what if you want gRPC transport?)
- Increase complexity
- Violate single responsibility principle
Think of it like Protocol Buffers: protobuf defines the format, but you choose the transport (gRPC, HTTP, message queues, etc.).
A: No, it's a reference implementation showing how to integrate FARP. Real gateways should implement their own logic tailored to their architecture, error handling, and performance requirements.
A:
- HTTP - Simpler, no backend storage needed, service controls freshness
- Registry - More reliable, schemas persist even if service dies, faster gateway startup
- Both (Hybrid) - Best for production, high availability
A: Yes! FARP is just a JSON format. Any language can:
- Generate a
SchemaManifestJSON document - Expose it via HTTP
- Register with discovery backend
The Go library is a reference implementation.
- For Services: Read Forge FARP Integration Guide
- For Gateways: Read Gateway Discovery Examples
- For Both: Read FARP Specification