public interface Moveable { void move();}public abstract class Animal implements Moveable {}
public class Bird extends Animal { public void move(){ //try to fly System.out.println("I'am flying"); }}public class Dog extends Animal { public void move(){ //try to swim System.out.println("I'am swimming"); }}
public class PolicyController { private final InsuranceInsureService insuranceInsureService;
/** * 投保出单 * @param request * @return 保单 ID */ public String issuePolicy(IssuePolicyRequest request){ return insuranceInsureService.issuePolicy(request); }}
其中最外层接口是面向具体业务场景的,可以根据业务发展再进行分包。
pojo 包中定义了应用层用到的各种数据类(上面的 IssuePolicyRequest 就在这里)及其向其他层传播时需要进行类型转换的转化器。
tasks 包中定义了一些定时任务的入口。
注意,在领域编程实践中,会需要非常多的类型转换,我们可以借助一些框架(例如 MapStruct[2])来减少这些类型转换给我们带来的繁琐工作。
2、用例代码
public class InsuranceInsureServiceImpl implements InsuranceInsureService {private final PolicyFactory policyFactory;private final StakeHolderConvertor stakeHolderConvertor;private final PolicyService policyService;/*** 事务控制一般在应用层* 但是需要注意底层存储对事务的支持特性* 底层是分库分表时,可能需要其他手段来保证事务,或者将非核心的操作从事务中剥离(例如数据库 ID 生成)*/(rollbackFor = Exception.class)public String issuePolicy(IssuePolicyRequest request) {Policy policy = policyFactory.createPolicy(request.getProductId(),stakeHolderConvertor.convert(request.getStakeHolders()));//出单流程控制policyService.issue(policy);PolicyIssuedMessage message = new PolicyIssuedMessage();message.setPolicyId(policy.getId());MQPublisher.publish(MQConstants.INSURANCE_TOPIC, MQConstants.POLICY_ISSUED_TAG, message);return policy.getId().toString();}}
这里代码展示的是应用层对用例 2 的处理。
anticorruption 是领域防腐层,是当前领域需要获知其他领域或者外部信息时,对其他领域二方包的封装。防腐层从代码层面来看,可以避免调用外部客户端时,在领域内部进行复杂的参数拼装和结果的转换。
factory 解决了复杂聚合的初始化问题。我们设计好领域模型供外部调用,但如果外部也必须使用如何装配这个对象,则必须知道对象的内部结构。对调用方开发来说这是很不友好的。其次,复杂对象或者聚合当中的领域知识(业务规则)需要得到满足,如果让外部自己装配复杂对象或聚合的话,就会将领域知识泄露到调用方代码中去。需要注意的是,这里主要是把聚合或实体需要的数据填充进来,而不涉及对象的行为。
因此这里工厂的核心作用是从各处拉取初始化聚合或实体所需要的外部数据。
public class PolicyFactory {/*** 产品领域防腐层服务*/private final ProductService productService;/*** 从各种数据来源查询直接能查到的前置数据,填充到 policy 中* @param productId* @param stakeHolders* @return*/public Policy createPolicy(Long productId, List<StakeHolder> stakeHolders) {PolicyProduct product = productService.getById(productId);//其他填充数据,这里调用了聚合自身的静态工厂方法Policy policy = Policy.create(product, stakeHolders);return policy;}}
model 中是领域对象的定义。其中 vo 包中定义了领域内用到的值对象。可以看到这里有PolicyProduct 这样一个保险产品类,在投保领域,我们关注的是和保单相关的某个产品及其快照信息,因此我们在这里定义一个保单保险产品类,防腐层负责把从产品域获得的保险产品信息转换为我们关心的保单保险产品类对象。
按照领域驱动设计的最佳实践,领域对象模型中不允许出现 service、repository 这些用以获取外部信息的东西,它的核心概念是一个完备的实体初始化完成后,它能做什么,或者它经历了什么之后状态会发生怎样的变化。
下面是领域内核心的聚合 Policy 的示例代码。
public class Policy {private Long id;private PolicyProduct product;private List<StakeHolder> stakeHolders;private Date issueTime;/*** 工厂方法* @param product* @param stakeHolders* @return*/public static Policy create(PolicyProduct product, List<StakeHolder> stakeHolders){Policy policy = new Policy();policy.product = product;policy.stakeHolders = stakeHolders;return policy;}/*** 保单出单*/public void issue(Long id) {this.id = id;this.issueTime = new Date();}}
repository 是仓储包,只定义仓储接口,不关心具体实现,具体的实现交由基础设施层负责,体现了依赖倒置的思想。
service 是领域服务,它定义一些不属于领域对象的行为,但是又有必要的操作,比如一些流程控制。
2、用例代码
public class PolicyService {private final InsureUnderwriteService insureUnderwriteService;private final PolicyRepository policyRepository;public void issue(Policy policy) {if(!insureUnderwriteService.underwrite(policy)){throw new BizException("核保失败");}policy.issue(IdGenerator.generate());//保存信息//policyRepository.save(policy);policyRepository.create(policy);}}
这里注意我们注掉了一行 policyRepository.save(policy);,那么为什么要区别 save 和 create 呢?
save 是领域驱动设计中最正确的做法:我的聚合或者实体有变动,仓储不用关心是新建还是更新,帮我保存起来就好了。听上去很美好,但对关系型数据库存储却是很不友好的。因此,在我们的场景里,需要违背一下书上所谓的最佳实践,我们告诉仓储是要新建还是更新,甚至如果是更新的话更新的是哪些列。
另外领域驱动的最佳实践是基于事件驱动的,AxonFramework 对其有完美的实现,应用层发出一个 IssuePolicyCommand 指令,领域层接收该指令,完成保单创建后发出PolicyIssuedEvent,该 event 会被监听并且持久化到 event store 中。这种方式目前看起来在我们这里落地的可能性不大,不做更多介绍。
public class PolicyRepositoryImpl implements PolicyRepository { private final PolicyDAO policyDAO; private final StakeHolderDAO stakeHolderDAO; private final PolicyConvertor policyConvertor; private final StakeHolderConvertor stakeHolderConvertor;
public String save(Policy policy) { throw new UnsupportedOperationException(); }
public String create(Policy policy) { policyDAO.insert(policyConvertor.convert(policy)); stakeHolderDAO.insertBatch(stakeHolderConvertor.convert(policy)); //...其它数据入库 return policy.getId().toString(); }
public void updatePolicyStatus(String newStatus) {
}}
开发者评测局特别节目暨无影评测大赛颁奖典礼
重磅来袭!
阿里云开发者社区重磅评测栏目《开发者评测局》暨无影评测大赛颁奖典礼重磅开播。CSDN TOP 1 博主“处女座程序猿”、清华大学教授卓晴,苏宁消金安全运维总经理顾黄亮等来自开发者、高校、企业的参赛代表嘉宾与无影内部团队展开了深度圆桌论坛,共话“云时代云办公”。点击阅读原文查看完整视频!
![]()