go-actor
is tiny library for writing concurrent programs in Go using actor model.
Intention of go-actor is to bring actor model closer to Go developers and to provide design pattern needed to build scalable and high performing concurrent programs.
Without re-usable design principles codebase of complex system can become hard to maintain. Codebase written using Golang can highly benefit from design principles based on actor model as goroutines and channels naturally translate to actors and mailboxes.
go-actor
's base abstraction layer only has three interfaces:
actor.Actor
is anything that implements Start()
and Stop()
methods. Actors created using actor.New(actor.Worker)
function will create preferred actor implementation which will on start spawn dedicated goroutine to perform work of supplied actor.Worker
.actor.Worker
encapsulates actor's executable logic. This is the only interface which developers need to write in order to describe behavior of actors.actor.Mailbox
is an interface for message transport mechanisms between actors. Mailboxes are created using the actor.NewMailbox(...)
function.Dive into examples to see go-actor
in action.
// This example will demonstrate how to create actors for producer-consumer use case.
// Producer will create incremented number on every 1 second interval and
// consumer will print whatever number it receives.
func main() {
mailbox := actor.NewMailbox[int]()
// Produce and consume workers are created with same mailbox
// so that produce worker can send messages directly to consume worker
pw := &produceWorker{mailbox: mailbox}
cw1 := &consumeWorker{mailbox: mailbox, id: 1}
// Note: Example creates two consumers for the sake of demonstration
// since having one or more consumers will produce the same result.
// Message on stdout will be written by first consumer that reads from mailbox.
cw2 := &consumeWorker{inC: mailbox.ReceiveC(), id: 2}
// Create actors using these workers and combine them to singe actor
a := actor.Combine(
mailbox,
actor.New(pw),
actor.New(cw1),
actor.New(cw2),
)
// Finally all actors are started and stopped at once
a.Start()
defer a.Stop()
select {}
}
// produceWorker will produce incremented number on 1 second interval
type produceWorker struct {
mailbox actor.MailboxSender[int]
num int
}
func (w *produceWorker) DoWork(c actor.Context) actor.WorkerStatus {
select {
case <-time.After(time.Second):
w.num++
w.mailbox.Send(c, w.num)
return actor.WorkerContinue
case <-c.Done():
return actor.WorkerEnd
}
}
// consumeWorker will consume numbers received on inC channel
type consumeWorker struct {
mailbox actor.MailboxReceiver[int]
id int
}
func (w *consumeWorker) DoWork(c actor.Context) actor.WorkerStatus {
select {
case num := <-w.mailbox.ReceiveC():
fmt.Printf("consumed %d \t(worker %d)\n", num, w.id)
return actor.WorkerContinue
case <-c.Done():
return actor.WorkerEnd
}
}
go-actor
is intended to be a tiny library with lean interfaces and basic mechanism providing core building blocks. However, developers may build on top of it and extend it's functionality with user or domain specific addon abstractions. This section lists addon abstractions for go-actor
which could be used in addition to it.
Design decisions are documented here.
All contributions are useful, whether it is a simple typo, a more complex change, or just pointing out an issue. We welcome any contribution so feel free to open PR or issue.
Continue reading here.
Author: vladopajic
Source Code: https://github.com/vladopajic/go-actor
License: GPL-3.0 license