This example demonstrates a complete workflow setup using MariaDB/MySQL for persistent storage. It shows all the key components working together:
- ✅ SQL RecordStore - Persist workflow state in MariaDB
- ✅ SQL TimeoutStore - Manage scheduled operations in the database
- ✅ Transactional Outbox Pattern - Ensure exactly-once event processing
- ✅ Event Streaming - Coordinate workflow steps via events
- ✅ Real-world Workflow - Order processing with multiple steps
- How to set up MariaDB/MySQL with Workflow
- How to configure connection pooling and timeouts
- How the transactional outbox pattern works
- How to persist workflow state, events, and timeouts to SQL
- How to query workflow data directly from the database
- Docker and Docker Compose installed
- Go 1.21 or later
docker-compose up -dThis starts MariaDB and automatically creates the required schema.
go run .You'll see output showing:
- Database connection established
- Schema verification
- Order workflows being processed
- Data being persisted to MariaDB
Connect to MariaDB and query the workflow data:
# Connect to MariaDB
docker exec -it sqlexample-mariadb mysql -uworkflow_user -pworkflow_pass workflow_db
# Query workflow records
SELECT workflow_name, foreign_id, run_id, run_state, status, created_at
FROM workflow_records
ORDER BY created_at DESC;
# Query outbox events
SELECT id, workflow_name, created_at
FROM workflow_outbox
ORDER BY created_at DESC;docker-compose down -vThe example implements a realistic e-commerce order workflow:
Order Created → Validate → Process Payment → Fulfill → Completed
↓ (invalid) ↓ (failed) ↓ (failed)
Rejected Payment Failed Fulfillment Failed
Order States:
OrderCreated- Initial state when order is placedOrderValidated- Order passed validation checksPaymentProcessed- Payment successfully chargedOrderFulfilled- Items shipped to customerOrderCompleted- Order fully completeOrderRejected- Validation failedPaymentFailed- Payment processing failedFulfillmentFailed- Shipping failed
workflow_records - Stores order workflow state:
workflow_name: "order-processor"foreign_id: Order ID (e.g., "order-1001")run_id: Unique execution ID (UUID)run_state: System state (Running, Completed, etc.)status: Business state (OrderCreated, PaymentProcessed, etc.)object: Serialised Order data (customer, items, totals)created_at,updated_at: Audit timestamps
workflow_outbox - Transactional outbox for events:
- Events created when state changes
- Published to event stream
- Deleted after successful publish
- Ensures exactly-once processing
The example demonstrates the transactional outbox pattern:
- Step Executes - Payment processing step runs
- Transaction Begins - Database transaction starts
- State Update - Order state updated to
PaymentProcessed - Event Written - Event written to
workflow_outbox - Transaction Commits - Both changes committed atomically
- Event Published - Outbox processor publishes event to stream
- Event Deleted - Successfully published event removed from outbox
This guarantees that state changes and events are always in sync.
sqlexample/
├── README.md # This file
├── docker-compose.yml # MariaDB setup
├── schema.sql # Database schema
├── main.go # Main application
└── main_test.go # Tests
dsn := "workflow_user:workflow_pass@tcp(localhost:3306)/workflow_db?parseTime=true&charset=utf8mb4"Parameters explained:
parseTime=true- Required for proper datetime handlingcharset=utf8mb4- Full Unicode support (including emojis)collation=utf8mb4_unicode_ci- Case-insensitive Unicode comparison
db.SetMaxOpenConns(25) // Max concurrent connections
db.SetMaxIdleConns(5) // Keep 5 connections alive
db.SetConnMaxLifetime(5 * time.Minute) // Recycle connections every 5minTune these based on your workload:
- Low traffic:
MaxOpenConns=10,MaxIdleConns=2 - Medium traffic:
MaxOpenConns=25,MaxIdleConns=5(default in example) - High traffic:
MaxOpenConns=50-100,MaxIdleConns=10-25
You can query workflow data directly for reporting, debugging, or integration:
// Find all orders in payment processing
rows, err := db.Query(`
SELECT foreign_id, object, created_at
FROM workflow_records
WHERE workflow_name = ? AND status = ?
`, "order-processor", OrderPaymentProcessed)
// Find failed orders
rows, err := db.Query(`
SELECT foreign_id, object
FROM workflow_records
WHERE workflow_name = ? AND status IN (?, ?, ?)
`, "order-processor", OrderRejected, OrderPaymentFailed, OrderFulfillmentFailed)
// Get order history timeline
rows, err := db.Query(`
SELECT run_id, status, updated_at
FROM workflow_records
WHERE workflow_name = ? AND foreign_id = ?
ORDER BY updated_at
`, "order-processor", "order-1001")Check if outbox events are building up (indicates event publishing issues):
SELECT
workflow_name,
COUNT(*) as pending_events,
MIN(created_at) as oldest_event,
MAX(created_at) as newest_event
FROM workflow_outbox
GROUP BY workflow_name;If events are accumulating:
- Check event streamer is running
- Verify event stream (Kafka, Reflex) is available
- Check network connectivity
- Review logs for publish errors
Backup workflow data:
# Backup all workflow data
docker exec sqlexample-mariadb mysqldump -uworkflow_user -pworkflow_pass workflow_db > backup.sql
# Backup just workflow tables
docker exec sqlexample-mariadb mysqldump -uworkflow_user -pworkflow_pass workflow_db workflow_records workflow_outbox > backup.sqlRestore:
docker exec -i sqlexample-mariadb mysql -uworkflow_user -pworkflow_pass workflow_db < backup.sqlCheck MariaDB is running:
docker-compose ps
docker-compose logs mariadbRun schema creation:
docker exec -i sqlexample-mariadb mysql -uworkflow_user -pworkflow_pass workflow_db < schema.sqlReduce connection pool or increase MariaDB max_connections:
db.SetMaxOpenConns(10) // Reduce from 25This can happen under high concurrency. The workflow library handles this automatically with retries.
- ✅ Use environment variables for credentials (not hardcoded)
- ✅ Use SSL/TLS for database connections (
tls=truein DSN) - ✅ Restrict database user permissions to only required tables
- ✅ Use connection timeouts to prevent hanging connections
- ✅ Monitor slow queries with
EXPLAIN - ✅ Add indexes for your specific query patterns
- ✅ Implement archival strategy for old records
- ✅ Monitor outbox event lag
- ✅ Configure appropriate connection pool sizes
- ✅ Set up MariaDB replication for redundancy
- ✅ Use connection retry logic
- ✅ Monitor database health
- ✅ Plan for backup and recovery
- Database Setup Guide - Complete database configuration guide
- Adapters - Learn about all adapter types
- Configuration - Tune workflow performance
- Monitoring - Set up observability
- Order Processor Example - Complex order processing workflow
- Getting Started - Basic workflow concepts