架构师成长之路
- 1. 如何构建高质量应用?
- 2. 三大设计原则?
- 3.DDD妙招
- 4. 最终的改造结果
- 5.模型
项目中的“坏”味道
- 可维护性差:大量的第三方模块影响核心代码的稳定性
- 可扩展性差:业务逻辑与数据存储相互依赖,无法复用
- 可测试性差:庞大事务脚本与基础设施强耦合,无法单元测试。
最后的结果:业务发生几次迭代后,这段代码就将成为一个可怕的黑洞。
1. 如何构建高质量应用?
高内聚、低耦合
2. 三大设计原则?
- 单一职责原则:一个类只负责单一职责,另一种理解也就是一个类应该只有一个引起它变化的原因。
- 开放封闭原则: 对扩展开放,对修改关闭。
- 依赖反转原则:程序之间应该只只依赖于抽象接口,而不要依赖于具体实现(java特性-多态体现)。
3.DDD妙招
- 使用充血模型的实体对象,描述核心业务能力。
系统能做什么事情,一目了然。
public class Account {private Long id;private Long accountNumber;private BigDecimal available;public void withdraw() {// 转入操作available = abailable + money;}public void deposit(BigDecimal money) {//转出操作if (available < monrey ) {throws new InsufficientMoneyException();}abailable = available - money;}
}
充血模型(引起实体变化的因素放入实体中) ——> 贫血模型POJO(Martin Fowler 提出)MicroService ——> 贫血失忆症
public interface AccountRepository {// .....
}
public class AccountRepositoryImpl implments AccountRespository {@Autowiredprivate AccountDao accountDAO;@Autowiredprivate AccountBuilder accountBuilder; // 仓库工厂@Overridepublic Account find() {AccountDO accountDO = accountDAO.selectById(id);return accountBuilder.toAccount(accountDO);}@Overridepublic Account find(Long accountNumber) {AccountDO accountDO = accountDAO.selectByAccountNumber(AccountNumber);return accountBuilder.toAccount(accountDO);}@Overridepublic Account save(Account account) {AccountDO accountDO = accountBuilder.fromAccount(account);if (accountDO.getId() != null) {accountDAO.insert(accountDO);} else {accountDAO,update(accountDO);}return accountBuilder.toAccount(accountDO);}
}
- 构建防腐层 隔离外部服务(众人皆醉我独醒)
public interface BusiSafeService{// ....
}public class BusiSafeServiceImp implements public class BusiSafeServiceImp implements BusiSafeService{@Autowiredprivate RiskChkService riskChkService;public Result chekcBusi(Long userId,Long mechantAccount,BigDecimal money) {RiskCode riskCode = riskCheckService.checkPayment(...);if(“0000”.equals(reskCode.getCode())){return Result.SUCCESS; }return Result.REJECT;}}
}
防腐层
隔离
第三方组件(摆脱技术框架限制 提供无限可能)
// 值对象,只有属性,没有任何方法
public class AuditMessage {private Long userId;private Long clientAccount;private Long merchantAccount;private BigDecimal money;private Date date;....
}
public interface AuditMessageProducer{// ...
}
public class AuditMessageProducerImpl implements AuditMessageProducer {private KafkaTemplate<String,String> kafkaTemplate;public SendResult send(AuditMessage message) {String messgeBody = message.getBody();kafkaTemplate.send("some topic", messageBody);return SendResult.SUCCESS;}
}
- 使用
领域服务
,封装跨实体业务
(保持实体纯粹性,出淤泥而不染)
public interface AccountTransferService {void transfer(Account sourceAccount, Account targetAccount,Money money);
}public class AccountTransferserviceImpl implements AccountTransferService {public void transfer(Account sourceAccount, Account targetAccount, Money money) {sourceAccount.deposit(money);targetAccount.withdraw(money);}
}
4. 最终的改造结果
public class PayServiceImpl extends PayService {private AccountRepository accountRepository;private AuditMessageProducer auditMessageProducer;private BusiSafeService busiSafeService;private AccountTranserSerivce accountTransferService;public Result pay(Account client, Account merchant, Money amount) {// 加载数据Account clientAccount = accountRepository.find(clinet.getId());Account merAccount = accountRepository.find(merchant.getId());//交易检查Result preCheck = busiSafeService.checkBusi(client,merchant,money);if(preCheck != Result.SUCCESS) {return Result.REJECT;}// 转账业务accountTransferServie.transfer(client,merchant,money);// 保存数据;accountRepository.sace(client);accountRepository.save(merchant);//发送审计AuditMessage message = new AuditMessage(client, merchant, money);auditoMessageProducer.send(message);return Result.SUCCESS;}
}
5.模型
- 用户接口层(User Interface Layer)
- 负责与用户交互,包括但不限于web界面、API接口、命令行界面等
- 处理用户的输入并将其转化为系统内部可处理的指令或请求
- 展示从应用层获取的数据和信息给用户
- 应用层(Application Layer)
- 作为领域模型和外部世界的桥梁,它定义了应用程序的服务边界,并负责协调和编排业务逻辑。
- 应用层包含了控制器、服务类等组件,执行业务流程,调用领域对象来完成工作,同时可能设计DTO转换。
- 领域层 (Domain Layer)
- 核心业务逻辑所在的地方,包含领域模型(实体、值对象、聚合根等)以及领域服务。
- 领域层专注于表达和实现业务规则、约束和操作,它是整个架构中最关键的部分,反映的是业务领域的核心概念和知识。
- 基础设施层(Infrastructure Layer)
- 提供通用的技术支持和基础设施服务,如数据库访问(Repository模式)、消息队列、缓存、日志记录、安全框架等。
- 这一层实现持久化仓储(Repository),将领域对象与数据库或其他数据存储方式解耦,确保领域层不受具体技术实现的影响。
注意几点:
- A: 业务写在哪?
Q: 领域层,核心业务是跟随实体展开的,是不掺杂其他业务的逻辑,只关注实体的业务逻辑。
- A: 那领域层包含什么?
Q: 实体、值对象、聚合根、领域服务
- 实体:具有唯一标识符和业务逻辑的对象,在领域模型中代表具有持久
状态
和行为
的业务对象
。- 值对象(Value Object): 代表
不变性
或只读属性
的业务概念,它们没有独立的身份标识,而是通过其属性的值来定义其等价性。例如地址、邮箱等。可能里面会包含校验规则。- 聚合根(Aggregate Root): 在一个聚合内作为整体边界的一部分,负责维护内部
一致性
。它是聚合内的入口点
,对外暴露方法,控制对聚合内部其他实体和值对象的访问。- 领域服务(Domain Service): 当某个业务操作跨越了多个实体或者值对象,并且不自然地属于任何一个实体时,可以创建一个领域服务来封装这类业务逻辑。领域服务与实体和值对象一样,也是领域模型中的组成部分,但它更关注于处理那些不属于特定实体或值对象职责范围内的复杂业务流程。
- A: 操作的事务是在领域层有所体现吗?
在领域驱动设计中,事务管理通常不直接在领域层体现。领域层关注的是业务逻辑的实现,而不是数据访问或持久化技术的具体细节。
- 事务通常是与基础层(Instructure Layer)或应用层(Application Layer)关联的概念,因为这两个层次更接近于数据库或其他数据存储系统,负责执行数据的读写操作以及相关的事务控制。
- 然而,在领域模型的设计过程中,我们确实需要考虑业务一致性边界和事务边界,这通常体现在聚合根的设计上。聚合根作为一组相关对象的统一入口点,它负责维护内部的一致性,并可以在一定程度上隐式地影响到事务的范围。
- 具体说,当一个业务操作涉及到多个实体的时候,如果这些实体都属于同一个聚合根,则他们的操作可以视为一个事务单元来处理。在这种情况下,应用层在调用领域层时,可以将整个聚合的操作包裹在一个数据库事务中,以确保数据一致性。
- 总结来说,虽然领域层不直接处理事务,但它通过聚合根的设计间接影响了事务边界的选择和实现。实际的事务管理代码会存在于应用层或基础设施层中。
- A:如何判断对象之间是否是聚合关系?
部分与整体关系。比如订单聚合,订单包含客户账户、商家账户、商品列表、收货地址。
那么订单不存在了,收货地址也就没什么意义了。所以它们是聚合关系。
- A: 聚合根的作用?
通过聚合与聚合根的设计,极大的简化整个系统内的对象关系图。
- A: 那你说一下这个调用链呗?不知道如何写呀!!!(张了一张疑问脸)
- 从用户接口进来的请求
- 调用了应用层的服务编排类的方法(!!! 注意应用层不是之前mvc模式下的service)
3.应用层可能会有仓库的调用、第三方服务抽象接口的调用、中间件抽象服务方法的调用(依赖倒置)- 在领域层,实体中包含了具体实体的状态和实体的行为(充血模型),以及值对象(只包含属性,无标识)、聚合根(多个相关实体组成的边界)、领域服务(一些不属于某个实体的业务逻辑,但还必须是领域层的逻辑,可以定义一个领域服务来收纳配置)。
- 应用层对领域层进行了服务编排后,实现了模版式地调用,而不关心底层是如何是实现的。即便会出现更迭,只需要新增实现,替换实现即可,无需更改实现。
- A: 梳理一下,这个四层架构?
- 基础设施层(数据库、API、缓存、网关)
- 领域层(聚合、(领域)实体1值对象1 (领域)实体2值对象2、领域服务)
- 应用层(应用服务1、应用服务2)
- 用户接口层(用户界面、Web服务、其他接口)
- A 升华四层架构规范
- 领域层:Domain Layer: 放之四海而皆准的理想。系统的核心,纯粹表达业务能力,不需要任何外部依赖。
- 应用层:Application Layer: 理想与现实。协调领域对象,组织形成业务场景。只依赖于领域层
- 用户层:User Interface Layer:护城河,负责与用户进行交互。解释用户请求,返回用户响应、只引用应用层。
- 基础层:Infrastructure Layer: 业务与数据分离,为领域层提供持久化机制,为其他层提供通用技术能力。
- 聚合与聚合根引来的问题?
一个聚合中包含聚合根和其他实体或者值对象。
那么当对其中一个实体进行统计查询的时候,这时候DDD强调了有一个聚合根作为入口,所以导致了查询的不便。
举个例子:订单聚合,订单聚合中包含订单聚合根、商品、收货地址、等。
如果说想要查询某一天某件商品的销量,那么需要首先查询出订单,在通过订单判断商品,将其累加起来。这样就造成了不必要的麻烦。真的是这样的吗?
其实,DDD推荐的是,也一直说,DDD强调的是对实体属性状态变化去产生的。而报表和查询是没必要这样做的。
- 限界上下文
当把功能放大到各个模块的时候,就会出现领域的边界,这个边界就是限界上下文。
有了限界上下文的划分、单体、微服务、事件驱动这些架构就都只是领域之间不同的协作方式。而领域本身是保持稳定的。