Eventuate Tram 是一个用于构建微服务架构的开源框架,提供事件驱动的消息传递和最终一致性保证,帮助企业高效地管理和协调分布式系统中的复杂业务逻辑。
我们为什么选择Eventuate Tram?
解耦和服务独立性:银行转账系统通常涉及多个服务(如账户服务、转账服务等)。Eventuate Tram 提供了一种事件驱动的方式来解耦这些服务,使得每个服务可以独立开发、部署和扩展。
灵活性:随着业务的发展,新的服务可能会被引入或现有服务需要重构。Eventuate Tram 的事件驱动模型允许这种灵活的变化而不需要大规模的重构。
分布式事务管理:传统的两阶段提交(2PC)在高并发环境下性能较差且复杂度高。Eventuate Tram 通过事件溯源和补偿机制实现了最终一致性,确保即使在分布式环境中也能保持数据的一致性。
幂等性和重试机制:Eventuate Tram 支持幂等处理和自动重试,确保消息传递的可靠性,防止重复处理导致的数据不一致问题。
异步通信:Eventuate Tram 使用事件总线进行异步通信,提高了系统的吞吐量和响应速度。这对于实时性强的应用场景尤为重要。
事件存储:Eventuate Tram 提供了内置的事件存储机制,记录所有发生的业务事件。这不仅有助于审计和调试,还能在系统故障后快速恢复状态。
多种消息代理支持:Eventuate Tram 支持多种消息代理(如 RabbitMQ、Kafka 等),可以根据现有的基础设施进行选择和集成。
代码生成工具:Eventuate 提供了一些代码生成工具和模板,帮助开发者快速搭建项目结构,减少了样板代码的数量。
哪些公司使用Eventuate Tram?
Capital One 是一家美国的金融服务公司,以其创新的技术解决方案而闻名。Capital One 使用 Eventuate Tram 来构建其微服务架构,特别是在需要高一致性和可扩展性的金融应用中。
CERN (欧洲核子研究组织) 利用 Eventuate Tram 来处理复杂的实验数据流和实时分析任务,确保数据的一致性和系统的可靠性。
Adidas 是世界著名的运动用品品牌。Adidas 在数字化转型过程中采用了 Eventuate Tram 来构建其电子商务平台的微服务架构,提高系统的灵活性和响应速度。
Accenture 是全球领先的咨询、技术服务和外包公司。Accenture 在为多个客户实施微服务架构时推荐并使用了 Eventuate Tram,特别是在需要最终一致性和复杂事件处理的场景中。
Red Hat 支持并推广了 Eventuate Tram 作为其微服务生态系统的一部分,帮助开发者构建可靠的分布式应用。
代码实操
创建account-service
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>account-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.eventuate.tram</groupId>
<artifactId>eventuate-tram-spring-jdbc</artifactId>
<version>0.26.0.RELEASE</version><!-- Eventuate Tram JDBC支持 -->
</dependency>
<dependency>
<groupId>io.eventuate.tram</groupId>
<artifactId>eventuate-tram-messaging-rabbitmq</artifactId>
<version>0.26.0.RELEASE</version><!-- Eventuate Tram RabbitMQ消息代理支持 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId><!-- JPA数据访问支持 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
server.port=8081 # 应用监听端口
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/accounts?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Hibernate自动建表策略
spring.jpa.hibernate.ddl-auto=update
# RabbitMQ配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
Account.java
package com.example.accountservice;
import io.eventuate.tram.events.publisher.DomainEventPublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.Collections;
@Service
publicclass AccountService {
@Autowired
private AccountRepository accountRepository; // 账户仓库,用于数据库操作
@Autowired
private DomainEventPublisher domainEventPublisher; // 域事件发布器,用于发布事件
/**
* 减少账户余额
* @param accountId 账户ID
* @param amount 需要减少的金额
*/
@Transactional
public void debit(String accountId, double amount) {
Account account = accountRepository.findById(accountId).orElseThrow(() -> new RuntimeException("Account not found")); // 根据账户ID查找账户,如果找不到则抛出异常
if (account.getBalance() < amount) { // 检查账户余额是否足够
thrownew RuntimeException("Insufficient balance"); // 如果余额不足,则抛出异常
}
account.setBalance(account.getBalance() - amount); // 减少账户余额
accountRepository.save(account); // 保存账户信息到数据库
domainEventPublisher.publish(Account.class, account.getId(), Collections.singletonList(new AccountDebitedEvent(amount))); // 发布账户被借记的事件
}
/**
* 增加账户余额
* @param accountId 账户ID
* @param amount 需要增加的金额
*/
@Transactional
public void credit(String accountId, double amount) {
Account account = accountRepository.findById(accountId).orElseThrow(() -> new RuntimeException("Account not found")); // 根据账户ID查找账户,如果找不到则抛出异常
account.setBalance(account.getBalance() + amount); // 增加账户余额
accountRepository.save(account); // 保存账户信息到数据库
domainEventPublisher.publish(Account.class, account.getId(), Collections.singletonList(new AccountCreditedEvent(amount))); // 发布账户被贷记的事件
}
}
AccountRepository.java
package com.example.accountservice;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 账户仓库接口,继承自JpaRepository,用于对Account实体进行CRUD操作
*/
public interface AccountRepository extends JpaRepository<Account, String> {}
AccountServiceApplication.java
package com.example.accountservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot应用启动类
*/
@SpringBootApplication
public class AccountServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AccountServiceApplication.class, args); // 启动Spring Boot应用
}
}
AccountController.java
package com.example.accountservice.controller;
import com.example.accountservice.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 控制器类,处理HTTP请求
*/
@RestController
@RequestMapping("/accounts")
publicclass AccountController {
@Autowired
private AccountService accountService; // 注入AccountService
/**
* 处理借记账户的HTTP POST请求
* @param accountId 账户ID
* @param amount 借记金额
*/
@PostMapping("/{accountId}/debit/{amount}")
public void debit(@PathVariable String accountId, @PathVariable double amount) {
accountService.debit(accountId, amount); // 调用AccountService的debit方法
}
/**
* 处理贷记账户的HTTP POST请求
* @param accountId 账户ID
* @param amount 贷记金额
*/
@PostMapping("/{accountId}/credit/{amount}")
public void credit(@PathVariable String accountId, @PathVariable double amount) {
accountService.credit(accountId, amount); // 调用AccountService的credit方法
}
}
AccountDebitedEvent.java
package com.example.accountservice.event;
/**
* 账户借记事件类
*/
publicclass AccountDebitedEvent {
privatedouble amount; // 借记金额
public AccountDebitedEvent(double amount) {
this.amount = amount; // 构造函数初始化借记金额
}
// 获取借记金额的方法
public double getAmount() {
return amount;
}
// 设置借记金额的方法
public void setAmount(double amount) {
this.amount = amount;
}
}
AccountCreditedEvent.java
/**
* 账户贷记事件类
*/
publicclass AccountCreditedEvent {
privatedouble amount; // 贷记金额
public AccountCreditedEvent(double amount) {
this.amount = amount; // 构造函数初始化贷记金额
}
// 获取贷记金额的方法
public double getAmount() {
return amount;
}
// 设置贷记金额的方法
public void setAmount(double amount) {
this.amount = amount;
}
}
TransferEventHandler.java
package com.example.accountservice.handler;
import com.example.accountservice.event.TransferMadeEvent;
import com.example.accountservice.AccountService;
import io.eventuate.tram.events.subscriber.DomainEventEnvelope;
import io.eventuate.tram.events.subscriber.EventHandlerMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 事件处理器类,处理转账完成事件
*/
@Component
publicclass TransferEventHandler {
@Autowired
private AccountService accountService; // 注入AccountService
/**
* 处理转账完成事件的方法
* @param event 包含转账完成事件的对象
*/
@EventHandlerMethod
public void handle(DomainEventEnvelope<TransferMadeEvent> event) {
TransferMadeEvent transferMadeEvent = event.getEvent(); // 获取转账完成事件对象
accountService.credit(transferMadeEvent.getCreditAccountId(), transferMadeEvent.getAmount()); // 调用AccountService的credit方法,增加目标账户的余额
}
}
创建transfer-service
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>transfer-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.eventuate.tram</groupId>
<artifactId>eventuate-tram-spring-jdbc</artifactId>
<version>0.26.0.RELEASE</version><!-- Eventuate Tram JDBC支持 -->
</dependency>
<dependency>
<groupId>io.eventuate.tram</groupId>
<artifactId>eventuate-tram-messaging-rabbitmq</artifactId>
<version>0.26.0.RELEASE</version><!-- Eventuate Tram RabbitMQ消息代理支持 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
server.port=8082
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/transfers?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Hibernate自动建表策略
spring.jpa.hibernate.ddl-auto=update
# RabbitMQ配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
Transfer.java
package com.example.transferservice;
import io.eventuate.tram.events.publisher.DomainEventPublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.Collections;
/**
* 转账服务类
*/
@Service
publicclass TransferService {
@Autowired
private TransferRepository transferRepository; // 转账仓库,用于数据库操作
@Autowired
private DomainEventPublisher domainEventPublisher; // 域事件发布器,用于发布事件
/**
* 执行转账操作
* @param cmd 包含转账命令的对象
*/
@Transactional
public void makeTransfer(MakeTransferCommand cmd) {
Transfer transfer = new Transfer(cmd.getSourceAccountId(), cmd.getTargetAccountId(), cmd.getAmount()); // 创建转账记录
transferRepository.save(transfer); // 保存转账记录到数据库
domainEventPublisher.publish(Transfer.class, transfer.getId(),
Collections.singletonList(new TransferMadeEvent(cmd.getSourceAccountId(), cmd.getTargetAccountId(), cmd.getAmount()))); // 发布转账完成事件
}
}
TransferRepository.java
package com.example.transferservice;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 转账仓库接口,继承自JpaRepository,用于对Transfer实体进行CRUD操作
*/
public interface TransferRepository extends JpaRepository<Transfer, String> {}
TransferServiceApplication.java
package com.example.transferservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot应用启动类
*/
@SpringBootApplication
public class TransferServiceApplication {
public static void main(String[] args) {
SpringApplication.run(TransferServiceApplication.class, args); // 启动Spring Boot应用
}
}
TransferController.java
package com.example.transferservice.controller;
import com.example.transferservice.MakeTransferCommand;
import com.example.transferservice.TransferService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制器类,处理HTTP请求
*/
@RestController
@RequestMapping("/transfers")
publicclass TransferController {
@Autowired
private TransferService transferService; // 注入TransferService
/**
* 处理转账的HTTP POST请求
* @param cmd 包含转账命令的对象
*/
@PostMapping
public void makeTransfer(@RequestBody MakeTransferCommand cmd) {
transferService.makeTransfer(cmd); // 调用TransferService的makeTransfer方法执行转账操作
}
}
MakeTransferCommand.java
package com.example.transferservice.command;
/**
* 转账命令类,包含转账所需的信息
*/
publicclass MakeTransferCommand {
private String sourceAccountId; // 源账户ID
private String targetAccountId; // 目标账户ID
privatedouble amount; // 转账金额
public MakeTransferCommand(String sourceAccountId, String targetAccountId, double amount) {
this.sourceAccountId = sourceAccountId; // 初始化源账户ID
this.targetAccountId = targetAccountId; // 初始化目标账户ID
this.amount = amount; // 初始化转账金额
}
// 获取源账户ID的方法
public String getSourceAccountId() {
return sourceAccountId;
}
// 设置源账户ID的方法
public void setSourceAccountId(String sourceAccountId) {
this.sourceAccountId = sourceAccountId;
}
// 获取目标账户ID的方法
public String getTargetAccountId() {
return targetAccountId;
}
// 设置目标账户ID的方法
public void setTargetAccountId(String targetAccountId) {
this.targetAccountId = targetAccountId;
}
// 获取转账金额的方法
public double getAmount() {
return amount;
}
// 设置转账金额的方法
public void setAmount(double amount) {
this.amount = amount;
}
}
TransferMadeEvent.java
package com.example.transferservice.event;
/**
* 转账完成事件类
*/
publicclass TransferMadeEvent {
private String debitAccountId; // 借记账户ID
private String creditAccountId; // 贷记账户ID
privatedouble amount; // 转账金额
public TransferMadeEvent(String debitAccountId, String creditAccountId, double amount) {
this.debitAccountId = debitAccountId; // 初始化借记账户ID
this.creditAccountId = creditAccountId; // 初始化贷记账户ID
this.amount = amount; // 初始化转账金额
}
// 获取借记账户ID的方法
public String getDebitAccountId() {
return debitAccountId;
}
// 设置借记账户ID的方法
public void setDebitAccountId(String debitAccountId) {
this.debitAccountId = debitAccountId;
}
// 获取贷记账户ID的方法
public String getCreditAccountId() {
return creditAccountId;
}
// 设置贷记账户ID的方法
public void setCreditAccountId(String creditAccountId) {
this.creditAccountId = creditAccountId;
}
// 获取转账金额的方法
public double getAmount() {
return amount;
}
// 设置转账金额的方法
public void setAmount(double amount) {
this.amount = amount;
}
}
AccountEventHandler.java
package com.example.transferservice.handler;
import com.example.transferservice.event.AccountDebitedEvent;
import com.example.transferservice.TransferService;
import io.eventuate.tram.events.subscriber.DomainEventEnvelope;
import io.eventuate.tram.events.subscriber.EventHandlerMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 事件处理器类,处理账户借记事件
*/
@Component
publicclass AccountEventHandler {
@Autowired
private TransferService transferService; // 注入TransferService
/**
* 处理账户借记事件的方法
* @param event 包含账户借记事件的对象
*/
@EventHandlerMethod
public void handle(DomainEventEnvelope<AccountDebitedEvent> event) {
AccountDebitedEvent accountDebitedEvent = event.getEvent(); // 获取账户借记事件对象
// 这里可以添加额外的逻辑,当账户被借记时执行的操作
}
}
测试
curl -X POST http://localhost:8082/transfers -H "Content-Type: application/json" -d '{"sourceAccountId": "account1", "targetAccountId": "account2", "amount": 150}'
测试结果
无返回内容,说明操作成功。
关注我,送Java福利
/**
* 这段代码只有Java开发者才能看得懂!
* 关注我微信公众号之后,
* 发送:"666",
* 即可获得一本由Java大神一手面试经验诚意出品
* 《Java开发者面试百宝书》Pdf电子书
* 福利截止日期为2025年02月28日止
* 手快有手慢没!!!
*/
System.out.println("请关注我的微信公众号:");
System.out.println("Java知识日历");