How Are Transports Registered in OpenFlux: Complete Guide to the Factory Pattern
OpenFlux uses a central registry pattern where each transport package self-registers via an init() function that maps a string name to a factory function in the global transportFactories map defined in transport/transport.go.
The OpenFlux messaging framework implements a pluggable transport architecture that allows new protocols to be added without modifying core application code. Understanding how transports register themselves is essential for extending the library or troubleshooting transport loading issues. This pattern leverages Go's package initialization system to build a declarative, self-organizing registry.
The Central Transport Registry
At the heart of OpenFlux transport management lies a package-level map in transport/transport.go that associates string identifiers with constructor functions. This registry enables runtime polymorphism, allowing the application to instantiate concrete transport implementations by name.
The registry structure consists of:
transportFactories: A privatemap[string]func(cfg TransportConfig) Transportthat stores factory functionsRegisterTransport: A public helper that adds entries to the mapNewTransport: A lookup function that instantiates transports by name
// transport/transport.go
var transportFactories = map[string]func(cfg TransportConfig) Transport{}
// RegisterTransport adds a new transport type to the registry.
func RegisterTransport(name string, factory func(cfg TransportConfig) Transport) {
transportFactories[name] = factory
}
// NewTransport looks up the name in the registry and creates the transport.
func NewTransport(name string, cfg TransportConfig) (Transport, error) {
if f, ok := transportFactories[name]; ok {
return f(cfg), nil
}
return nil, fmt.Errorf("transport %q not registered", name)
}
The Transport interface itself is defined in this same file, establishing the contract that all registered implementations must satisfy.
Self-Registration via Package init()
Rather than requiring a central manifest file, OpenFlux employs Go's init() function mechanism to allow each transport package to self-register during program initialization. This decouples transport implementations from the core registry logic.
The Registration Mechanism
When a transport package is imported (even with the blank identifier _), its init() function executes automatically, calling RegisterTransport with the transport's canonical name and a closure that returns a concrete implementation.
According to the source code in p1neappleXpress/OpenFlux, this pattern appears consistently across all transport implementations:
Yandex Transport (transport/yandex/yandex.go):
func init() {
RegisterTransport("yandex", func(cfg TransportConfig) Transport {
return NewYandexTransport(cfg)
})
}
Oneme Transport (transport/oneme/max_transport.go):
func init() {
RegisterTransport("oneme", func(cfg TransportConfig) Transport {
return NewMaxTransport(cfg)
})
}
Encrypted Transport (transport/encrypted.go):
func init() {
RegisterTransport("encrypted", func(cfg TransportConfig) Transport {
return NewEncryptedTransport(cfg)
})
}
This pattern also extends to other implementations like transport/cupsonline/cupsonline.go, ensuring architectural consistency across the codebase.
Instantiating Transports with NewTransport
Once registration is complete during program startup, the application retrieves transport instances through the NewTransport function. This function performs a map lookup and executes the stored factory function with the provided configuration.
cfg := transport.DefaultConfig()
tr, err := transport.NewTransport("yandex", cfg)
if err != nil {
// Handle unregistered transport error
log.Fatal(err)
}
// tr now satisfies the Transport interface and is ready for use
The factory pattern allows the configuration object (TransportConfig) to be passed at instantiation time while keeping the registration logic free of configuration specifics.
Complete Working Example
The following example demonstrates importing transports for their side effects (registration), creating instances by name, and registering custom transports at runtime:
package main
import (
"log"
// Import for side effects to trigger init() registration
_ "github.com/p1neappleXpress/OpenFlux/transport/yandex"
_ "github.com/p1neappleXpress/OpenFlux/transport/oneme"
"github.com/p1neappleXpress/OpenFlux/transport"
)
type MyCustomTransport struct {
config transport.TransportConfig
}
func (m *MyCustomTransport) Start() error { return nil }
func (m *MyCustomTransport) Send(data []byte) error { return nil }
func (m *MyCustomTransport) Stop() error { return nil }
func main() {
cfg := transport.DefaultConfig()
// Create registered transports by name
yandexTr, err := transport.NewTransport("yandex", cfg)
if err != nil {
log.Fatalf("failed to create yandex transport: %v", err)
}
// Register a custom transport at runtime
transport.RegisterTransport("mycustom", func(c transport.TransportConfig) transport.Transport {
return &MyCustomTransport{config: c}
})
// Instantiate the custom transport
customTr, err := transport.NewTransport("mycustom", cfg)
if err != nil {
log.Fatalf("failed to create custom transport: %v", err)
}
// Use the transport
if err := yandexTr.Start(); err != nil {
log.Fatalf("failed to start: %v", err)
}
defer yandexTr.Stop()
if err := yandexTr.Send([]byte("hello world")); err != nil {
log.Printf("send error: %v", err)
}
}
Summary
- OpenFlux maintains a private
transportFactoriesmap intransport/transport.gothat stores factory functions keyed by string names. - The
RegisterTransportfunction populates this map, whileNewTransportretrieves and executes factory functions to create concrete instances. - Each transport package (such as
transport/yandex,transport/oneme, andtransport/encrypted) contains aninit()function that self-registers by callingRegisterTransportwith a closure constructor. - This pattern eliminates hard-coded transport lists and allows third-party transports to integrate by simply importing the package and calling the registration API.
- Runtime registration is also supported, enabling dynamic plugin architectures.
Frequently Asked Questions
Where is the transport registry defined in OpenFlux?
The transport registry is defined in transport/transport.go as a package-level variable transportFactories of type map[string]func(cfg TransportConfig) Transport. This file also contains the RegisterTransport and NewTransport functions that manage the registry operations.
Can I register a custom transport at runtime?
Yes. You can call transport.RegisterTransport(name, factory) at any point after import. Pass a unique string identifier and a factory function that accepts TransportConfig and returns a type implementing the Transport interface. The transport becomes immediately available via transport.NewTransport(name, cfg).
What happens if I request an unregistered transport name?
The NewTransport function returns an error formatted as transport "name" not registered when the requested string key does not exist in the transportFactories map. This error handling prevents nil pointer panics and provides clear diagnostic information about missing imports or typos in transport names.
Do all transport implementations follow the same registration pattern?
Yes. According to the source analysis of p1neappleXpress/OpenFlux, all built-in transports including Yandex, Oneme, and Encrypted follow the identical pattern: an init() function that calls RegisterTransport with a constructor closure. This consistency ensures predictable behavior across the transport subsystem.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →