Storage Backends
Where Skipper persists workflow state — an embedded SQLite store by default, MySQL for production, behind pluggable interfaces.
Skipper persists workflow state and schedules work through two pluggable interfaces: a
workflow store and a scheduler. Both are set on your SkipperConfig, so you can run
Skipper on the embedded default to start, then point it at your production database — without
changing any workflow code.
The default: embedded SQLite
Out of the box, Skipper uses an embedded SQLite store. With no configuration it runs fully in-memory, so you can build and run workflows with zero setup:
val config = SkipperConfig.forService("my-service") // in-memory SQLite, nothing else needed
SkipperConfig config = SkipperConfig.forService("my-service"); // in-memory SQLite, nothing else needed
The in-memory store keeps no data after the process exits, which is exactly what you want for getting started, local development, and tests. For a durable single-node store, back SQLite with a file instead:
val config = SkipperConfig.forService("my-service").apply {
workflowStore = SqliteWorkflowStore.Factory("skipper.db")
scheduler = SqliteScheduler.Factory("skipper.db")
}
SkipperConfig config = SkipperConfig.forService("my-service");
config.setWorkflowStore(new SqliteWorkflowStore.Factory("skipper.db"));
config.setScheduler(new SqliteScheduler.Factory("skipper.db"));
SQLite is single-node by design. To run multiple Skipper instances against shared state — the typical production setup — use MySQL.
MySQL (production)
The MySQL adapter stores Skipper’s tables in the same database your application already uses,
so Skipper adds no new critical dependency and supports running multiple instances against
shared state. Set the factories and provide a JDBC DataSource:
val config = SkipperConfig.forService("my-service").apply {
workflowStore = MySqlWorkflowStore.Factory()
scheduler = MySqlScheduler.Factory()
mySqlDataSource = dataSource
}
SkipperConfig config = SkipperConfig.forService("my-service");
config.setWorkflowStore(new MySqlWorkflowStore.Factory());
config.setScheduler(new MySqlScheduler.Factory());
config.setMySqlDataSource(dataSource);
Creating the schema
The MySQL adapter requires its schema to exist. Skipper’s schema ships as versioned Flyway
migrations bundled in the jar under db/migration (they create Skipper’s tables under a
configurable prefix — skipper_* by default, or tempo_* for deployments that keep the legacy
names, set via SkipperConfig.tablePrefix).
Apply them to your database once, before the first run — Skipper does not run them
automatically. The simplest way is to point Flyway at the bundled migrations on the classpath:
Flyway.configure()
.dataSource(dataSource)
.locations("classpath:db/migration") // migrations bundled in the Skipper jar
.load()
.migrate()
If you manage schema changes with your own tooling, apply the V*__*.sql files from
db/migration in order instead.
Migrations are immutable. Every schema change ships as a new migration file. Because the library runs against each adopting service’s database, a code change that references a new column must wait until that migration has been applied everywhere it runs — otherwise the deployed code references a column that doesn’t yet exist. Apply the migration first, then deploy the code.
Configuration such as the store, scheduler, retry strategy, and checkpoint mode is set on
SkipperConfigdirectly.SkipperRuntime(config)then wires the engine from it — see the Quickstart.
Serialization
Workflow state, action results, signals, and errors are persisted as serialized blobs. Types must be serializable — a primitive, or a POJO/data class that Jackson can serialize (no top-level generics). Serialization is kept backwards compatible so that data written by an older version can still be read after an upgrade. See Troubleshooting if you hit a serialization error.