Distributed actor framework for Go
Locks, channels, and a broker are three ways to coordinate state. An actor is one.
GoAkt is a distributed actor framework for Go. Typed messages, supervision, clustering, and Grains behind one API, from a single process to a cluster of nodes.
go get github.com/tochemey/goakt/v4
In production at Baki Money and Event Processor. Read a production report ↓
Your first actor
One struct, one Receive method, one message at a time.
This is the whole program from the quickstart. Nothing is elided.
package main
import (
"context"
"os"
"os/signal"
"syscall"
"time"
goakt "github.com/tochemey/goakt/v4/actor"
"github.com/tochemey/goakt/v4/log"
)
// Greet asks the actor to greet someone; Greeting is its reply.
type Greet struct{ Name string }
type Greeting struct{ Message string }
// Greeter is the actor. State lives in unexported fields; no locks are
// needed because an actor processes one message at a time.
type Greeter struct {
greeted int
}
var _ goakt.Actor = (*Greeter)(nil)
func (x *Greeter) PreStart(*goakt.Context) error { return nil }
func (x *Greeter) Receive(ctx *goakt.ReceiveContext) {
switch msg := ctx.Message().(type) {
case *Greet:
x.greeted++
ctx.Response(&Greeting{Message: "Hello, " + msg.Name + "!"})
default:
ctx.Unhandled()
}
}
func (x *Greeter) PostStop(*goakt.Context) error { return nil }
func main() {
ctx := context.Background()
logger := log.DefaultLogger
system, err := goakt.NewActorSystem("quickstart", goakt.WithLogger(logger))
if err != nil {
logger.Fatal(err)
}
if err := system.Start(ctx); err != nil {
logger.Fatal(err)
}
pid, err := system.Spawn(ctx, "greeter", &Greeter{})
if err != nil {
logger.Fatal(err)
}
response, err := goakt.Ask(ctx, pid, &Greet{Name: "World"}, time.Second)
if err != nil {
logger.Fatal(err)
}
logger.Info(response.(*Greeting).Message)
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-sig
_ = system.Stop(ctx)
}
Why actors
Four things you stop writing yourself.
Simpler concurrency
Actors process one message at a time. You write plain Go with no locks, channel plumbing, or shared-state bugs.
Location transparency
Send a message to a local, remote, or clustered actor with the same API. The framework handles the wire.
Resilience by design
Supervision trees and the let-it-crash model keep failures contained and recoverable.
Batteries included
Clustering, Grains, CRDTs, streams, scheduling, passivation, and observability, without re-rolling them yourself.
In production
31 pods, one monolith, and no broker in between.
One Go application, deliberately kept a monolith, running on Kubernetes at roughly 31 pods in steady state, with about 16 new pods joining the existing cluster simultaneously during a rolling update. GoAkt is the entire distribution layer: no MQTT or AMQP broker, no service mesh, no external actor registry. The only stateful things next to the app are a database and Redis.
Also running GoAkt
- Baki MoneyAI-powered expense tracking that turns receipts into stories.
- Event ProcessorClustered complex event processing for IoT data streams.
Running GoAkt in production? Write a short report to be listed here, or leave a note in the feedback thread.
What is in the box
One import path. Every piece links to its guide.
Discovery providers: Kubernetes, Consul, etcd, NATS, mDNS, DNS-SD, a static list, or self-managed LAN. Service discovery ↗
Where it does not fit
Three things GoAkt does not try to be.
-
Cluster nodes are Go processes.
Other languages reach a GoAkt system through the API you expose, not by joining the cluster.
-
Streams are in-memory.
They replace a broker for fan-out inside a process. They are not a log with retention or replay.
-
Actor state lives in memory.
Event sourcing, durable state, projections, and sagas are the job of eGo, built on GoAkt.
Numbers
Every figure reproduces from the repository.
Start
Paste the program above into main.go. Then three commands.
$ go mod init quickstart
$ go get github.com/tochemey/goakt/v4
$ go run .