The simple & low-maintenance Go distributed actor framework
Francis is a framework and runtime for Distributed Actors (also known as Durable Objects) for Go apps, and a durable workflow engine built on them.
With Francis, you can build highly-available apps that scale horizontally and/or use microservices. Unlike other actor frameworks, Francis is designed to be simpler to add to your solution and lower-maintenance: it only requires a relational database (PostgreSQL or SQLite) and can optionally run embedded in your apps too, without a separate control plane service.
What you can use Francis for:
- Build stateful services where each entity (e.g. a user, a device, a shopping cart, a game session) is an actor with its own durable state
- Run multi-step workflows that survive restarts and undo the work that already succeeded when a later step fails
- Scale horizontally across many hosts without sharding your data by hand: Francis places each actor on exactly one host and routes calls to it
- Run background work on a schedule with durable alarms that survive restarts
- Add resilience to microservices without standing up extra infrastructure beyond a database
// An actor is just a Go struct that implements one or more methods
func (c *Counter) Invoke(ctx context.Context, method string, data actor.Envelope) (any, error) {
state, _ := c.client.GetState(ctx)
state.Count++
_ = c.client.SetState(ctx, state, nil)
return state.Count, nil
}A workflow is a declared graph of steps, each one a plain Go function:
checkout, _ := workflow.New("checkout",
workflow.WithSteps(
workflow.Step("reserve-inventory",
workflow.WithRun(reserveInventory),
workflow.WithCompensate(releaseInventory),
),
workflow.Step("charge",
workflow.WithRun(chargeCard),
workflow.WithCompensate(refundCharge),
),
workflow.Step("create-shipment", workflow.WithRun(createShipment)),
),
)If create-shipment fails, Francis runs refundCharge and then releaseInventory, and the instance terminates as failed.
Francis is fully open source and released under a permissive MIT license.
If you’re new to the distributed actors pattern, this article provides a good starting point.
How it works#
- You write an actor as a Go struct and register it with an actor host under a type name.
- Your app invokes an actor by its type and ID (e.g.
cart/user-42). Francis activates the actor on exactly one host in the cluster and routes the call to it.
Actors implement turn-based concurrency to ensure that they are serving a single request at a time. - The actor reads and writes its own durable state, stored in the database.
While the actor is active, its state is cached in-memory for fast access.
State outlives the actor: when an actor is hibernated and later re-activated (possibly on another host), its state is still there. - Actors can schedule alarms to run work at a future time, optionally on a repeating interval.
Alarms are durable and survive restarts. - After a configurable idle period, an actor is hibernated automatically to free resources. The next call re-activates it.
Key features#
- Virtual actors: actors are addressed by type and ID, activated on demand, and run one invocation at a time, so you never manage their lifecycle or worry about concurrent access to their state.
- Durable state: each actor has its own state persisted in PostgreSQL or SQLite: it survives deactivation, restarts, and moving between hosts.
- Durable alarms: schedule one-off or repeating work that survives process restarts.
- Durable workflows : declare a multi-step process as a graph, with parallel steps, fan-out, child workflows, and a per-step undo that runs in reverse when the process fails.
- Two topologies, same code: for small clusters, run everything embedded in your app (local) with no extra services. Alternatively, point your workers at a standalone runtime (remote) when you want a dedicated control plane to support a larger number of hosts. Your actor code is identical.
- Low-maintenance: the only hard dependency is a relational database: no separate message broker, no external coordination service.
- Secure by default: hosts authenticate each other with mTLS using certificates derived from a shared cluster key, with pluggable host bootstrap (pre-shared key or JWT)
- Built for Go: a small, idiomatic API familiar to Go developers.
Where to go next#
- New to actors? Start with What is Francis and the core concepts .
- Want to run something now? Follow the Quickstart .
- Ready to write code? See Writing actors .
- Have a process with several steps that must all happen? See Workflows .