agentleFS
MatanElbazMatanElbaz/backend-skills/skills/transaction-boundaries/SKILL.md

transaction-boundaries

Skill1 starsChanged 42 days agoMITView on GitHub

Use when writing or reviewing code that mixes database writes with external side effects such as HTTP calls, message publishing, emails, or cache updates

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    No network call inside an open transaction.

    line 17
  2. Distinctive:

    Keep transactions short. No waiting on users or remote systems, except the outbox relay described below.

    line 22

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Transaction boundaries

A database transaction can roll back. An HTTP call, a published message, or a sent email cannot. Mixing them inside one method is how systems end up charged-but-not-ordered or announced-but-not-saved.

When to use

  • A method writes to the database and also calls another service, publishes an event, sends a notification, or updates a cache.
  • Reviewing any @Transactional method.

Checklist

  1. No network call inside an open transaction. It holds a connection and locks while it waits, and it cannot be undone if the commit fails.
  2. Publishing before commit can announce something that then rolls back. Publishing after commit can be lost if the process dies in between. Use a transactional outbox: write the event as a row in the same transaction, and let a relay publish it.
  3. Assume every consumer sees events at least once, and make handlers idempotent (see idempotency).
  4. With Spring's default proxy mode, @Transactional is applied by a proxy. A call from another method in the same class bypasses it, and so does a private method.
  5. By default Spring rolls back on unchecked exceptions only. A checked exception commits unless rollbackFor says otherwise.
  6. Keep transactions short. No waiting on users or remote systems, except the outbox relay described below.
  7. A read followed by a write of the same row needs a lock or an optimistic version column, or two requests will overwrite each other.

Bad example

@Transactional
public void placeOrder(Order o) {
    orders.save(o);
    paymentClient.charge(o.total());        // remote call inside the transaction
    broker.send("order-placed", o.id());    // published before commit
}

What to flag

  • charge runs even if the commit later fails: the customer is charged and there is no order.
  • send can be consumed before the commit, so a consumer looks up an order that does not exist yet. If the commit fails, the event describes something that never happened.
  • The remote call keeps a database connection and row locks open for as long as the payment service takes to answer.

Good example

@Transactional
public void placeOrder(Order o) {
    orders.save(o);
    outbox.add("order-placed", o.id());   // same transaction as the order
}

// Separate component, runs after commit and retries on failure.
@Scheduled(fixedDelay = 500)
void relay() {
    for (OutboxEvent e : outbox.pending(100)) {
        broker.send(e.topic(), e.payload());   // must block until the broker acknowledges
        outbox.markSent(e.id());
    }
}

The order and its event commit or roll back together. send must block until the broker acknowledges, otherwise markSent can record an event that was never delivered. The relay can still publish an event twice if it crashes between send and markSent, which is why consumers must be idempotent. If several relay instances run, either run a single relay, or have relay open a transaction, lock the rows inside it with FOR UPDATE SKIP LOCKED, and keep it open until the batch is marked sent. That transaction spans the broker call, the one accepted exception to item 1, so keep batches small and the send timeout short. Charge the customer in a consumer of order-placed, using an idempotency key derived from the order ID.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.