Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7935dfb
accept a closure in RunAsStep
maxdml Aug 8, 2025
e156816
fix
maxdml Aug 8, 2025
88cf424
fix
maxdml Aug 8, 2025
fb5b0b6
fix
maxdml Aug 8, 2025
dfcff91
fix
maxdml Aug 9, 2025
a62ee45
check special steps
maxdml Aug 9, 2025
555c367
remove t.Fatal calls in wf/steps
maxdml Aug 9, 2025
6d38d3f
fix race in step name map
maxdml Aug 9, 2025
3ebbf9f
add test for running workflows in goroutines
maxdml Aug 9, 2025
63e2c78
more concurrent tests
maxdml Aug 9, 2025
f81fff4
set version
maxdml Aug 9, 2025
cacb25c
allow custom workflow names
maxdml Aug 11, 2025
83b8148
test conflicting execution of workflows / tasks of the same ID
maxdml Aug 11, 2025
071eac6
nit
maxdml Aug 11, 2025
1071108
test that running a recorded child workflow, from a parent, returns a…
maxdml Aug 11, 2025
3069590
Merge branch 'main' into more-tests
maxdml Aug 11, 2025
a9a83a1
nits
maxdml Aug 11, 2025
e9d5c4f
fix test + nits
maxdml Aug 11, 2025
04390cf
use a sync.Map
maxdml Aug 11, 2025
2960cce
for now return a flat error if we try to enqueue a workflow in a diff…
maxdml Aug 11, 2025
b4e2327
make system DB private
maxdml Aug 11, 2025
952d619
getBinaryHash becomes private
maxdml Aug 11, 2025
30c41ef
update README
maxdml Aug 11, 2025
8eea741
typo
maxdml Aug 11, 2025
bc34f74
dbos.go docs
maxdml Aug 11, 2025
9d91729
Merge remote-tracking branch 'origin/main' into readme-and-docs
maxdml Aug 11, 2025
f8cf8c7
errors.go docs
maxdml Aug 11, 2025
7548d41
queue.go docs
maxdml Aug 11, 2025
89baa7c
nit
maxdml Aug 11, 2025
25dcf5c
workflow.go docs
maxdml Aug 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 182 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
<div align="center">

# DBOS Transact: Lightweight Durable Workflows
[![Go Reference](https://pkg.go.dev/badge/github.com/dbos-inc/dbos-transact-go.svg)](https://pkg.go.dev/github.com/dbos-inc/dbos-transact-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/dbos-inc/dbos-transact-go)](https://goreportcard.com/report/github.com/dbos-inc/dbos-transact-go)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/dbos-inc/dbos-transact-go?sort=semver)](https://github.com/dbos-inc/dbos-transact-go/releases)


# DBOS Transact: Lightweight Durable Workflow Orchestration with Postgres

#### [Documentation](https://docs.dbos.dev/) &nbsp;&nbsp;•&nbsp;&nbsp; [Examples](https://docs.dbos.dev/examples) &nbsp;&nbsp;•&nbsp;&nbsp; [Github](https://github.com/dbos-inc) &nbsp;&nbsp;•&nbsp;&nbsp; [Discord](https://discord.com/invite/jsmC6pXGgX)
</div>

---
#### This Golang version of DBOS Transact is in Alpha!
For production ready Transacts, check our [Python](https://github.com/dbos-inc/dbos-transact-py) and [TypeScript](https://github.com/dbos-inc/dbos-transact-ts) versions.

This package is in alpha -- the roadmap of missing features is listed [below](#roadmap).
---

## What is DBOS?

DBOS provides lightweight durable workflows on top of Postgres. Instead of managing your own workflow orchestrator or task queue system, you can use DBOS to add durable workflows and queues to your program in just a few lines of code.
DBOS provides lightweight durable workflow orchestration on top of Postgres. Instead of managing your own workflow orchestrator or task queue system, you can use DBOS to add durable workflows and queues to your program in just a few lines of code.


## When Should I Use DBOS?
Expand All @@ -33,36 +39,67 @@ If your program ever fails, when it restarts all your workflows will automatical
You add durable workflows to your existing Golang program by registering ordinary functions as workflows or running them as steps:

```golang
var (
wf = dbos.WithWorkflow(workflow)
package main

import (
"context"
"fmt"
"os"

"github.com/dbos-inc/dbos-transact-go/dbos"
)

func workflow(ctx context.Context, _ string) (string, error) {
_, err := dbos.RunAsStep(ctx, step1, "")
if err != nil {
return "", err
}
return dbos.RunAsStep(ctx, step2, "")
func workflow(dbosCtx dbos.DBOSContext, _ string) (string, error) {
_, err := dbos.RunAsStep(dbosCtx, func(ctx context.Context) (string, error) {
return stepOne(ctx)
})
if err != nil {
return "", err
}
return dbos.RunAsStep(dbosCtx, func(ctx context.Context) (string, error) {
return stepTwo(ctx)
})
}

func step1(ctx context.Context, _ string) (string, error) {
fmt.Println("Executing step 1")
return "Step 1 completed", nil
func stepOne(ctx context.Context) (string, error) {
fmt.Println("Step one completed!")
return "Step 1 completed", nil
}

func step2(ctx context.Context, _ string) (string, error) {
fmt.Println("Executing step 2")
return "Step 2 completed - Workflow finished successfully", nil
func stepTwo(ctx context.Context) (string, error) {
fmt.Println("Step two completed!")
return "Step 2 completed - Workflow finished successfully", nil
}

func main() {
err := dbos.Launch()
if err != nil {
panic(err)
}
defer dbos.Shutdown()

wf(context.Background(), "hello DBOS")
// Initialize a DBOS context
ctx, err := dbos.NewDBOSContext(dbos.Config{
DatabaseURL: os.Getenv("DBOS_SYSTEM_DATABASE_URL"),
AppName: "myapp",
})
if err != nil {
panic(err)
}

// Register a workflow
dbos.RegisterWorkflow(ctx, workflow)

// Launch DBOS
err = ctx.Launch()
if err != nil {
panic(err)
}
defer ctx.Cancel()

// Run a durable workflow and get its result
handle, err := dbos.RunAsWorkflow(ctx, workflow, "")
if err != nil {
panic(err)
}
res, err := handle.GetResult()
if err != nil {
panic(err)
}
fmt.Println("Workflow result:", res)
}
```

Expand Down Expand Up @@ -90,36 +127,56 @@ You can add queues to your workflows in just a couple lines of code.
They don't require a separate queueing service or message broker&mdash;just Postgres.

```golang
var (
queue = dbos.NewWorkflowQueue("example-queue")
taskWf = dbos.WithWorkflow(task)
package main

import (
"fmt"
"os"
"time"

"github.com/dbos-inc/dbos-transact-go/dbos"
)

func task(ctx context.Context, i int) (int, error) {
time.Sleep(5 * time.Second)
func task(ctx dbos.DBOSContext, i int) (int, error) {
ctx.Sleep(5 * time.Second)
fmt.Printf("Task %d completed\n", i)
return i, nil
}

func main() {
err := dbos.Launch()
// Initialize a DBOS context
ctx, err := dbos.NewDBOSContext(dbos.Config{
DatabaseURL: os.Getenv("DBOS_SYSTEM_DATABASE_URL"),
AppName: "myapp",
})
if err != nil {
panic(err)
}
defer dbos.Shutdown()

// Register the workflow and create a durable queue
dbos.RegisterWorkflow(ctx, task)
queue := dbos.NewWorkflowQueue(ctx, "queue")

// Launch DBOS
err = ctx.Launch()
if err != nil {
panic(err)
}
defer ctx.Cancel()

// Enqueue tasks and gather results
fmt.Println("Enqueuing workflows")
handles := make([]dbos.WorkflowHandle[int], 10)
for i := range 10 {
handle, err := taskWf(context.Background(), i, dbos.WithQueue(queue.Name))
handle, err := dbos.RunAsWorkflow(ctx, task, i, dbos.WithQueue(queue.Name))
if err != nil {
panic(fmt.Sprintf("failed to enqueue step %d: %v", i, err))
}
handles[i] = handle
}
results := make([]int, 10)
for i, handle := range handles {
result, err := handle.GetResult(context.Background())
result, err := handle.GetResult()
if err != nil {
panic(fmt.Sprintf("failed to get result for step %d: %v", i, err))
}
Expand All @@ -130,47 +187,115 @@ func main() {
```
</details>

<details><summary><strong>🎫 Exactly-Once Event Processing</strong></summary>

####

Use DBOS to build reliable webhooks, event listeners, or Kafka consumers by starting a workflow exactly-once in response to an event.
Acknowledge the event immediately while reliably processing it in the background.

For example:

```golang
_, err := dbos.RunAsWorkflow(ctx, task, i, dbos.WithWorkflowID(exactlyOnceEventID))
```
</details>

<details><summary><strong>📅 Durable Scheduling</strong></summary>

####

Schedule workflows using cron syntax, or use durable sleep to pause workflows for as long as you like (even days or weeks) before executing.

```golang
dbos.RegisterWorkflow(dbosCtx, func(ctx dbos.DBOSContext, scheduledTime time.Time) (string, error) {
return fmt.Sprintf("Workflow executed at %s", scheduledTime), nil
}, dbos.WithSchedule("* * * * * *")) // Every second
```

You can add a durable sleep to any workflow with a single line of code.
It stores its wakeup time in Postgres so the workflow sleeps through any interruption or restart, then always resumes on schedule.

```golang
func workflow(ctx dbos.DBOSContext, duration time.Duration) (string, error) {
ctx.Sleep(duration)
return fmt.Sprintf("Workflow slept for %s", duration), nil
}

handle, err := dbos.RunAsWorkflow(dbosCtx, workflow, time.Second*5)
_, err = handle.GetResult()
```

</details>

<details><summary><strong>📫 Durable Notifications</strong></summary>

####

Pause your workflow executions until a notification is received, or emit events from your workflow to send progress updates to external clients.
All notifications are stored in Postgres, so they can be sent and received with exactly-once semantics.
Set durable timeouts when waiting for events, so you can wait for as long as you like (even days or weeks) through interruptions or restarts, then resume once a notification arrives or the timeout is reached.

For example, build a reliable billing workflow that durably waits for a notification from a payments service, processing it exactly-once:

```golang
func sendWorkflow(ctx dbos.DBOSContext, message string) (string, error) {
err := dbos.Send(ctx, dbos.WorkflowSendInput[string]{
DestinationID: "receiverID",
Topic: "topic",
Message: message,
})
}

func receiveWorkflow(ctx dbos.DBOSContext, topic string) (string, error) {
return dbos.Recv[string](ctx, dbos.WorkflowRecvInput{Topic: topic, Timeout: 48 * time.Hour})
}

// Start a receiver in the background
recvHandle, err := dbos.RunAsWorkflow(dbosCtx, receiveWorkflow, "topic", dbos.WithWorkflowID("receiverID"))

// Send a message
sendHandle, err := dbos.RunAsWorkflow(dbosCtx, sendWorkflow, "hola!")
_, err = sendHandle.GetResult()

// Eventually get the response
recvResult, err := recvHandle.GetResult()
```

</details>


## DBOS workflows

A workflow can be any function with the following signature:
```golang
type WorkflowFunc[P any, R any] func(ctx context.Context, input P) (R, error)
type GenericWorkflowFunc[P any, R any] func(ctx context.Context, input P) (R, error)
```

`P` and `R` must be concrete types (not `any`).
To register a workflow call `dbos.RegisterWorkflow(dbosCtx, workflow)` after having initialized a DBOS Context. Workflows can only be registered before DBOS is launched.

Workflows must be registered with DBOS using the `WithWorkflow` method before DBOS is launched.

Workflows can run steps, which can be any function with the following signature:
```golang
type StepFunc[P any, R any] func(ctx context.Context, input P) (R, error)
type GenericStepFunc[R any] func(ctx context.Context) (R, error)
```

To run a step within a workflow, use `RunAsStep`. Importantly, you must pass to `RunAsStep` the context received in the workflow function.
To run a step within a workflow, use `RunAsStep`. Importantly, you must pass to `RunAsStep` the context received in the workflow function (see examples above.)

The input and output of workflows and steps are memoized in your Postgres database for workflow recovery. Under the hood, DBOS uses the [encoding/gob](https://pkg.go.dev/encoding/gob) package for serialization (this means that only exported fields will be memoized and types without exported fields will generate an error.)

## Roadmap:
* logging for DBOS internals -- consider accepting a user provided logger
* OTel trace generation and export
* OTel logs -- consider leveraging the user provided logger
* config?
* go doc
* workflows send and recv
* workflows set and get event
* workflow cancellation maps
* queue dedup
* queue priority
* workflow timeouts
* DBOS Client
* datasources & transactions

## Getting started

Install the DBOS Transact package in your program:

```shell
github.com/dbos-inc/dbos-transact-go
go get github.com/dbos-inc/dbos-transact-go
```

You can store and export a Postgres connection string in the `DBOS_SYSTEM_DATABASE_URL` environment variable for DBOS to manage your workflows state. By default, DBOS will use `postgres://postgres:${PGPASSWORD}@localhost:5432/dbos?sslmode=disable`.
You can store and export a Postgres connection string in the `DBOS_SYSTEM_DATABASE_URL` environment variable for DBOS to manage your workflows state. By default, DBOS will use `postgres://postgres:${PGPASSWORD}@localhost:5432/dbos?sslmode=disable`.


## ⭐️ Like this project?

[Star it on GitHub](https://github.com/dbos-inc/dbos-transact-go)
[![GitHub Stars](https://img.shields.io/github/stars/dbos-inc/dbos-transact-go?style=social)](https://github.com/dbos-inc/dbos-transact-go)
Loading
Loading