Skip to main content
Version: 2.4

Transactions

What Is a Transaction?

Imagine you are transferring money between two bank accounts. The bank must debit one account and credit another. If the system crashes after the debit but before the credit, the money would simply vanish. A transaction prevents this — it groups multiple operations into a single "all-or-nothing" unit:

  • Either every operation succeeds and is permanently saved (commit),
  • Or every operation is undone as if nothing happened (rollback).

This guarantee is provided by the database. Only data stored in the database participates in a transaction — the database knows how to undo (roll back) every change it made within that transaction.

Key Principle

Only database changes within a microservice are transactional. Anything that leaves the database boundary — a REST call to other microservice, external system such as JIRA, a message to SAP, an email — is not part of the database transaction and cannot be automatically rolled back.

The Core Problem: What About Non-Database Operations?

In a typical tSM configuration, you don't just update a database. You also call external systems (JIRA, SAP, billing platforms), send notifications, create comments, and more. These operations fall outside the database transaction. This creates a fundamental challenge:

If something fails after the JIRA task is already created, the database rolls back all its changes — but the JIRA task remains. You now have an orphaned task in JIRA that references data that no longer exists in tSM.

There are two strategies for handling non-database operations:

Strategy 1: Delay Until After Commit (Kafka / Async)

The operation is not executed immediately. Instead, a message is written to Apache Kafka — a messaging infrastructure. The message is only dispatched after the database transaction successfully commits. If the transaction rolls back, the message is never sent.

Why Kafka? Writing to Kafka is an infrastructure operation that practically never fails (Kafka is a local, highly available cluster). This makes it a safe "handoff point" between the transactional world and the outside world.

What if the Kafka consumer fails? The message stays in Kafka and is retried. The database is already committed, so there is no inconsistency — the operation will eventually succeed.

This is the preferred approach for: notifications, comments, audit logs, and any operation where a small delay is acceptable. In tSM, this is supported via:

Strategy 2: Send Immediately, Accept the Risk

The external call is made during the transaction, before the commit. If the call succeeds but the transaction later fails (e.g., a validation error), the database rolls back — but the external effect (e.g., a created JIRA task) persists.

This approach is risky and requires you to implement compensation (manual or automated rollback of the external system). Only use it when you genuinely need the response from the external system during the same transaction.

Comparison: Which Strategy to Use?

AspectImmediate CallAsync (Kafka)External / Kafka Task
When is the call made?During the transactionAfter commit (via Kafka)In a separate transaction
Response available?✅ Yes❌ No (fire-and-forget)✅ Yes
Safe on rollback?❌ External effect remains✅ Never sent✅ Isolated transaction
Retry on failure?❌ Manual✅ Kafka retries✅ Automatic retries
ComplexityLowLowMedium
Use whenRead-only queries, idempotent callsFire-and-forget, notifications, commentsNeed response, must handle errors, critical operations

Where to Go Next

  • SpEL Scripts — Learn how the {async: true} flag and #businessException interact with transactions in SpEL expressions: SpEL and Transactions
  • Process Engine — Learn about wait states, transaction boundaries, external tasks, and the SAGA pattern: Process Engine Transactions