聚合根:聚合中可代表整个业务操作的实体对象,通过它提供对外访问操作,它维护聚合内部的数据一致性,它是聚合中对象的管理者
代码示例:
// 交易
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 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架构,更多JPA实践请参考>>
-
这里的领域建模使用的是贫血模型,结构简单,职责单一,相互隔离性好但缺乏面向对象设计思想,关于领域建模可参考《领域建模的贫血模型与充血模型》
-
domainEvents()为领域事件发布的一种实现,作用是交易对象任何的数据操作都将触发事件的发布,再配合事件订阅实现事件驱动设计模型,当然也可以有别的实现方式
2)领域服务
======
/**
-
Created on 2020/9/7 11:40 上午
-
@author barry
-
Description: 交易服务
*/
public interface TradeService {
/**
-
充值
-
@param tradeRecord
-
@return
*/
TradeRecord recharge(TradeRecord tradeRecord);
/**
-
消费
-
@param tradeRecord
-
@return
*/
TradeRecord consume(TradeRecord tradeRecord);
}
先定义服务接口,接口的定义需要遵循现实业务的操作,切勿以程序逻辑或数据库逻辑来设计定义出增删改查
-
主要的思考方向是交易对象对外可提供哪些服务,这种服务的定义是粗粒度且高内聚的,切勿将某些具体代码实现层面的方法定义出来
-
接口的输入输出参数尽量考虑以对象的形式,充分兼容各种场景变化
-
关于前端需要的复杂查询方法可不在此定义,一般情况下查询并非是一种领域服务且没有数据变化,可单独处理
-
领域服务的实现主要关注逻辑实现,切勿包含技术基础类代码,比如缓存实现,数据库实现,远程调用等
3)基础设施接口
========
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 findAll();
}
-
基础设施接口放在领域层主要的目的是减少领域层对基础设施层的依赖
-
接口的设计是不可暴露实现的技术细节,如不能将拼装的SQL作为参数
4. 应用层实现(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消息发布一般放在事件订阅中
消息订阅:
-
消息订阅是多个微服务间协作解耦的一步实现方式
-
消息体尽量以统一的对象包装进行传递,降低对象异构带来的处理难度
5. 基础设施层(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 findAll() {
return jpaTradeRepository.findAll();
}
}
-
基础设施层是数据的输出向,主要包含数据库、缓存、消息队列、远程访问等的技术实现
-
基础设计层对外隐藏技术实现细节,提供粗粒度的数据输出服务
-
数据库操作:领域层传递的是数据对象,在这里可以按数据表的实现方式进行拆分实现
6. 用户接口层(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));
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)
结尾
这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。
由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
qxSq2-1713821835839)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)
[外链图片转存中…(img-KgzTti9x-1713821835839)]
结尾
[外链图片转存中…(img-nsHEU8va-1713821835839)]
这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。
由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!