bloccs
Use cases

Use case

Payments & stateful flows

Money paths have to be legible. bloccs lays the flow out as a typed graph: validate and dedupe before anything moves, branch on state, then capture and book the ledger as separate supervised sinks. Idempotency and the DB/HTTP boundaries are declared, so a retry can't double-charge and a stray effect can't compile.

The flow

payments.bloccs
Payments & stateful flows as a bloccs network

Where this stands today

Where this stands today: idempotency, DB reads and writes, outbound capture, and supervised recovery are all shipped in 0.8 — the core of this flow runs on real adapters. As with any source, charge is fed by your web layer; bloccs doesn't serve HTTP itself.

Source of truth

The manifest

The whole network is one TOML file. Drop it in, run mix bloccs.compile, and bloccs emits a Broadway supervision tree from it — the compiler checks every edge, schema, and declared effect first.

  • Edges match ports by schema, or it won't compile.
  • Effects are declared per node — nothing touches the outside world undeclared.
  • Supervision and concurrency are part of the file, not an afterthought.
payments.bloccs
[network]
id      = "payments"
version = "0.1.0"
runtime = "beam"

[nodes]
charge   = { use = "nodes/charge.bloccs" }
validate = { use = "nodes/validate.bloccs" }
dedupe   = { use = "nodes/dedupe.bloccs" }
branch   = { use = "nodes/branch.bloccs" }
capture  = { use = "nodes/capture.bloccs" }
ledger   = { use = "nodes/ledger.bloccs" }

[[edges]]
from = "charge.created"
to   = "validate.charge"

[[edges]]
from = "validate.valid"
to   = "dedupe.charge"

[[edges]]
from = "dedupe.fresh"
to   = "branch.charge"

# Fan-out: capture at the provider and book the ledger entry.
[[edges]]
from = "branch.authorized"
to   = ["capture.charge", "ledger.entry"]

[expose]
in  = { charge = "charge.created" }
out = { captured = "capture.captured", booked = "ledger.booked" }

[supervision]
strategy     = "rest_for_one"
max_restarts = 3
max_seconds  = 60

[deploy]
concurrency = { capture = 1, ledger = 1 }

Anatomy

Node by node

Each node declares its kind and the capabilities it's allowed to use. Pure nodes touch nothing; effectful nodes carry a badge for exactly what they reach.

charge

Source +HTTP

Receives the charge request as a typed Charge@1.

validate

Node

Checks amount, currency, and shape. Pure and deterministic.

dedupe

Node +DB

Looks up the idempotency key so a retried request can't double-charge.

branch

Split

Routes on charge state — only authorized charges proceed to capture.

capture

Sink +HTTP

Captures at the payment provider. The outbound call is a scoped capability.

ledger

Sink +DB

Writes the double-entry ledger row. Runs alongside capture under supervision.

Build this one for real.

$ {:bloccs, "~> 0.9"}