代码设计

DDD 分层架构典型目录结构

.
├── pom.xml
└── src
    ├── main
       ├── java
          └── fun
              └── barryhome
                  └── ddd
                      ├── WalletApplication.java
                      ├── application
                         ├── TradeEventProcessor.java
                         ├── TradeMQReceiver.java
                         └── TradeManager.java
                      ├── constant
                         └── MessageConstant.java
                      ├── controller
                         ├── TradeController.java
                         ├── WalletController.java
                         └── dto
                             └── TradeDTO.java
                      ├── domain
                         ├── TradeService.java
                         ├── TradeServiceImpl.java
                         ├── enums
                            ├── InOutFlag.java
                            ├── TradeStatus.java
                            ├── TradeType.java
                            └── WalletStatus.java
                         ├── event
                            └── TradeEvent.java
                         ├── model
                            ├── BaseEntity.java
                            ├── TradeRecord.java
                            └── Wallet.java
                         └── repository
                             ├── TradeRepository.java
                             └── WalletRepository.java
                      └── infrastructure
                          ├── TradeRepositoryImpl.java
                          ├── WalletRepositoryImpl.java
                          ├── cache
                             └── Redis.java
                          ├── client
                             ├── AuthFeignClient.java
                             └── LocalAuthClient.java
                          ├── jpa
                             ├── JpaTradeRepository.java
                             └── JpaWalletRepository.java
                          └── mq
                              └── RabbitMQSender.java
       └── resources
           ├── application.properties
           └── rabbitmq-spring.xml
    └── test
        └── java

领域层实现

领域对象

这里的领域对象包括实体对象、值对象。

  • 实体对象:具有唯一标识,能单独存在且可变化的对象
  • 值对象:不能单独存在或在逻辑层面单独存在无意义,且不可变化的对象
  • 聚合:多个对象的集合,对外是一个整体
  • 聚合根:聚合中可代表整个业务操作的实体对象,通过它提供对外访问操作,它维护聚合内部的数据一致性,它是聚合中对象的管理者
// 交易
public class TradeRecord extends BaseEntity {
    /**
     * 交易号
     */
    @Column(unique = true)
    private String tradeNumber;
    /**
     * 交易金额
     */
    private BigDecimal tradeAmount;
    /**
     * 交易类型
     */
    @Enumerated(EnumType.STRING)
    private TradeType tradeType;
    /**
     * 交易余额
     */
    private BigDecimal balance;
    /**
     * 钱包
     */
    @ManyToOne
    private Wallet wallet;

    /**
     * 交易状态
     */
    @Enumerated(EnumType.STRING)
    private TradeStatus tradeStatus;

  	@DomainEvents
    public List<Object> domainEvents() {
        return Collections.singletonList(new TradeEvent(this));
    }
}

// 钱包
public class Wallet extends BaseEntity {

    /**
     * 钱包ID
     */
    @Id
    private String walletId;
    /**
     * 密码
     */
    private String password;
    /**
     * 状态
     */
    @Enumerated(EnumType.STRING)
    private WalletStatus walletStatus = WalletStatus.AVAILABLE;
    /**
     * 用户Id
     */
    private Integer userId;
    /**
     * 余额
     */
    private BigDecimal balance = BigDecimal.ZERO;

}

从钱包交易例子的系统设计中,钱包的任何操作如:充值、消息等都是通过交易对象驱动钱包余额的变化。交易对象和钱包对象均为实体对象且组成聚合关系,交易对象是钱包交易业务模型的聚合根,代表聚合向外提供调用服务。经过分析交易对象与钱包对象为 1 对多关系(@ManyToOne),这里采用了 JPA 做 ORM 架构,这里的领域建模使用的是贫血模型,结构简单,职责单一,相互隔离性好但缺乏面向对象设计思想。domainEvents()为领域事件发布的一种实现,作用是交易对象任何的数据操作都将触发事件的发布,再配合事件订阅实现事件驱动设计模型。

领域服务

public interface TradeService {
    /**
     * 充值
     *
     * @param tradeRecord
     * @return
     */
    TradeRecord recharge(TradeRecord tradeRecord);

    /**
     * 消费
     *
     * @param tradeRecord
     * @return
     */
    TradeRecord consume(TradeRecord tradeRecord);
}

先定义服务接口,接口的定义需要遵循现实业务的操作,切勿以程序逻辑或数据库逻辑来设计定义出增删改查。主要的思考方向是交易对象对外可提供哪些服务,这种服务的定义是粗粒度且高内聚的,切勿将某些具体代码实现层面的方法定义出来。接口的输入输出参数尽量考虑以对象的形式,充分兼容各种场景变化,关于前端需要的复杂查询方法可不在此定义,一般情况下查询并非是一种领域服务且没有数据变化,可单独处理。领域服务的实现主要关注逻辑实现,切勿包含技术基础类代码,比如缓存实现,数据库实现,远程调用等。

基础设施接口

public interface TradeRepository {
    /**
     * 保存
     * @param tradeRecord
     * @return
     */
    TradeRecord save(TradeRecord tradeRecord);

    /**
     * 查询订单
     * @param tradeNumber
     * @return
     */
    TradeRecord findByTradeNumber(String tradeNumber);

    /**
     * 发送MQ事件消息
     * @param tradeEvent
     */
    void sendMQEvent(TradeEvent tradeEvent);

    /**
     * 获取所有
     * @return
     */
    List<TradeRecord> findAll();
}

基础设施接口放在领域层主要的目的是减少领域层对基础设施层的依赖,接口的设计是不可暴露实现的技术细节,如不能将拼装的 SQL 作为参数。

应用层实现(application)

// 交易服务
@Component
public class TradeManager {

    private final TradeService tradeService;
    public TradeManager(TradeService tradeService) {
        this.tradeService = tradeService;
    }


    // 充值
    @Transactional(rollbackFor = Exception.class)
    public TradeRecord recharge(TradeRecord tradeRecord) {
        return tradeService.recharge(tradeRecord);
    }


     // 消费
    @Transactional(rollbackFor = Exception.class)
    public TradeRecord consume(TradeRecord tradeRecord) {
        return tradeService.consume(tradeRecord);
    }
}

// 交易事件订阅
@Component
public class TradeEventProcessor {

    @Autowired
    private TradeRepository tradeRepository;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "# tradeEvent.tradeStatus.name() == 'SUCCEED'")
    public void TradeSucceed(TradeEvent tradeEvent) {
        tradeRepository.sendMQEvent(tradeEvent);
    }
}

// 交易消息订阅
@Component
public class TradeMQReceiver {

    @RabbitListener(queues = "ddd-trade-succeed")
    public void receiveTradeMessage(TradeEvent tradeEvent){
        System.err.println("========MQ Receiver============");
        System.err.println(tradeEvent);
    }
}
  • 应用服务:应用层是很薄的一层,主要用于调用和组合领域服务,切勿包含任何业务逻辑,可包括少量的流程参数判断,由于可能是多个领域服务组合操作调用,如果存在原子性要求可以增加 @Transactional 事务控制。

  • 事件订阅:事件订阅是进程内多个领域操作协作解耦的一种实现方式,它也是进程内所有后续操作的接入口。它与应用服务的组合操作用途不一样,组合是根据场景需求可增可减,但事件订阅后的操作是相对固化的,主要是满足逻辑的一致性要求。TransactionPhase.AFTER_COMMIT 配置是在前一操作事务完成后再调用,从而减少后续操作对前操作的影响。事件订阅可能会有多个消息主体,为了方便管理最好统一在一个类里处理,MQ 消息发布一般放在事件订阅中。

  • 消息订阅:消息订阅是多个微服务间协作解耦的一步实现方式,消息体尽量以统一的对象包装进行传递,降低对象异构带来的处理难度。

基础设施层(infrastructure)

@Repository
public class TradeRepositoryImpl implements TradeRepository {

    private final JpaTradeRepository jpaTradeRepository;
    private final RabbitMQSender rabbitMQSender;
    private final Redis redis;

    public TradeRepositoryImpl(JpaTradeRepository jpaTradeRepository, RabbitMQSender rabbitMQSender, Redis redis) {
        this.jpaTradeRepository = jpaTradeRepository;
        this.rabbitMQSender = rabbitMQSender;
        this.redis = redis;
    }

    @Override
    public TradeRecord save(TradeRecord tradeRecord) {
        return jpaTradeRepository.save(tradeRecord);
    }

    /**
     * 查询订单
     */
    @Override
    public TradeRecord findByTradeNumber(String tradeNumber) {
        TradeRecord tradeRecord = redis.getTrade(tradeNumber);
        if (tradeRecord == null){
            tradeRecord = jpaTradeRepository.findFirstByTradeNumber(tradeNumber);
            // 缓存
            redis.cacheTrade(tradeRecord);
        }

        return tradeRecord;
    }

    /**
     * 发送事件消息
     * @param tradeEvent
     */
    @Override
    public void sendMQEvent(TradeEvent tradeEvent) {
        // 发送消息
        rabbitMQSender.sendMQTradeEvent(tradeEvent);
    }

    /**
     * 获取所有
     */
    @Override
    public List<TradeRecord> findAll() {
        return jpaTradeRepository.findAll();
    }
}

基础设施层是数据的输出向,主要包含数据库、缓存、消息队列、远程访问等的技术实现。基础设计层对外隐藏技术实现细节,提供粗粒度的数据输出服务。数据库操作:领域层传递的是数据对象,在这里可以按数据表的实现方式进行拆分实现

用户接口层(controller)

@RequestMapping("/trade")
@RestController
public class TradeController {

    @Autowired
    private TradeManager tradeManager;

    @Autowired
    private TradeRepository tradeRepository;

    @PostMapping(path = "/recharge")
    public TradeDTO recharge(@RequestBody TradeDTO tradeDTO) {
        return TradeDTO.toDto(tradeManager.recharge(tradeDTO.toEntity()));
    }

    @PostMapping(path = "/consume")
    public TradeDTO consume(@RequestBody TradeDTO tradeDTO) {
        return TradeDTO.toDto(tradeManager.consume(tradeDTO.toEntity()));
    }

    @GetMapping(path = "/{tradeNumber}")
    public TradeDTO findByTradeNumber(@PathVariable("tradeNumber") String tradeNumber){
        return TradeDTO.toDto(tradeRepository.findByTradeNumber(tradeNumber));
    }

}

用户接口层面向终端提供服务支持,可根据不同的场景单独一个模块,面向 Web 提供 http restful,面向服务间 API 调用提供 RPG 支持。为 Web 端提供身份认证和权限验证服务,VO 数据转换;为 API 端提供限流和熔断服务,DTO 数据转换。将数据转换从应用层提到用户接口层更方便不同场景之前的需求变化,同时也保证应用层数据格式的统一性。

下一页