Dataflow and Flow-based programming library for Go.
GoFlow is a lean and opinionated implementation of Flow-based programming (FBP) that lets you design applications as graphs of components reacting to data as it flows through the graph.
Flow-based programming is a programming paradigm that defines applications as networks of black-box processes that exchange data across predefined connections by message passing, where the connections are specified externally to the processes.
- Concurrent — graph nodes run in parallel via goroutines and channels.
- Structural — applications are described as components, their ports, and the connections between them.
- Reactive — system behavior is defined by how components react to events or manage their lifecycle.
- Asynchronous by default — events have no predetermined order unless you enforce one.
- Isolated — communication replaces shared state; components don't share memory.
GoFlow requires Go 1.23 or later.
go get github.com/trustmaster/goflowThen import it in your code:
import "github.com/trustmaster/goflow"Below is a complete program that builds a simple two-component network: one greets names, the other prints the result.
package main
import (
"fmt"
"github.com/trustmaster/goflow"
)
// Greeter sends greetings.
type Greeter struct {
Name <-chan string // input port
Res chan<- string // output port
}
// Process reads incoming names and sends back greetings.
func (c *Greeter) Process() {
for name := range c.Name {
c.Res <- fmt.Sprintf("Hello, %s!", name)
}
}
// Printer prints its input on screen.
type Printer struct {
Line <-chan string // input port
}
// Process reads lines and prints them.
func (c *Printer) Process() {
for line := range c.Line {
fmt.Println(line)
}
}
// NewGreetingApp defines the application graph.
func NewGreetingApp() *goflow.Graph {
n := goflow.NewGraph()
n.Add("greeter", new(Greeter))
n.Add("printer", new(Printer))
n.Connect("greeter", "Res", "printer", "Line")
n.MapInPort("In", "greeter", "Name")
return n
}
func main() {
net := NewGreetingApp()
in := make(chan string)
net.SetInPort("In", in)
wait := goflow.Run(net)
in <- "John"
in <- "Boris"
in <- "Hanna"
close(in)
<-wait
}Why the
waitchannel? The flow-based world is asynchronous — events don't necessarily happen in the order they were sent. Thewaitchannel signals when the network has fully completed, preventing premature program termination.
| Term | Description |
|---|---|
| Component | The basic processing element. Its structure consists of input/output ports and state fields; its behavior is defined by event handlers. Analogous to a Class. |
| Connection | A link between two ports in the graph. In GoFlow this is a typed channel. |
| Graph | A higher-level entity composed of components and connections. Can represent composite components or entire applications. Analogous to a Class. |
| Network | A running instance of a Graph. Analogous to an Object. |
| Port | A property through which a Component or Graph communicates with the outside world (input/output). In GoFlow this is a channel field. |
| Process | A running instance of a Component. Analogous to an Object. |
More terms can be found in the Flowbased.org Terminology and the FBP wiki.
- Components — ports, events, process, and state.
- Graphs — structure definition and behavior.
go doc github.com/trustmaster/goflowOr view the online reference.
- Flow-based.org — specifications and recommendations for FBP systems.
- J. Paul Morrison's Flow-Based Programming — the origin of FBP, including JavaFBP, C#FBP, and the DrawFBP diagramming tool.
- NoFlo — FBP for JavaScript and Node.js.
- Integration with NoFlo-UI / Flowhub (in progress)
- Distributed networks via TCP/IP and UDP
- Reflection and monitoring of networks
Contributions are welcome! Please open an issue or pull request on GitHub.
Before submitting changes, make sure your code passes the linter and tests:
golangci-lint run ./...
go test -v -race ./...