GoFlow: Building Go Applications as Concurrent Flow Graphs
Flow-based and dataflow programming library for Go (golang)
At a glance
- What is it?
- GoFlow is an MIT-licensed Go library for defining applications as connected components that exchange data through channels. It suits developers who need explicit dataflow structure and asynchronous process coordination, but its event ordering and lifecycle requirements add complexity.
- Who is it for?
- Adopt GoFlow when an application maps naturally to independent components connected by typed channel ports, especially when explicit graph structure and asynchronous coordination are useful. Do not adopt it when deterministic event ordering, shared-memory simplicity, or a mature distributed runtime is more important than the FBP model.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 141 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
GoFlow's Channel-Based Dataflow Model
GoFlow addresses applications that can be decomposed into processing components connected by data paths. Its intended users are Go developers who want the structure of Flow-based programming without leaving the Go ecosystem. A component exposes input and output ports as channel fields, while a graph records how those ports connect. This differs from an ordinary function call chain because the graph definition is external to the component behavior.
The model is deliberately asynchronous. The README states that events have no predetermined order unless the application enforces one, and graph nodes run in parallel through goroutines and channels. Components therefore communicate by passing values, not by sharing mutable memory. That isolation can make ownership and data movement clearer, although it also requires careful channel closing, buffering, and completion handling.
GoFlow also distinguishes a graph, which describes structure, from a network, which is a running instance. Components have ports and behavior through methods such as `Process`. A graph can represent either an individual application or a reusable composite component. This vocabulary gives the library a specific role in service and data-processing code, but it is not a general workflow engine with persistence, retries, or scheduling documented by the repository.
Defining Greeting Components and Connecting Ports
The README's first example defines a `Greeter` component with an input channel named `Name` and an output channel named `Res`. Its `Process` method ranges over incoming strings and formats each value as a greeting. A `Printer` component consumes the resulting strings from its `Line` port. The connection between `greeter.Res` and `printer.Line` is created in graph construction rather than by either component knowing the other's concrete identity.
The graph adds both components, connects their ports, and maps the `Greeter.Name` port to an external graph port named `In`. At runtime, the program creates the input channel, starts the graph with `goflow.Run`, sends three names, closes the input channel, and waits for the returned channel. The `wait` channel is part of the example's completion design. Without waiting for it, `main` could exit before all component goroutines have processed their events.
This structure is useful when the same component can be connected to different peers in different graphs. It also makes the data path inspectable at the graph level. The cost is that port direction and channel ownership must be explicit. A component can compile while its surrounding graph is incomplete or incorrectly connected, so graph design remains part of the correctness problem.
Installing GoFlow and Running the First Graph
GoFlow requires Go 1.23 or later. Add the module to a Go project with the command documented in the README. The command downloads the package as a Go module dependency; it does not start a service or install a separate daemon.
go get github.com/trustmaster/goflowA minimal program needs the module import and the component definitions. The following block contains the package declaration, imports, and the two component types exactly as shown in the README. Add it as `main.go` in a Go module.
package main
import (
"fmt"
"github.com/trustmaster/goflow"
)
type Greeter struct {
Name <-chan string
Res chan<- string
}
type Printer struct {
Line <-chan string
}The component methods consume their ports until the channels are closed. The formatted greeting is sent on `Greeter.Res`, and the printer emits each received line to standard output.
func (c *Greeter) Process() {
for name := range c.Name {
c.Res <- fmt.Sprintf("Hello, %s!", name)
}
}
func (c *Printer) Process() {
for line := range c.Line {
fmt.Println(line)
}
}The graph definition names the components, connects `Res` to `Line`, and exposes `In` as the external entry port. `MapInPort` links the external graph port to `greeter.Name`.
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
}Finally, `main` sets the input channel, starts the network, sends the documented sample values, closes the channel, and waits for completion. The expected output shown by the example is three greeting lines, although the README does not specify a test harness for this application.
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
}The Makefile provides a project-level validation target. It runs the configured linter and then the Go test suite with the race detector.
make testLoading Graphs from the .fbp DSL
For applications that prefer a textual graph definition, GoFlow includes a `dsl` package. The README says it supports process declarations, connections, array ports, initial information packets, and port exports. A graph file uses standard Flow-based Programming syntax, while a Go factory supplies the implementation associated with each declared process. The separation moves topology out of Go code, but the application still needs factory logic and the same runtime and channel-lifecycle understanding.
The documented load operation takes a file path and a factory:
g, err := dsl.LoadFile("graph.fbp", factory)The repository provides a dedicated `dsl/README.md` for the complete syntax and API reference. The top-level README does not document the factory interface, the exact process registration procedure, or error recovery for a malformed `.fbp` file, so those details should be checked in the package documentation before adopting the DSL for a production graph. The DSL is a reasonable choice when graph topology changes independently of component code. It is less attractive when the graph is small, because the extra parsing and factory layer can outweigh the benefit of visual or textual separation.
Asynchrony, Ordering, and Completion Boundaries
The main limitation is visible in the library's central execution model. The README explicitly says that events do not necessarily happen in the order they were sent. The sample sends names in a known order, but that does not promise that greetings will be processed in the same order or that unrelated component events will be serialized. Code that depends on a fixed sequence must enforce that dependency itself.
The `wait` channel addresses completion, not ordering. It tells the caller when the network has fully completed according to the example, preventing premature program termination. It does not make concurrent components synchronous. If a component expects to see all inputs before producing an output, the graph and component must express that rule through its own protocol and synchronization.
Channel ownership is another failure boundary. The README example closes the external input channel after sending values, and the components use `range`, which terminates when their input channels close. Other channel closures and error paths are not specified in the documentation. A production design should therefore establish explicit ownership for every channel and test shutdown, cancellation, and partial failure. The repository's `runtime.go`, `protocol.go`, and related tests are the appropriate places to inspect the current contract before relying on undocumented behavior.
GoFlow Compared With NoFlo and Ordinary Go Concurrency
The README identifies NoFlo as a related Flow-based programming project for JavaScript and Node.js. GoFlow's difference is its native fit for Go: components are Go structs, ports are Go channel fields, and graph definitions can be assembled directly through the `github.com/trustmaster/goflow` API. NoFlo is associated with the JavaScript and Node.js ecosystem, so the choice is not merely a comparison of FBP syntax.
For ordinary Go concurrency, GoFlow removes the need to invent a graph abstraction when a workflow has several independent producers and consumers. Standard Go can implement the same channel pipeline directly, but the application then owns graph construction, process startup, and shutdown itself. GoFlow supplies those concepts, including `Graph`, `Component`, `Network`, `Process`, and port mapping. In exchange, programmers must adopt FBP terminology and reason about asynchronous event flow rather than simply calling functions and using `sync` primitives.
GoFlow is therefore not the right tool for a small straight-line request handler where direct Go calls are clearer. It is more relevant when topology is reusable, components need independent lifecycles, or the application benefits from a graph that can later be represented through the `.fbp` DSL. The repository's roadmap mentions TCP/IP and UDP distribution and reflection and monitoring, but those are roadmap items, not documented features available in the current release.
License, Releases, and Maintenance Cost
GoFlow uses the MIT license. That permits broad reuse and modification under the license terms, but organizations still need to review the actual `LICENSE` file and comply with its notice and disclaimer requirements. This paragraph is a practical license note, not legal advice.
The repository is not archived. Its last push was on 2026-06-05, and the listed releases are `1.1.0` on 2026-06-05 and `1.0.0` on 2026-05-25. The module declares Go 1.23 in `go.mod`, which creates a concrete upgrade constraint: applications must use a compatible Go toolchain or account for the project's minimum version. The Makefile's `make test` target runs `golangci-lint run ./...` and `go test -v -race ./...`, so maintaining a fork or submitting changes requires access to that toolchain and the configured linter.
Upgrade cost is likely to center on API compatibility, component lifecycle behavior, and graph definitions rather than a separate service deployment. The repository does not document a compatibility policy between releases, a migration guide, or a rollback procedure. Review release changes and current package documentation before changing a production graph.
Editorial conclusion
Adopt GoFlow when an application maps naturally to independent components connected by typed channel ports, especially when explicit graph structure and asynchronous coordination are useful. Do not adopt it when deterministic event ordering, shared-memory simplicity, or a mature distributed runtime is more important than the FBP model. Verify first that your components can tolerate the library's asynchronous delivery semantics, that every output and channel lifecycle is handled correctly, and that the stated Go 1.23 requirement matches your build environment. The last push was on 2026-06-05, so dependency and API checks should start with the current release.
Frequently asked questions
How do I install GoFlow?
The README requires Go 1.23 or later and documents `go get github.com/trustmaster/goflow` as the installation command. Import `github.com/trustmaster/goflow` in the Go code that builds the graph.
Does GoFlow guarantee the order of events?
No. The README says GoFlow is asynchronous by default and that events have no predetermined order unless the application enforces one. Applications that require sequencing must express that requirement in their component or graph design.
Can GoFlow load a graph from a .fbp file?
Yes. The built-in `dsl` package provides `dsl.LoadFile("graph.fbp", factory)`. The README also states that it supports process declarations, connections, array ports, IIPs, and port exports, while detailed syntax is in `dsl/README.md`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/trustmaster-goflow)Was this useful?