微服务调用dubbo

服务提供者:通过 @DubboService 把 AccountApi 暴露成 Dubbo 服务,注册到 Nacos。AccountApi 接口是公共模块的接口,不是当前模块的

package com.example.account;

import com.example.api.AccountApi;
import com.example.account.mapper.AccountMapper;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboService;

import java.math.BigDecimal;

/**
 * 账户服务 —— Dubbo provider。
 * <p>
 * 通过 @DubboService 把 AccountApi 暴露成 Dubbo 服务,注册到 Nacos。
 */
@DubboService
@RequiredArgsConstructor
public class AccountApiImpl implements AccountApi {

    private final AccountMapper accountMapper;

    @Override
    public void deduct(Long userId, BigDecimal money) {
        int rows = accountMapper.deduct(userId, money);
        if (rows == 0) {
            // 余额不足,抛异常 → 分支事务标记失败 → 全局事务回滚
            throw new RuntimeException("余额不足,userId=" + userId);
        }
    }
}

消费者:通过 @DubboReference 从注册中心拿到提供者,这里拿到的也是共同模块的AccountApi 

package com.example.order.service;

import com.example.api.AccountApi;
import com.example.api.StorageApi;
import com.example.order.entity.Order;
import com.example.order.feign.NotifyFeignClient;
import com.example.order.mapper.OrderMapper;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

/**
 * <ul>
 *   <li>内部服务(storage/account):走 Dubbo RPC(TCP 二进制,靠 Nacos 发现)</li>
 *   <li>外部服务(notify):走 Feign HTTP(REST,固定 URL 直连第三方)</li>
 * </ul>
 */
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderMapper orderMapper;
    private final NotifyFeignClient notifyFeignClient;

    // Dubbo 引用:内部服务,靠 Nacos 发现 provider,XID 通过 RpcContext 附件透传
    @DubboReference
    private StorageApi storageApi;

    @DubboReference
    private AccountApi accountApi;

    /**
     * 下单:创建订单 + 扣库存 + 扣余额,三步必须一起成功,否则一起回滚。
     * 注意:不需要 @Transactional,@GlobalTransactional 已接管整个链路。
     */
    @GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
    public void createOrder(Order order) {
        // ① 本地分支事务:创建订单(本服务 = RM,操作 seata_order 库)
        orderMapper.insert(order);

        // ② 远程分支事务:扣库存(Dubbo RPC → storage-service,RM)
        storageApi.deduct(order.getProductId(), order.getCount());

        // ③ 远程分支事务:扣余额(Dubbo RPC → account-service,RM)
        accountApi.deduct(order.getUserId(), order.getMoney());

        // ④ 外部通知:Feign HTTP 调用,【不参与】分布式事务
        //    通知服务是第三方,没有 undo_log、无法回滚,所以这里尽力而为。
        notifyFeignClient.send("用户" + order.getUserId() + "下单成功,金额 " + order.getMoney());

        // 四步全部成功 → TM 通知 TC 全局提交
        // ①②③ 任一步抛异常(库存不足/余额不足)→ TM 通知 TC 全局回滚
        // ④ 抛异常不会回滚①②③(Feign 不在事务链路内,这正体现了“内外边界”)
    }
}

公共模块接口

/**
 * 账户服务的 Dubbo 接口(内部 RPC 契约)。
 */
public interface AccountApi {

    /**
     * 扣减余额。
     * @param userId 用户 ID
     * @param money  扣减金额
     */
    void deduct(Long userId, BigDecimal money);
}

public interface StorageApi {

/**
* 扣减库存。
* @param productId 商品 ID
* @param count 扣减数量
*/
void deduct(Long productId, Integer count);
}
 

 

这样内部账户/库存服务就不需要controller层,因为不需要对外提供http接口,提供者和消费者都依赖同一个公共模块接口.

 

posted @ 2026-08-16 11:16  杨吃羊  阅读(3)  评论(0)    收藏  举报