All posts
Engineering8 min read

An activation is a saga, not a transaction

CloudShope Team · Engineering

When a customer pays for three services at once, provisioning them is three separate pieces of work against three different systems. Wrapping all of it in one database transaction would be tidy and wrong.

Failure is per item, so state is per item

Each line of an order carries its own activation status, its own retry count and its own last error. An order-level counter loses the only thing worth knowing: which item failed, and how often it has been tried.

The two that succeeded stay provisioned. The customer has them, they paid for them, and unwinding them because a third system was briefly unavailable would be the wrong kind of consistency.

Retries are rows, not a queue

The sweep claims items whose next attempt is due, locking them so two overlapping runs cannot activate the same item twice. The retry state lives in the row rather than in a message broker, which means it survives a restart and can be read with a query.

Money is checked before anything is provisioned

The order is priced by the same code that writes it, the payment is reconciled against that price, and only then does provisioning begin. A licence minted against an unbalanced order is far more expensive to unpick than a sale that was refused at the counter.

What this costs

Complexity, honestly. There is a status machine, a sweep, and a set of rules about which states are terminal. The alternative — all-or-nothing provisioning — is simpler right up until the first partial failure, at which point it is simpler and wrong.

Want this running on your numbers?

Start with a free trial — no card, and every service on one account.

See pricing