DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

SBOMs are essential to circumventing software supply chain attacks, and they provide visibility into various software components.

Related

  • Designing Microservices Architecture With a Custom Spring Boot Starter and Auto-Configuration Framework
  • Failure Handling Mechanisms in Microservices and Their Importance
  • Chaos Engineering for Microservices
  • gRPC and Its Role in Microservices Communication

Trending

  • How to Build a Real API Gateway With Spring Cloud Gateway and Eureka
  • Understanding Time Series Databases
  • The Agile Paradox
  • The Battle of the Frameworks: Choosing the Right Tech Stack
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Seata the Deal: No More Distributed Transaction Nightmares Across (Spring Boot) Microservices

Seata the Deal: No More Distributed Transaction Nightmares Across (Spring Boot) Microservices

Seata the deal: Example of Apache Seata-managed distributed transactions across multiple Spring Boot microservices; Apache Seata, Spring Boot, Java 21.

By 
Biagio Tozzi user avatar
Biagio Tozzi
·
Jul. 03, 25 · Analysis
Likes (2)
Comment
Save
Tweet
Share
1.8K Views

Join the DZone community and get the full member experience.

Join For Free

In a time when nostalgia may have gone a little too far, the software development world has been swept by a heated debate: has the massive adoption of the microservices pattern truly delivered the expected benefits, or is its balance sheet more uncertain? Many teams are starting to wonder whether it’s time for a “homecoming” to the good old, reliable monolith — or if the answer lies somewhere in between, in the form of the modular monolith.

A distracted architect

A distracted architect

distributed transactions

This article aims to address precisely this topic, demonstrating how Apache Seata, in this case combined with the agility of Spring Boot, manages to transform what for many is a nightmare into a surprisingly manageable solution. I will show you how Seata can help you sleep soundly at night, eliminating the need for complex architectures and rollback strategies, and/or resorting to more cumbersome patterns like the Outbox pattern.

A nostalgic and skeptical architect
A nostalgic and skeptical architect

Apache Seata

Apache Seata is an open-source distributed transaction solution, currently in Apache incubation, that delivers high-performance and easy-to-use distributed transaction services under a microservices architecture.

The main components are:

  • Transaction Coordinator (TC): Maintains the status of global and branch transactions, drives the global commit or rollback.
  • Transaction Manager (TM): Defines the scope of a global transaction, begins a global transaction, commits or rolls back a global transaction.
  • Resource Manager (RM): Manages resources that branch transactions work on, talks to TC for registering branch transactions and reporting the status of branch transactions, and drives the branch transaction commit or rollback.

The supported transaction models are: AT, XA (2PC), and SAGA.

(If you’d rather skip the theory, go straight to the practical solution!)

XA (2PC)

XA is a specification released in 1991 by X/Open (which later merged with The Open Group).

1. Phase 1: Prepare (Commit Vote)

  • TC (Seata Server) asks all participating RMs (e.g., your microservices interacting with databases) if they are ready to commit their local transaction.
  • Each RM performs its operations, writes transaction logs to ensure durability, and locks resources to guarantee isolation.
  • If an RM is ready, it responds “Yes” to the TC. If not, it responds “No”.

2. Phase 2: Commit or Rollback

  • If all RMs respond “Yes”, the TC sends a “Commit” command to all RMs. They finalize their local transactions and release locks.
  • If any RM responds “No”, or if the TC detects a timeout/failure, the TC sends a “Rollback” command. All RMs undo their operations and release locks.

Pros and Cons (Briefly)

  • Pro: Provides strong data consistency (ACID) across multiple services, acting like a single, unbreakable transaction.
  • Con: Can lead to resource blocking (high latency) if participants or the coordinator are slow or fail, potentially impacting availability and scalability. It also relies on underlying databases/resources supporting the XA standard.

SAGA

The SAGA pattern is a widely adopted approach in microservices architectures to manage distributed transactions. Unlike 2PC, Saga sacrifices immediate strong consistency for higher availability and scalability, achieving eventual consistency.

A Saga is a sequence of local transactions, where each local transaction (within a single microservice) updates its own database and then publishes an event or message to trigger the next local transaction in the sequence.

  • No global locks: crucially, local transactions commit immediately and do not hold global locks, allowing for greater concurrency.
  • Compensation for failure: If any local transaction fails, the Saga does not “rollback” in the traditional sense. Instead, it executes a series of compensating transactions to semantically undo the effects of previously completed local transactions. These compensating transactions are new operations designed to reverse the business impact.

Saga can be implemented via:

  • Choreography: Services publish events and react to them, leading to a decentralized flow.
  • Orchestration: A central orchestrator service coordinates the flow, sending commands and reacting to responses.

Pros and Cons (Briefly)

  • Pro: Excellent for high availability and scalability due to the lack of long-held distributed locks. Ideal for loosely coupled microservices.
  • Con: Achieves eventual consistency, meaning data might be temporarily inconsistent. Requires significant development effort to implement all compensating transactions and manage complex Saga logic, which can also make debugging harder.

AT

The AT (Automatic Transaction) Mode is Seata’s flagship solution, aiming to offer the ease-of-use of 2PC with the non-blocking nature and scalability benefits of Saga. It’s the recommended default for most microservices using relational databases.

1. Phase 1: Local Transaction and Prepare

  • When a microservice (RM) performs a database operation (e.g., UPDATE, INSERT, DELETE) within a global transaction:
  • Seata’s intelligent DataSourceProxy intercepts the SQL.
  • It automatically creates an undo_log (recording the data’s state before the modification).
  • The SQL operation is executed and committed immediately on the local database.
  • Seata then acquires a global lock for the modified resource via the Transaction Coordinator (TC). This lock is not a traditional database lock; it prevents other global transactions from concurrently modifying the same resource, but doesn’t block read operations.
  • The RM informs the TC that its branch transaction is “prepared.”

2. Phase 2: Global Commit or Rollback

  • Global commit: If all branch transactions prepare successfully, the TC instructs them to commit. Since local DB transactions were already committed in Phase 1, RMs simply release their global locks.
  • Global rollback: If any branch transaction fails, or the global transaction needs to roll back:
    • The TC instructs the RMs to roll back.
    • RMs use their stored undo_log to automatically compensate for the changes made to their local databases, effectively restoring the previous state. They then release their global locks.

Pros and Cons (Briefly)

  • Pro: Provides strong consistency for the global transaction. Offers excellent availability and scalability as local database locks are held only briefly. It’s highly transparent to developers, requiring minimal code changes. Automatic rollback simplifies error handling.
  • Con: Primarily designed for relational databases. While non-blocking at the DB level, there’s still an overhead from generating undo_logs and managing global locks.

An impatient developer

An impatient developer

A Practical Demonstration With Spring Boot

Let’s envision a scenario involving two distinct microservices, each operating with its own dedicated and autonomous database:

  • Credit API: responsible for managing user credit (money balance)
  • Shipping API: dedicated to handling shipment purchases

Orchestrating these two services is a BFF (Backend For Frontend). Its role is to coordinate the shipment purchase operation, which translates into a sequence of distributed calls:

  • Checking and/or deducting user credit via the Credit API.
  • Actually purchasing the shipment using the credit, via the Shipping API.

The crucial question then arises: how can we ensure that these operations, distributed across different services and databases, maintain their transactional consistency, guaranteeing that the purchase is completed only if the credit has been successfully updated, and vice versa?

A neighbor

A neighbor

Architecture


Architecture

TM

The BFF will represent the Transaction Manager, i.e., it will define the global transaction.

Java
 
@Service
public class BFFManager {

    @Autowired
    private CreditService creditService;

    @Autowired
    private ShippingService shippingService;

    @GlobalTransactional
    public ShippingResult buyShipping(Long userID, BigDecimal cost) {
        var wallet = creditService.updateBalance(userID, cost);
        var shipping = shippingService.buyShipping(userID, cost);
        
        wallet = creditService.getWallet(userID);

        var result = new ShippingResult();
        result.setCost(cost);
        result.setShippingID(shipping.getId());
        result.setCurrentBalance(wallet.getBalance());
        return result;
    }
}


So, just the @GlobalTransactional annotation is enough? Of course not, but little else is needed:

Dependencies needed for TM:

Java
 
implementation 'org.apache.seata:seata-spring-boot-starter:2.3.0'
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-seata:2023.0.3.3') {
  exclude group: 'org.apache.seata', module: 'seata-spring-boot-starter'
}


The spring-cloud-starter-alibaba-seata, among other things it can do, ensures that HTTP communications between microservices always contain the Global Transaction ID (XID).

The seata-spring-boot-starter is the classic Spring Boot starter that autoconfigures the Seata entity (in this case, the TM) starting from the properties:

Java
 
seata:
  enabled: true
  data-source-proxy-mode: AT
  enable-auto-data-source-proxy: true
  application-id:  ${spring.application.name}
  tx-service-group: default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default
    grouplist:
      default: 127.0.0.1:8091


RM

Both credit-api and shipping-api act as RM. They only need dependency seata-spring-boot-starter with the following properties:

Java
 
seata:
  enabled: true
  data-source-proxy-mode: AT
  enable-auto-data-source-proxy: true
  application-id:  ${spring.application.name}
  tx-service-group: default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default
    grouplist:
      default: 127.0.0.1:8091


It is necessary that the RM DB contains the undo_log table for Seata. Here are the necessary scripts for each type of DB.

In the code that you will find on GitHub the creation of the table is managed through the docker compose that creates the dedicated db (through org.springframework.boot:spring-boot-docker-compose)

In RMs, no specific annotation is needed. Write your code towards the repository as you always have. If you want, and I recommend it, continue to use @Transactional for local transactions.

TC

The TC is represented by the heart of Seata, seata-server. This requires two main configurations:

  • Registry: defines the Service Registry that Seata will use (nacos, eureka, consul, zookeper, redis, file). For this example, I chose to use the file type.
  • Store: defines the persistence of global transaction data and global locks (file, db, redis). For this example, I chose to use db type.

Here’s the Docker Compose setup used to initialize the server:

YAML
 
services:
  mysql:
    image: mysql:8.0.33
    container_name: mysql-seata
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: seata
      MYSQL_USER: seata_user
      MYSQL_PASSWORD: seata_pass
    ports:
      - "3317:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./docker/seata/mysql.sql:/docker-entrypoint-initdb.d/seata.sql:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-prootpass"]
      interval: 10s
      timeout: 5s
      retries: 5

  seata-server:
    image: apache/seata-server:2.3.0
    container_name: seata-server
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      - SEATA_CONFIG_NAME=application.yml
    volumes:
      - "./docker/seata/resources/application.yml:/seata-server/resources/application.yml"
      - "./docker/seata/mysql-connector-j-8.0.33.jar:/seata-server/libs/mysql-connector-j-8.0.33.jar"
    ports:
      - "7091:7091"
      - "8091:8091"

volumes:
  mysql_data:


And the configuration properties (application.yaml):

YAML
 
server:
  port: 7091

spring:
  application:
    name: seata-server
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://mysql:3306/seata
    username: seata_user
    password: seata_pass

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${log.home:${user.home}/logs/seata}

console:
  user:
    username: seata
    password: seata

seata:
  security:
    secretKey: seata
    tokenValidityInMilliseconds: 1800000

  config:
    type: file

  registry:
    type: file

  store:
    mode: db
    db:
      dbType: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://mysql:3306/seata
      user: seata_user
      password: seata_pass
      min-conn: 10
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      vgroup-table: vgroup_table
      query-limit: 1000
      max-wait: 5000


As you may have noticed, the dedicated database needs a number of tables to function. All the creation SQL scripts are available here.

Play!

Java
 
@SpringBootTest
public class GlobalTransactionalTest {

    @Autowired
    private BFFManager bffManager;

    @Autowired
    private CreditService creditService;

    @MockitoBean
    private ShippingService shippingService;

    @Test
    public void globalTransactionalTest_OK() {
        var wallet = creditService.getWallet(1L);
        var shipping = new Shipping();
        shipping.setId(2L);
        when(shippingService.buyShipping(1L, new BigDecimal(4))).thenReturn(shipping);
        bffManager.buyShipping(1L, new BigDecimal(4));
        var newWallet = creditService.getWallet(1L);
        assertEquals(new BigDecimal("4.00"), wallet.getBalance().subtract(newWallet.getBalance()));
    }

    @Test
    public void globalTransactionalTest_KO() {
        var wallet = creditService.getWallet(1L);
        var shipping = new Shipping();
        shipping.setId(2L);
        when(shippingService.buyShipping(1L, new BigDecimal(4))).thenThrow(new RuntimeException());

        try {
            bffManager.buyShipping(1L, new BigDecimal(4));
        } catch (Exception e) {}

        var newWallet = creditService.getWallet(1L);
        assertEquals(newWallet.getBalance(), wallet.getBalance());
    }

}


The complete and working code is available on GitHub. Run the components and let me know what you think!

Key Alternatives

  • Atomikos: XA (2PC). It only works within the same microservice that accesses multiple databases (there is no Transaction Coordinator, no XID propagation)
  • Workflow engine for SAGA orchestration (Camunda, Temporal.io): requires an external orchestrator and a higher integration effort.
  • SAGA (choreography event-driven): eventual consistency trade-off.
  • Outbox pattern: eventual consistency.

This article does not aim to promote Apache Seata over the other alternatives mentioned, but rather to highlight its ease of use. As always, the right tool should be chosen based on the specific context and system requirements.

Next episode: “How cool were the monoliths?” Stay tuned.

Stay tuned

Distributed transaction Relational model Spring Boot microservices

Published at DZone with permission of Biagio Tozzi. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Designing Microservices Architecture With a Custom Spring Boot Starter and Auto-Configuration Framework
  • Failure Handling Mechanisms in Microservices and Their Importance
  • Chaos Engineering for Microservices
  • gRPC and Its Role in Microservices Communication

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: