distribute/discovery/behaviour
Discovery behaviour contract for peer discovery.
This module defines the contract that discovery adapters must implement to provide peer discovery and membership tracking in the distribute library. Discovery adapters are responsible for finding peers, tracking membership state, and emitting membership events.
Design Philosophy
This behaviour follows distribute’s patterns:
- Pure function signatures (like
crypto/provider) - Actor-based implementations (like
registry/actor) - Event-driven with
process.Subject(likeglobal,messaging) - Capability negotiation support (like
handshake)
Implementation Approaches
Adapters can be implemented as:
- Actor-based: Use
gleam/otp/actor(recommended, seeregistry/actor) - DNS-SRV: Query DNS for service records
- Kubernetes: Use K8s API for pod discovery
- Static: Fixed list of peers for simple deployments
Integration with distribute
Discovery adapters integrate with:
handshakemodule for triggering negotiation when peers joinregistryfor tracking peer metadatatransportfor establishing connections to discovered peers
Example Implementation
See distribute/discovery/beam_adapter for a reference implementation.
Types
Errors that can occur during discovery operations.
Classification
- Transient (retryable):
SyncFailed,Timeout - Permanent (do not retry):
InvalidConfiguration,PeerNotFound
pub type DiscoveryError {
StartFailed(reason: String)
StopFailed(reason: String)
ShutdownTimeout(elapsed_ms: Int)
SyncFailed(reason: String)
PeerNotFound(peer: String)
InvalidConfiguration(reason: String)
Timeout(elapsed_ms: Int)
SubscriptionFailed(reason: String)
}
Constructors
-
StartFailed(reason: String)Failed to start the discovery adapter
-
StopFailed(reason: String)Failed to stop the discovery adapter
-
ShutdownTimeout(elapsed_ms: Int)Shutdown timed out
-
SyncFailed(reason: String)Failed to sync with discovery backend
-
PeerNotFound(peer: String)Peer not found in current membership
-
InvalidConfiguration(reason: String)Invalid adapter configuration
-
Timeout(elapsed_ms: Int)Operation timed out
-
SubscriptionFailed(reason: String)Failed to create/manage subscription
Events emitted by discovery adapters.
Discovery adapters push events to subscribers when membership changes. Subscribers should process events quickly; heavy processing should be done asynchronously.
Variants
PeerUp: A new peer has been discovered and is availablePeerUpdate: A known peer’s metadata has changedPeerDown: A peer has left or become unavailable
pub type DiscoveryEvent {
PeerUp(peer: String, metadata: dict.Dict(String, String))
PeerUpdate(peer: String, metadata: dict.Dict(String, String))
PeerDown(peer: String, reason: String)
}
Constructors
-
PeerUp(peer: String, metadata: dict.Dict(String, String))A new peer has been discovered. Contains the peer ID and initial metadata.
-
PeerUpdate(peer: String, metadata: dict.Dict(String, String))A known peer’s metadata has been updated. Contains the peer ID and updated metadata.
-
PeerDown(peer: String, reason: String)A peer has left or become unavailable. Contains the peer ID and reason for departure.
Options for starting a discovery adapter.
pub type DiscoveryOptions {
DiscoveryOptions(
name: String,
sync_interval_ms: Int,
sync_timeout_ms: Int,
telemetry_prefix: String,
seed_peers: List(String),
custom: dict.Dict(String, dynamic.Dynamic),
)
}
Constructors
-
DiscoveryOptions( name: String, sync_interval_ms: Int, sync_timeout_ms: Int, telemetry_prefix: String, seed_peers: List(String), custom: dict.Dict(String, dynamic.Dynamic), )Arguments
- name
-
Registered name for the adapter process
- sync_interval_ms
-
How often to poll/sync with discovery backend (milliseconds)
- sync_timeout_ms
-
Timeout for sync operations (milliseconds)
- telemetry_prefix
-
Prefix for telemetry events
- seed_peers
-
Initial list of seed peers (for bootstrap)
- custom
-
Adapter-specific custom options
Callback type for discovery events.
pub type EventCallback =
fn(DiscoveryEvent) -> Nil
Extended health information with sync status.
pub type HealthInfo {
HealthInfo(
status: HealthStatus,
last_sync_time_ms: option.Option(Int),
last_error: option.Option(String),
known_peers_count: Int,
)
}
Constructors
-
HealthInfo( status: HealthStatus, last_sync_time_ms: option.Option(Int), last_error: option.Option(String), known_peers_count: Int, )
Health status of a discovery adapter.
Variants
Up: Adapter is fully operationalDegraded: Adapter is working but with issues (e.g., slow responses)Down: Adapter is not operational
pub type HealthStatus {
Up
Degraded(reason: String)
Down(reason: String)
}
Constructors
-
UpAdapter is fully operational
-
Degraded(reason: String)Adapter is operational but degraded
-
Down(reason: String)Adapter is down and cannot discover peers
Peer identifier used across the distribute library.
Matches the NodeId from registry/behaviour and handshake modules.
Typically formatted as “node@host” (Erlang node naming convention).
pub type PeerId =
String
Metadata associated with a discovered peer.
Contains information about the peer such as:
- Service version
- Capabilities
- Health status
- Custom attributes
All keys and values are strings for portability.
pub type PeerMetadata =
dict.Dict(String, String)
Subscription identifier returned by subscribe().
Use this with unsubscribe() to stop receiving events.
pub opaque type SubscriptionId
Values
pub fn default_options(name: String) -> DiscoveryOptions
Create default discovery options.
pub fn is_permanent_error(error: DiscoveryError) -> Bool
Check if an error is permanent and should not be retried.
pub fn is_transient_error(error: DiscoveryError) -> Bool
Check if an error is transient and should be retried.
pub fn new_subscription_id(id: String) -> SubscriptionId
Create a new subscription ID.
pub fn subscription_id_value(id: SubscriptionId) -> String
Extract the string value from a subscription ID.