Let's assume there is a simple integer calculator that only supports addition and multiplication operation. It will receive an integer generator and an integer as additive or multiplier as its input parameters and apply the corresponding calculation for each element that comes from the generator.
I think the following rough sequence diagram depicts this logic appropriately.
But when I use goroutines and channels to implement the same logic, the straight method/function calling relationship disappeared because goroutines utilize channels to send and receive data.
generator := func(integers ...int) <-chan int {
intStream := make(chan int)
go func() {
defer close(intStream)
for _, i := range integers {
intStream <- i
}
}()
return intStream
}
multiply := func(intStream <-chan int, multiplier int) <-chan int {
multipliedStream := make(chan int)
go func() {
defer close(multipliedStream)
for i := range intStream {
multipliedStream <- i*multiplier
}
}()
return multipliedStream
}
add := func(intStream <-chan int, additive int) <-chan int {
addedStream := make(chan int)
go func() {
defer close(addedStream)
for i := range intStream {
addedStream <- i additive
}
}()
return addedStream
}
intStream := generator(1, 2, 3, 4)
pipeline := multiply(add(intStream, 1), 2)
for v := range pipeline {
fmt.Println(v)
}
The goroutine born in the generator
acts as a producer to send integers; the goroutines born in the add
and multiply
are both producers and consumers; they receive integers, handle them, and put them into new channels. Finally, two channels connect these 3 goroutines as a pipeline, but I have no idea to present it to be clear at a glance.
Is there a kind of goroutines-oriented UML diagram?
CodePudding user response:
There is no one-size fits all in this domain. It all depends where you want to set the focus in your design:
- if you want to insist on the fact that a goroutine is lightweight thread, and on the channel (buffered or not) that is consumed, you may be interested in activity diagrams. Activity diagrams are also suitable to highlight the flow of values (i.e. object flows) in a functional design.
- if you want to show how objects (including functors) interact in a specific scenario, keep the sequence diagram, but complete it to show what happens: you need at least some messages between the consumers and the generator (this corresponds to the exchanges via the channel: arrows are not only function calls; they are messages that can correspond to a function call but also to other forms of communication). If the channel is very important, you may even consider to add a liefline for it: this would address most of your expressed concerns.
Not related: using UML diagrams to visually document low-level code, or do some kind of visual programming is perfectly valid, but tends to create very complex diagrams that are harder to read thant the code. This may not be the best purpose.