Run a job instead of a server#
An application on the database layer alone, whose boot is a unit of work over rows: run once,
on a schedule, or by a cron.
The build#
Depend on eezo-db instead of the umbrella, and skip the plugin: there are no routes to
generate.
libraryDependencies += "io.eezo" %% "eezo-db" % eezoVersionWith only eezo-db on the classpath, derives Form and derives Resource don't compile, and
dev is a command the application doesn't know. That's the point of opting into one layer.
boot is the job#
import java.time.LocalDate
import io.eezo.db.*
import io.eezo.db.Scopes.*
import models.Reminder
object Main extends DbApp {
override def schema: Schema = AppSchema
override def boot(): Unit = {
val reminders = Table[Reminder]
val today = LocalDate.now()
val delivered = transact {
val due = reminders
.where(_.sent === false)
.where(_.dueOn <= today)
.orderBy(_.dueOn.asc)
.list()
due.foreach { reminder =>
println(s" ${reminder.dueOn} ${reminder.text}")
reminders.update(reminder.copy(sent = true))
}
due.size
}
println(s"$delivered reminder(s) delivered")
}
}DbApp installs the database around boot and takes it down after, so sbt run does the work
and exits. The query is the typed layer: where on a field, orderBy, list(), inside one
transact so the reads and the updates are one transaction.
The schema commands come along#
status, sync, freeze, migrate, reset, drop, dump and ddl work exactly as on a web
application, so the table is created the same way:
sbt "run freeze initial schema"
sbt "run migrate --apply"
sbt runRunning it repeatedly#
- On a schedule: whatever runs commands on your platform, cron or a Fly scheduled machine,
running the same
java -cp lib/* Mainthe staged Dockerfile does. - While developing, re-run on save with sbt's own watch:
sbt ~run.
Seeding#
A job that wants data on its first run can check the table and insert:
if (reminders.query.count() == 0L) Reminder.seed(today).foreach(reminders.insert)The reminders example under examples/reminders is this whole page as a running project.