youndie@kotlin.website:~/src$ cat petich/README.md

petich

jvm · ktor · exposed · postgres
Distributed saga engine built around one question: what is left in the system if you die halfway. Steps as interceptors, compensation in reverse order, sagas that pause for a human and resume on a later request, and an outbox written in the same transaction as the state change.

Why it exists

An operation spanning several services is not one database write. Reserve capacity, claim a quota, apply the change, hand it downstream, notify — any step can refuse, and by then some of the earlier ones are already irreversible. try/catch does not help: what needs undoing is not a transaction but actions that already happened, in reverse, and only the ones that really did.

What you get

  • One interceptor, both directions. A step declares execute() and compensate() in the same object, so undo cannot quietly drift from do.
  • PhasesENRICHMENT → VALIDATION → AUTHORIZATION → EXECUTION → POST_PROCESSING — with priority inside each.
  • Waiting for a human without holding a thread or a connection: the saga is a value, it goes to storage and resumes on a later request. With a deadline, because a suspended saga nobody returns to is a resource leak with better manners.
  • An outbox in the same transaction as the state change, which makes "the work happened but the notification never went out" structurally impossible. A repository without outbox support still works — the engine degrades visibly to no events rather than pretending.
  • Optimistic locking by version plus a per-saga mutex.

Install

repositories { maven("https://reposilite.kotlin.website/snapshots") }

dependencies {
    implementation("io.github.youndie:petich-core:0.1.0.2")
    implementation("io.github.youndie:petich-ktor:0.1.0.2")
    implementation("io.github.youndie:petich-postgres:0.1.0.2")
}

petich-outbox-core, petich-idempotency and petich-scheduler deliberately do not depend on the core, and are usable on their own.

petich-postgres ships no driver and no connection pool: it takes an Exposed Database and does not care what is underneath. Choosing a driver is the application's decision.