简介
DDD(Domain-Driven Design):领域驱动设计。
Eric Evans “领域驱动设计之父”
DDD不是架构,而是一种方法论(Methodology)微服务架构从一出来就没有很好的理论支撑如何合理的划分服务边界,人们常常为服务要划分多大而争吵不休
分层架构
DDD中 四层架构 表现层,应用层、领域层和基础层
四层中的应用层是对三层架构中领域层进行进一步拆分。但是无论怎么分层,业务逻辑永远在领域层。
三层架构:
表现层(Contrtoller):负责向用户展示信息和接收用户的指令。需要负责处理展示逻辑,比如用户通过我们的系统进行信用卡还款,系统会返回三个状态未申请,处理中,处理完成。表面层需要根据这个状态给用户返回不同的页面,根据这三个不同的状态,向用户展示不同的中文说明。
领域层(Service):负责表达业务逻辑,是整个系统的核心层。比如信用卡还款服务。
持久层(DAO):提供数据查询和存储服务,包括按照状态查询信用卡。
四层架构:
表现层(Resources):同三层架构表现层。
应用层(Application):定义软件要完成的任务,不包含业务逻辑,而是协调,比如受理用户请求的任务。负责非
业务逻辑(批量删除、修改等)
领域层(Domain):同三层架构领域层。
基础层(Infrastucture):为各层提供通用的技术能力。为领域层提供数据和文件存储
项目包结构
基本概念
实体
身份标识(唯一标识):管理实体生命周期,如果没有唯一的身份标识,就无法追踪实体的状态变更,也就无法正确保证实体从创建、更改到消亡的生命过程。
属性
基本属性:通过基本类型定义的属性,如整型、布尔型、字符串类型等等
组合属性:通过自定义类型来定义的属性,比如类别Category,重量(Weight),单价(Price),自定义类型一般是指值类型
领域行为
变更状态的领域行为
自给自足的领域行为
互为协作的领域行为
@Entity
@Table(name = "t_sup_record",indexes = {@Index(name = "idx_t_sup_record1", columnList = "sp_type")}
)
public class SuperviseRecord extends ExBizEntity{@Column(length=10,nullable=false)private String spType;@OneToMany(mappedBy="supRecord",fetch= FetchType.EAGER,cascade= CascadeType.REMOVE,orphanRemoval=true)@OrderBy("createTime")private Set<SupRecordProcess> processes=new LinkedHashSet<>();public SuperviseRecord() {}public String getSpType() {return spType;}public Set<SupRecordProcess> getProcesses() {return processes;}
}
值对象
通常作为实体的属性而存在,在领域建模时,应该优先考虑使用值对象来建模而不是实体对象。因为值对象没有唯一标识,具备不可变性,是线程安全的,不用考虑并发访问带来的问题。值对象比实体更容易维护,更容易测试,更容易优化,也更容易使用。
@Embeddable
public class SuperviseRecordCreateInfo {
@JoinColumn(foreignKey=@ForeignKey(value=ConstraintMode.NO_CONSTRAINT))private SpecialSuperviseInfo specialSupInfo;@OneToOne(cascade=CascadeType.ALL,orphanRemoval=true)@JoinColumn(foreignKey=@ForeignKey(value=ConstraintMode.NO_CONSTRAINT))private NormalSuperviseInfo normalSupInfo;@OneToOne(cascade=CascadeType.ALL,orphanRemoval=true)@JoinColumn(foreignKey=@ForeignKey(value=ConstraintMode.NO_CONSTRAINT))private DisputeRecord disputeRecord;public SuperviseWork getSpWork() {return spWork;}public String getCreateOrgType() {return createOrgType;}public String getSpType() {return spType;}public SpecialSuperviseInfo getSpecialSupInfo() {return specialSupInfo;}public NormalSuperviseInfo getNormalSupInfo() {return normalSupInfo;}public DisputeRecord getDisputeRecord() {return disputeRecord;}public SuperviseRecordCreateInfo() {super();}public SuperviseRecordCreateInfo(SuperviseWork spWork, String createOrgType,SpecialSuperviseInfo specialSupInfo) {super();this.createOrgType = createOrgType;this.spType = SPTYPE_SPECIAL;this.spWork = spWork;this.specialSupInfo = specialSupInfo;}public SuperviseRecordCreateInfo(String createOrgType, SuperviseWork spWork,NormalSuperviseInfo normalSupInfo) {super();this.spType = SPTYPE_NORMAL;this.createOrgType = createOrgType;this.spWork = spWork;this.normalSupInfo = normalSupInfo;}//为 矛盾纠纷 构建public SuperviseRecordCreateInfo(String createOrgType, SuperviseWork spWork,DisputeRecord disputeRecord) {super();this.spType = SPTYPE_DISPUTE;this.createOrgType = createOrgType;this.spWork = spWork;this.disputeRecord = disputeRecord;}}
聚合根
在 Domain-Driven Design Reference 中,Eric Evans 阐释了何谓聚合模式:“将实体和值对象划分为聚合并围绕着聚合定义边界。选择一个实体作为每个聚合的根,并允许外部对象仅能持有聚合根的引用。作为一个整体来定义聚合的属性和不变量(Invariants),并将执行职责(Enforcement Responsibility)赋予聚合根或指定的框架机制。”
在项目中一个实体就是一个聚合根,实体与聚合根没有明显界限
@Entity
@Table(name="T_SUP_RECORD",indexes={@Index(name="idx_T_SUP_RECORD1",columnList="sp_work_id"),@Index(name="idx_T_SUP_RECORD2",columnList="spType")}
)
public class SuperviseRecord extends ExBizEntity {private SuperviseRecordCreateInfo createInfo;//创建信息private SuperviseRecordFinishInfo finishInfo;//办结信息@OneToMany(mappedBy="createInfo.supRecord",fetch=FetchType.EAGER,cascade=CascadeType.REMOVE,orphanRemoval=true)@OrderBy("createTime")private Set<SupRecordProcess> processes=new LinkedHashSet<>();@OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL,orphanRemoval=true)@JoinColumn(name="sup_record_id",nullable=false,foreignKey=@ForeignKey(value=ConstraintMode.NO_CONSTRAINT))@OrderBy("orderNo")private Set<SupRecordProcessLeaderInstruction> leaderInstrs = new LinkedHashSet<>();public SuperviseRecordCreateInfo getCreateInfo() {return createInfo;}public SuperviseRecordFinishInfo getFinishInfo() {return finishInfo;}public Set<SupRecordProcess> getProcesses() {return processes;}public Set<SupRecordProcessLeaderInstruction> getLeaderInstrs() {return leaderInstrs;}public SuperviseRecord() {super();}public SuperviseRecord(OperateInfo operateInfo, SuperviseWorkCategory spCategory, String cmpRecordId,SpecialSupExchCfxflzStatusInfo cfxfzlStatus, String fromSource, SysDataSimpleValObj category) {super();this.setCreateInfo(operateInfo);SpecialSuperviseInfo specialSupInfo=new SpecialSuperviseInfo(cmpRecordId, spCategory,fromSource,category);this.createInfo = new SuperviseRecordCreateInfo(spCategory.getSpWork(), operateInfo.getOperator().getOrgType(),specialSupInfo);this.finishInfo = new SuperviseRecordFinishInfo(spCategory.getSpWork().getSetting().getFinishJudge(),cfxfzlStatus);}public SuperviseRecord(OperateInfo operateInfo, NormalSuperviseInfo normalSupInfo, SuperviseWork spWork) {super();this.setCreateInfo(operateInfo);this.createInfo = new SuperviseRecordCreateInfo(operateInfo.getOperator().getOrgType(), spWork, normalSupInfo);this.finishInfo = new SuperviseRecordFinishInfo();}public SuperviseRecord(OperateInfo operateInfo, DisputeRecord disputeRecord, SuperviseWork spWork) {super();this.setCreateInfo(operateInfo);this.createInfo = new SuperviseRecordCreateInfo(operateInfo.getOperator().getOrgType(), spWork, disputeRecord);this.finishInfo = new SuperviseRecordFinishInfo(spWork.getSetting().getFinishJudge());}/********************************************开始领域业务方法***********************************************/public boolean updateFinishInfo(OperateInfo operateInfo, SuperviseRecordFinishInfo finishInfo) {this.lastUpdateDate = operateInfo.obtainNotNullOperateTime();boolean finishStatusTransfered=this.finishInfo.updateFinishInfo(operateInfo,finishInfo.getCloseCaseStatus(),finishInfo.getCloseCaseStatusDate(),finishInfo.getDefuseStatus(),finishInfo.getDefuseStatusDate(),finishInfo.getDefuseReportStatus(), finishInfo.getDefuseReportStatusDate(),finishInfo.getDefuseAuditStatus(),finishInfo.getDefuseAuditStatusDate());if(!finishStatusTransfered) return false;return true;}public void onBackflow(OperateInfo operateInfo) {this.lastUpdateDate = operateInfo.obtainNotNullOperateTime();SuperviseWork spWork = this.getCreateInfo().getSpWork();this.finishInfo.updateFinishJudge(spWork.getSetting().getFinishJudge());this.finishInfo.resetFinishStatusAfterBackFlow();}public void updateLeaderInstructions(OperateInfo operateInfo, Collection<SupRecordProcessLeaderInstruction> leaderInstrs){List<SupRecordProcessLeaderInstruction> oldLeaderInstructions = this.getPartyIdToLeaderInstructions(operateInfo.obtainOperateOrgId());Map<String,SupRecordProcessLeaderInstruction> existsLI=Optional.ofNullable(oldLeaderInstructions).orElse(new ArrayList<>()).stream().collect(Collectors.toMap(d->d.getId(), d->d,(d1,d2) -> d2));this.leaderInstrs.removeAll(oldLeaderInstructions);if (CollectionUtils.isEmpty(leaderInstrs)) return;for (SupRecordProcessLeaderInstruction lI : leaderInstrs) {SupRecordProcessLeaderInstruction newlI=existsLI.get(lI.getId());if(newlI==null) {newlI=lI;}else {newlI.updateLeaderInstruction(operateInfo,lI.getParty(),lI.getLeader(),lI.getContent(),lI.getOrderNo());}this.leaderInstrs.add(newlI);}}public SupRecordProcess createSupRecordProcessOfPartyPrc(OperateInfo operateInfo,SupRecordProcessSendRel supSendRel) {SupRecordProcess partyPrc=new SupRecordProcess(operateInfo, this, supSendRel.getParty());this.getProcesses().add(partyPrc);return partyPrc;}public boolean hasProcessed() {boolean result = false;for(SupRecordProcess prc:this.processes) {if(prc.getHandleInfo()!=null && prc.getHandleInfo().getProcessDate()!=null) {result=true;break;}for(SupRecordProcessSendRel sr:prc.getSendRels()) {if(sr.isSendedStatus()) {result = true;break;}}}return result;}}
工厂
DDD要求聚合内所有对象保证一致的生命周期,这往往会导致创建逻辑趋于复杂。为了减少调用者的负担,同时也为了约束生命周期,通常都会引入工厂来创建聚合
没有使用,项目中不需要
资源库(Repository)
资源库是对数据访问的一种业务抽象,使其具有业务意义。利用资源库抽象,就可以解耦领域层与外部资源,使领域层变得更为纯粹,能够脱离外部资源而单独存在。
资源库的设计原则:一个聚合对应一个资源库
接口与模型定义在一个包下
public interface SuperviseRecordRepository {SuperviseWorkCategoryParty getSuperviseWorkCategoryPartyById(String id);SuperviseWorkCategory getSuperviseWorkCategoryById(String id);SuperviseWork getSuperviseWorkById(String id);
}
实现定义在Infrastructure包下
@Repository
public class SuperviseRecordRepositoryJpaHibernate extends JpaHibernateRepositoryimplements SuperviseRecordRepository {@Overridepublic SuperviseWorkCategoryParty getSuperviseWorkCategoryPartyById(String id) {return this.getSession().find(SuperviseWorkCategoryParty.class,id);}@Overridepublic SuperviseWorkCategory getSuperviseWorkCategoryById(String id) {return this.getSession().find(SuperviseWorkCategory.class,id);}@Overridepublic SuperviseWork getSuperviseWorkById(String id) {return this.getSession().find(SuperviseWork.class,id);}
}
领域服务(Domain Service)
当存在下面其中的一种情况时,需要考虑引入领域服务
当存在聚合为了控制边界,并不会直接与别的聚合协作。
当业务系统中,有一些领域行为不适合放在任一聚合中,它们要么不需要聚合自身已知携带的数据,或者存在与聚合截然不同的变化方向。
当聚合需要同基础设施进行交互协作。
领域服务的特征
领域行为与状态无关
领域行为需要多个聚合参与协作,目的是使用聚合内的实体和值对象编排业务逻辑
领域行为需要与访问包括数据库在内的外部资源协作
@Service
@Transactional
public class SupRecordDomainService extends JpaBaseQueryService {public SuperviseWork saveSpecialSuperviseWork(Object ...) {//业务逻辑return null;}
}
领域事件(Domain Event)
领域事件是领域专家所关心的发生在领域中的一些事件。
主要用途
保证聚合间的数据一致性
替换批量处理
实现事件源模式
进行限界上下文集成
分类
内部事件:是一个领域模型内部的事件,不在限界上下文间进行共享
外部事件:是对外发布的事件,在多个限界上下文中进行共享
CollectRealtimeDispenseLogsEvent rdDe = new CollectRealtimeDispenseLogsEvent(spWork.getId() + "_update",0, operateInfo, "t_sup_work", spWork.getId(), 1);
DomainEventPublisherFactory.getRegisteredPublisher().publishEvent(rdDe);
public class CollectRealtimeDispenseLogsEvent implements DomainEvent {@ApiModelProperty(value="优先级 数字越大 越优先")private int priority=9;private DataChangeLogDTO log;private ExecutePoint executePoint = ExecutePoint.CURR_THREAD;public int getPriority() {return priority;}public DataChangeLogDTO getLog() {return log;}private String eventId;@Overridepublic String getEventId() {return eventId;}@Overridepublic Date obtainEventTime() {return null;}@Overridepublic AccessTokenUser getOperator() {return null;}@Overridepublic OperateInfo getOperateInfo() {return null;}@Overridepublic Object getEventData() {return null;}@Overridepublic ExecutePoint obtainExecutePoint() {return this.executePoint;}@Overridepublic String getEventType() {return this.getClass().getSimpleName();}public CollectRealtimeDispenseLogsEvent(String id, Integer priority, OperateInfo operateInfo, String u_table,String u_pk_vals, int u_type) {super();String logId=Utils.getUUID("");if(StringUtils.isBlank(id)) id=logId;this.eventId = this.getClass().getSimpleName()+"_"+id;if(priority!=null) this.priority = priority;long u_time=-1;String u_optor_id=null,u_optorg_id=null;if(operateInfo!=null) {u_time=operateInfo.obtainNotNullOperateTime().getTime();u_optor_id=operateInfo.obtainOperatorId();u_optorg_id=operateInfo.obtainOperateOrgId();}else{u_time=new Date().getTime();}this.log = new DataChangeLogDTO(logId, u_table, u_pk_vals, u_type, u_time,u_optor_id, u_optorg_id);}
}
贫血模型
指领域对象里只有get和set方法。
@Entity
@Table(name = "t_sup_work",indexes = {@Index(name = "idx_t_sup_work1", columnList = "sp_type")}
)
public class SuperviseWork extends ExBizEntity {@Column(length = 50)private String spType;@Column(length = 100)private String name;@AttributeOverrides({@AttributeOverride(name = "id", column = @Column(name = "p_sp_work_id", length = 100)),@AttributeOverride(name = "name", column = @Column(name = "p_sp_work_name", length = 100))})private SysDataSimpleValObj pSpWork;@OneToMany(mappedBy = "spWork", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)@OrderBy("orderNo asc")private Set<SuperviseWorkCategoryParty> spCategoryParties = new LinkedHashSet<>();public SuperviseWork() {}public SuperviseWork(String spType, String name, SysDataSimpleValObj pSpWork) {this.spType = spType;this.name = name;this.pSpWork = pSpWork;}public String getSpType() {return spType;}public String getName() {return name;}public SysDataSimpleValObj getpSpWork() {return pSpWork;}public Set<SuperviseWorkCategoryParty> getSpCategoryParties() {return spCategoryParties;}
}
充血模型
大多业务逻辑和持久化放在Domain Object里面,Business Logic只是简单封装部分业务逻辑以及控制事务、权限等,这样层次结构就变成Client->(BusinessFacade)->Business Logic->Domain Object->Data Access Object
@Entity
@Table(name = "t_sup_rd_process",indexes = {@Index(name = "idx_t_sup_rd_process1", columnList = "party_id")}
)
public class SupRecordProcess extends ExBizEntity {@ManyToOne(optional = false)@JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))private SuperviseWork spWork;@ManyToOne(optional = false)@JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))private SuperviseRecord supRecord;@AttributeOverrides({@AttributeOverride(name = "id", column = @Column(name = "party_id", length = 100, nullable = false)),@AttributeOverride(name = "name", column = @Column(name = "party_name", length = 100, nullable = false))})private SysDataSimpleValObj party;@OneToMany(mappedBy = "partyPrc", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE, orphanRemoval = true)@OrderBy("sendTime,createTime")private Set<SupRecordProcessSendRel> parents = new LinkedHashSet<>();//督办记录-承办 上级转办信息@OneToMany(mappedBy = "process", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE, orphanRemoval = true)@OrderBy("orderNo")private Set<SupRecordProcessSendRel> sendRels = new LinkedHashSet<>();//督办记录-承办 转办下级信息private SysDataSimpleValObj acceptor;private boolean accepted;private Date acceptTime;private SysDataSimpleValObj operaeOrg;private SysDataSimpleValObj operateDept;private SysDataSimpleValObj operator;public SupRecordProcess() {}public SuperviseWork getSpWork() {return spWork;}public SuperviseRecord getSupRecord() {return supRecord;}public SysDataSimpleValObj getParty() {return party;}public Set<SupRecordProcessSendRel> getParents() {return parents;}public Set<SupRecordProcessSendRel> getSendRels() {return sendRels;}public SysDataSimpleValObj getAcceptor() {return acceptor;}public boolean isAccepted() {return accepted;}public Date getAcceptTime() {return acceptTime;}public SysDataSimpleValObj getOperaeOrg() {return operaeOrg;}public SysDataSimpleValObj getOperateDept() {return operateDept;}public SysDataSimpleValObj getOperator() {return operator;}//领域方法//签收public void doAccept(OperateInfo operateInfo) {this.accepted = true;this.acceptor = operateInfo.getOperator().getUser();this.acceptTime = new Date();this.appointOperator(operateInfo);if (CollectionUtils.isNotEmpty(this.parents)) {this.parents.stream().forEach(p -> p.doAccept(operateInfo));}}//指定当前经办人public void appointOperator(OperateInfo operateInfo) {if (this.accepted) throw new RuntimeException("当前办理记录已签收,请刷新页面");if (this.operator != null) {this.addAttr("beforeOperator", this.acceptor);if (this.operateDept != null) {this.addAttr("beforeOperateDept", this.operateDept);}if (this.operaeOrg != null) {this.addAttr("beforeOperaeOrg", this.operaeOrg);}}this.operaeOrg = operateInfo.getOperator().obtainOrg();this.operateDept = operateInfo.getOperator().obtainDept();this.operator = operateInfo.getOperator().getUser();}public void updateProcessInfo() {}
}
@Component
public class SuperviseRecordDomainService {//这里才是业务逻辑正在处理的地方法
}
@Service
@Transactional
public class SuperviseRecordServiceImpl implements ISuperviseRecordService {//这里只是将前端传递的参数进行转换调用SuperviseRecordDomainService中的方法
}
读写分离
CQRS
CQRS — Command Query Responsibility Segregation,故名思义是将 command 与 query 分离的一种模式。
CQRS 将系统中的操作分为两类,即「命令」(Command) 与「查询」(Query)。命令则是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。而查询则和字面意思一样,即不会对数据产生变化的操作,只是按照某些条件查找数据。
CQRS 的核心思想是将这两类不同的操作进行分离,然后在两个独立的「服务」中实现。这里的「服务」一般是指两个独立部署的应用。在某些特殊情况下,也可以部署在同一个应用内的不同接口上。
Command 与 Query 对应的数据源也应该是互相独立的,即更新操作在一个数据源,而查询操作在另一个数据源上。
对于项目而已,要看具体实际情况,是否需要按照上面这样操作,对于用于群体少的,比如只有几百人的项目根本不需要分成两个服务,只需要将命令和查询分成两个独立的类即可
@Service
@Transactional
public class SuperviseRecordServiceImpl implements ISuperviseRecordService {@Overridepublic SupProcessEditDTO saveNormalSuperviseRecord(Object ...) {//业务逻辑return null;}
}
@Service
@SuppressWarnings("unchecked")
public class NormalSupRecordQueryServiceImpl extends JpaBaseQueryServiceimplements INormalSupRecordQueryService {@Overridepublic PageResult<SupRecordWithPartyDTO> pageQueryNormalSupRecord(OperateInfo operateInfo, NormalSupRecordQueryVO vo,boolean withExport) {//查询逻辑return null;}