Generic Types

Data Types and Interfaces Used in Ergo Framework

Ergo Framework uses several specialized types for identifying and addressing processes, nodes, and other entities in the system. Understanding these types is essential for working with the framework.

Identifiers and Names

gen.Atom

gen.Atom is a specialized string used for names - node names, process names, event names. While technically just a string, treating it as a distinct type allows the framework to optimize how these names are handled in the network stack.

Atoms appear in single quotes when printed:

fmt.Printf("%s", gen.Atom("myprocess"))
// Output: 'myprocess'

The network stack caches atoms and maps them to numeric IDs to reduce bandwidth when the same names appear repeatedly in messages.

gen.PID

A gen.PID uniquely identifies a process. It contains the node name where the process lives, a unique sequential ID, and a creation timestamp. The creation timestamp changes when a node restarts, allowing you to detect if you're talking to a reincarnation of a node rather than the original.

gen.PID values print with the node name hashed for brevity:

pid := gen.PID{Node: "node@localhost", ID: 1001, Creation: 1685523227}
fmt.Printf("%s", pid)
// Output: <90A29F11.0.1001>

The hash (90A29F11) is a CRC32 of the node name. This keeps the printed form compact while remaining unique.

gen.ProcessID

A gen.ProcessID identifies a process by its registered name rather than gen.PID. This is useful when you need to address a process but don't know its gen.PID, or when the gen.PID might change across restarts but the name remains constant.

processID := gen.ProcessID{Name: "worker", Node: "node@localhost"}
fmt.Printf("%s", processID)
// Output: <90A29F11.'worker'>

gen.Ref

gen.Ref values are unique identifiers generated by nodes. They're used for correlating requests and responses in synchronous calls, and as tokens when registering events.

A gen.Ref is guaranteed unique within a node for its lifetime. The structure includes the node name, creation time, and a unique ID array.

ref := node.MakeRef()
fmt.Printf("%s", ref)
// Output: Ref#<90A29F11.128194.23952.0>

References can also embed deadlines (stored in ID[2]) for timeout tracking. Recipients can check ref.IsAlive() to see if a request is still valid.

gen.Alias

gen.Alias is like a temporary gen.PID. Processes create aliases for additional addressability without registering names. Meta processes use aliases as their primary identifier.

Aliases use the same structure as references but print with a different prefix:

alias := process.CreateAlias()
fmt.Printf("%s", alias)
// Output: Alias#<90A29F11.128194.23952.0>

gen.Event

gen.Event values represent named message streams that processes can subscribe to. A gen.Event identifier consists of a name and the node where it's registered.

event := gen.Event{Name: "user_login", Node: "node@localhost"}
fmt.Printf("%s", event)
// Output: Event#<90A29F11:'user_login'>

gen.Env

Environment variable names in Ergo are case-insensitive. The gen.Env type ensures this by converting to uppercase.

env := gen.Env("database_url")
fmt.Printf("%s", env)
// Output: DATABASE_URL

This allows processes to inherit environment variables from parents, leaders, and the node, with consistent naming regardless of how they're specified.

Core Interfaces

The framework defines several interfaces that provide access to different parts of the system.

gen.Node

The gen.Node interface is what you get when you start a node. It provides methods for spawning processes, managing applications, configuring networking, and controlling the node lifecycle.

node, err := ergo.StartNode("mynode@localhost", gen.NodeOptions{})
// node implements gen.Node interface

Node operations can be called from any goroutine. The node manages processes but isn't itself an actor.

gen.Process

The gen.Process interface represents a running actor. It provides methods for sending messages, spawning children, linking to other processes, and managing the actor's lifecycle.

Actors typically embed this interface:

type MyActor struct {
    act.Actor  // Embeds gen.Process
}

func (a *MyActor) HandleMessage(from gen.PID, message any) error {
    // Process methods are available directly
    a.Send(from, "reply")
    return nil
}

Process methods enforce state-based access control. Some operations are only available when the process is in certain states, ensuring actor model constraints are maintained.

gen.Network

The gen.Network interface manages distributed communication. It handles connections to remote nodes, routing, and service discovery.

network := node.Network()
remoteNode, err := network.GetNode("remote@otherhost")

Network transparency means sending messages to remote processes uses the same API as local processes. The gen.Network interface is where you configure how that transparency is achieved.

gen.RemoteNode

A gen.RemoteNode represents a connection to another Ergo node. Through this interface, you can spawn processes on the remote node or start applications there.

remoteNode, err := network.GetNode("node@remotehost")
pid, err := remoteNode.Spawn("worker", gen.ProcessOptions{}, args)

The remote operations require the target node to have enabled the corresponding permissions.

Type Design Philosophy

These types reflect a few design decisions worth understanding.

Hashing for readability - Node names are hashed in output to keep logs and traces readable while maintaining uniqueness. Full names can be verbose, especially in distributed systems with descriptive naming.

Separate types for concepts - gen.PID, gen.ProcessID, gen.Alias, and gen.Event are distinct types even though they could have been unified. Each represents a different way of addressing or identifying something in the system, and the type system helps keep these concepts clear.

Network-aware design - Many types include the node name. This isn't just for completeness - it's what enables network transparency. A gen.PID tells you not just which process, but which node, allowing the framework to route messages appropriately.

For detailed API documentation of these interfaces and types, refer to the godoc comments in the source code.

Last updated