Skip to content
Vladimir Sibirov edited this page Oct 16, 2020 · 5 revisions

Graphs (subgraphs, nets) in flow-based programming represent entities of more complex nature than components. FBP networks may have multi-level hierarchical structure. A graph consists of components, other graphs and connections between them. A graph which is a part of another higher level graph is called a Subgraph or a Composite Component. Top-level graphs represent entire programs or applications.

Flow-based programming proposes visual representation of graphs over textual. Each composite component has its own diagram but on a parent (higher level) diagram it is displayed like an ordinary component. An example diagram made with DrawFBP is shown on the following figure:

MVC graph diagram example

Structure definition

In GoFlow graph structure is defined at run-time using methods of *goflow.Graph structure. You don't need to create any specific types for your graphs.

Constructor

Network processes, connections, and initial data (IIPs) are defined in the constructor function:

func NewSimpleNet() *SimpleGraph {
	n := goflow.NewGraph()
	// Graph nodes are created here
	// ...
	// Connections are made here
	// ...
	// Network ports are declared here
	// ...
	return n
}

Adding processes

New processes are added to network's graph using Add() method:

n.Add(new(ComponentType), "processName")

The first argument of the Add() method is the actual process object, the second argument is a string name which will be used to reference the process within the network.

After a process has been added to a network, it starts to "belong" to it. A process notifies its parent network when it starts or terminates. It can also reference its parent network and even modify its structure at run time.

Connections

Once processes have been added to a graph, they should be connected to each other using Connect() method:

n.Connect("senderProc", "senderPort", "receiverProc", "receiverPort")

The first 2 arguments are the name of the sender process in the network and the name of its outport that sends data. Next 2 arguments are the name of the receiver process in the network and the name of its inport which receives data.

By default a connection created in the above way is unbuffered. Such connections are blocking and cannot queue up the packets to be processed. You can specify a custom buffer size for a connection:

n.ConnectBuf("senderProc", "senderPort", "receiverProc", "receiverPort", bufferSize)

Where bufferSize is an integer. For unbuffered channels bufferSize == 0. The default buffer size used by Connect() method can be changed using GraphConfig.BufferSize variable passed to NewGraph function.

It is required that sender outport, receiver inport and the connection channel have the same underlying element type. Channel directions may be different (sender port can be send-only or bidirectional, receiver port can be receive-only or bidirectional, connection channel must be bidirectional), but the elements must be of the same type.

Ports

Network ports are different from component ports in GoFlow. While component ports are physical struct fields of channel type, net ports are virtual mappings. Each net port is mapped directly to a port of a contained process. Networks don't react to events on ports, they only route data between components and subgraphs.

To define an input or output port of a network, you need to map it to an appropriate input or outport of a contained process:

n.MapInPort("NetInportName", "processName", "processInport")
n.MapOutPort("NetOutportName", "processName", "processOutport")

A network knows how to connect its components and subgraphs using both physical and virtual ports. Normally you don't need to care about the difference. But if you want to use network's ports in top-level application, you can assign channels to them and use these channels to communicate with the network:

func main() {
	// create the net
	net := NewSimpleNet()
	// make channels
	in := make(chan InDataType)
	out := make(chan OutDataType)
	// connect to the net
	net.SetInPort("NetInportName", in)
	net.SetOutPort("NetOutportName", out)
	// run the network
	wait := goflow.Run(net)
	// now you can use channels to communicate
	in <- someValue
	close(in)
	result := <-out
	<-wait
}

SetInPort() and SetOutPort() methods are used to assign real channels to network's ports.

Nesting and subgraphs

As mentioned at the beginning of this article, networks can be nested within each other forming a hierarchical structure. Subgraphs are added to networks and connected just like other processes:

n.Add(NewSubgraphType(), "subnetProcessName")
n.Connect("proc1", "Out", "subnetProcessName", "In")

This feature allows you to build reusable composite components and design complex software in an easy way.

Behavior

Top-level networks are started with goflow.Run() call:

wait := flow.RunNet(net)

This call is non-blocking, so the caller routine continues to execute as soon as the network is initialized. Like components, networks must be passed by pointer.

The returned value is a channel that is closed when the network shuts down.

Clone this wiki locally