开局一张图
这是一张之前为了让开发人员理解DDD开发框架的maven依赖关系图,整个项目大概包含这么多的module和依赖关系,其中让大家比较疑惑的可能是infrastructure层为什么要依赖domain,这里有一个控制反转,把最不容易发生变化且业务最核心的的部分放在依赖的最顶层。
真实的项目结构如下:
关于父pom和common中的内容就不做介绍了,下面把api、client、domain、infrastructure、app层的代码详细贴一下,以student为例,也仅仅是个分层的示例,实际业务比这个要复杂的多。
api模块
api模块主要对提供对外部服务提供api接口的能力,只是服务和服务之间的api,不包括正常和前端、移动端的api。其中包括接口和dto两部分
DTO
@Getter
@Setter
@ToString
public class DeStudentDTO extends AbstractTenantBizDto<Long, Long> {
/**
* 学生ID
*/
@ApiModelProperty(value = "学生参数ID")
private Long paramId;
/**
* 学生姓名
*/
@ApiModelProperty("学生参数名称")
private String paramName;
/**
* 学生年龄
*/
@ApiModelProperty("学生参数年龄")
private Integer paramAge;
/**
* 学生性别
*/
@ApiModelProperty("学生参数性别")
private String paramGender;
/**
* 职业
*/
@ApiModelProperty("职业")
private String occupation;
/**
* 学校
*/
@ApiModelProperty("学校")
private String school;
/**
* 籍贯
*/
@ApiModelProperty("籍贯")
private String area;
/**
* 家庭详细住址
*/
@ApiModelProperty("详细住址")
private String address;
/**
* 生日
*/
@ApiModelProperty("生日")
private Date birthday;
/**
* 爱好
*/
@ApiModelProperty("爱好")
private String hobby;
/**
* 学习经历
*/
@ApiModelProperty("学习经历")
private List<ExperienceDTO> experiences = new ArrayList<>();
/**
* 附件ids
* 使用英文逗号分割
*/
@ApiModelProperty("附件ids")
private String fileIds;
/**
* 高级搜索
*/
@ApiModelProperty("高级搜索")
private String seniorSearchColumn;
@Override
public Long getId() {
return paramId;
}
@Override
public void setId(Long along) {
setParamId(along);
}
}
Facade
@RequestMapping("/facade/deStudentParam")
public interface DeStudentFacade {
@GetMapping("/getAllStudents")
List<DeStudentResultDTO> getAllStudents();
}
app模块
app模块为应用层,实现对外接口、业务逻辑,编排领域能力
Controller
@Api(value = "学生管理", tags = "学生管理")
@RestController
@Slf4j
@Validated
@RequestMapping("/data/SeStudentParam")
public class DeStudentController {
@Autowired
private DeStudentApplicationService studentApplicationService;
@GetMapping("/page")
public Page<DeStudentResultDTO> selectPage(Page page,DeStudentDTO studentDTO) {
DeStudentBo studentBo = DeStudentAssembler.toBo(studentDTO);
return studentApplicationService.selectPage(page, studentBo)
.map(entity -> DeStudentAssembler.toResultDTO(entity));
}
}
Assembler
public class DeStudentAssembler {
/**
* 将领域实体转换为resultDTO
* @param deStudentBo
* @return
*/
public static DeStudentResultDTO toResultDTO(DeStudentBo deStudentBo){
DeStudentResultDTO dto = DeStudentDTOBOMapper.INSTANCE.toDeStudentResultDTO(deStudentBo);
List<ExperienceResultDTO> experienceResultDTOS = deStudentBo.getExperienceBoList()
.stream().map(entity->{
ExperienceResultDTO resultDTO = DeStudentDTOBOMapper.INSTANCE.toExperienceResultDTO(entity);
if (!ObjectUtils.isEmpty(resultDTO.getPeriod())){
resultDTO.setPeriods(resultDTO.getPeriod().split(","));
}
return resultDTO;
}).collect(Collectors.toList());
dto.setExperiences(experienceResultDTOS);
return dto;
}
/**
* 将DTO转换为领域实体
* @param dto
* @return
*/
public static DeStudentBo toBo(DeStudentDTO dto){
DeStudentBo deStudentBo = DeStudentDTOBOMapper.INSTANCE.toDeStudentBo(dto);
// 教育经历处理
List<ExperienceBo> experienceBoList = dto.getExperiences()
.stream().map(entity->{
ExperienceBo bo = DeStudentDTOBOMapper.INSTANCE.toExperienceBo(entity);
if (entity.getPeriods().length == 2){
bo.setPeriod(entity.getPeriods()[0] + "," + entity.getPeriods()[1]);
}
return bo;
}).collect(Collectors.toList());
deStudentBo.setExperienceBoList(experienceBoList);
// 高级查询处理
String seniorSearchColumn = dto.getSeniorSearchColumn();
if (!StringUtils.isEmpty(seniorSearchColumn)){
// 解析字符串
seniorSearchColumn = StringEscapeUtils.unescapeHtml(seniorSearchColumn);
HashMap seniorSearchMap = JSON.parseObject(seniorSearchColumn, HashMap.class);
deStudentBo.setSeniorSearchMap(seniorSearchMap);
}
return deStudentBo;
}
public static List<DeStudentResultDTO> toResultDTOS(List<DeStudentBo> deStudentBos){
List<DeStudentResultDTO> resultDTOS = deStudentBos.stream().map(e->toResultDTO(e)).collect(Collectors.toList());
return resultDTOS;
}
}
mapstruct
public interface DeStudentDTOBOMapper {
DeStudentDTOBOMapper INSTANCE = Mappers.getMapper(DeStudentDTOBOMapper.class);
/**
* 学生参数入参dto转换入参的bo
* @return DeStudentBo
*/
DeStudentBo toDeStudentBo(DeStudentDTO deStudentDTO);
/**
* 学生参数 返回的BO转成返回的DTO
* @param deStudentBo
* @return DeStudentResultDTO
*/
DeStudentResultDTO toDeStudentResultDTO(DeStudentBo deStudentBo);
/**
* 学生参数实体集合转换成dto集合
* @param deStudentBoList
* @return 学生参数DeStudentResultDTO的集合
*/
List<DeStudentResultDTO> toDeStudentResultDTOList(List<DeStudentBo> deStudentBoList);
ExperienceBo toExperienceBo(ExperienceDTO experienceDTO);
ExperienceResultDTO toExperienceResultDTO(ExperienceBo experienceBo);
}
ApplicationService
@Service
public class CallslipApplicationService {
private final CallslipDomainService callslipDomainService;
public CallslipApplicationService(CallslipDomainService callslipDomainService) {
this.callslipDomainService = callslipDomainService;
}
public Long insertCallslip(CallslipBo callslipBo){
return callslipDomainService.insertCallslip(callslipBo);
}
public boolean delCallslip(Long id){
return callslipDomainService.delCallslip(id);
}
public boolean delCallslipByIds(List<Long> ids){
return callslipDomainService.delCallslipByIds(ids);
}
public boolean updateCallslip(CallslipBo callslipBo){
return callslipDomainService.updateCallslip(callslipBo);
}
public CallslipBo getCallslipById(Long id){
return callslipDomainService.getCallslipById(id);
}
public Page<CallslipBo> selectPage(Page page, CallslipBo callslipBo){
return callslipDomainService.selectPage(page,callslipBo);
}
public Page<CallslipBo> selectPageByIds(Page page, CallslipBo callslipBo){
return callslipDomainService.selectPageByIds(page,callslipBo);
}
}
domain模块
domain模块为领域层,提供最核心的领域能力
Bo
@Data
@ExcelTarget("deStudentBo")
public class DeStudentBo extends AbstractTenantBizBo<Long, String, Long> {
/**
* 学生ID
*/
private Long paramId;
/**
* 学生姓名
*/
@Excel(name = "学生姓名",needMerge = true)
private String paramName;
/**
* 学生年龄
*/
@Excel(name = "学生年龄",needMerge = true)
private Integer paramAge;
/**
* 学生性别
*/
@Excel(name = "学生性别",dict = "sex",needMerge = true)
private String paramGender;
/**
* 职业
*/
@Excel(name = "职业",dict = "crudDemoOccupation",needMerge = true)
private String occupation;
/**
* 学校(字典项)
*/
@Excel(name = "学校", dict = "school",needMerge = true)
private String school;
/**
* 籍贯
*/
@Excel(name = "籍贯",needMerge = true)
private String area;
/**
* 家庭详细住址
*/
@Excel(name = "家庭详细住址",needMerge = true)
private String address;
/**
* 生日
*/
@Excel(name = "生日",format = "yyyy-MM-dd",needMerge = true)
private Date birthday;
/**
* 爱好
*/
@Excel(name = "爱好",needMerge = true)
private String hobby;
/**
* 教育经历
*/
@ExcelCollection(name = "教育经历")
List<ExperienceBo> experienceBoList;
/**
* 高级查询
*/
HashMap<String,List<SeniorSearch>> seniorSearchMap;
@Override
public Long getId() {
return paramId;
}
@Override
public void setId(Long along) {
setParamId(along);
}
}
Factory
@Component
public class DeStudentFactory {
/**
* 将领域实体转换为PO
* @param deStudentBo
* @return
*/
public DeStudent createDeStudentPO(DeStudentBo deStudentBo){
DeStudent deStudent = DeStudentBOPOMapper.INSTANCE.toDeStudent(deStudentBo);
List<Experience> experienceList = deStudentBo.getExperienceBoList()
.stream().map(experienceBo -> DeStudentBOPOMapper.INSTANCE.createExperiencePO(experienceBo))
.collect(Collectors.toList());
deStudent.setExperienceList(experienceList);
return deStudent;
}
/**
* 将PO转换为领域实体
* @param deStudent
* @return
*/
public DeStudentBo getDeStudentBo(DeStudent deStudent){
DeStudentBo deStudentBo = DeStudentBOPOMapper.INSTANCE.toDeStudentBo(deStudent);
List<ExperienceBo> experienceBoList = deStudent.getExperienceList()
.stream().map(e->DeStudentBOPOMapper.INSTANCE.getExperienceBo(e))
.collect(Collectors.toList());
deStudentBo.setExperienceBoList(experienceBoList);
return deStudentBo;
}
/**
* 将PO集合转换为领域实体集合
* @param deStudents
* @return 领域实体DeStudentBo集合
*/
public List<DeStudentBo> getDeStudentBoList(List<DeStudent> deStudents){
List<DeStudentBo> deStudentBos = deStudents.stream().map(e->getDeStudentBo(e))
.collect(Collectors.toList());
return deStudentBos;
}
}
mapstruct
public interface CallSlipBOPOMapper {
CallSlipBOPOMapper instance = Mappers.getMapper(CallSlipBOPOMapper.class);
Callslip toCallslip(CallslipBo callslipBo);
CallslipBo toCallslipBo(Callslip callslip);
List<CallslipBo> toCallslipBoList(List<Callslip> callslips);
}
Respository
public interface CallslipRespository {
Long insertCallslip(Callslip callslip);
boolean delCallslip(Long id);
boolean updateCallslip(Callslip callslip);
Callslip getCallslipById(Long id);
Page<Callslip> selectPage(Page page,Callslip callslip);
Page<Callslip> selectPageByIds(Page page, Callslip callslip, List<Long> ids);
boolean delCallslipByIds(List<Long> ids);
default boolean retBool(Integer result) {
return null != result && result >= 1;
}
}
Po
@TableName("student")
@Data
public class DeStudent extends AbstractTenantBizEntity<Long, String, Long> {
/**
* 学生id
*/
@TableId(value = "id",type= IdType.ID_WORKER)
private Long paramId;
/**
* 学生姓名
*/
@TableField("name")
private String paramName;
/**
* 年龄
*/
@TableField("age")
private Integer paramAge;
/**
* 性别
*/
@TableField("gender")
private String paramGender;
/**
* 职业
*/
@TableField("occupation")
private String occupation;
/**
* 学校
*/
@TableField("school")
private String school;
/**
* 籍贯
*/
@TableField("area")
private String area;
/**
* 家庭详细住址
*/
@TableField("address")
private String address;
/**
* 生日
*/
@TableField("birthday")
private Date birthday;
/**
* 爱好
*/
@TableField("hobby")
private String hobby;
/**
* 学习经历
*/
@TableField(value = "experiences",exist = false)
private List<Experience> experienceList = new ArrayList<>();
@Override
public Long getId() {
return paramId;
}
@Override
public void setId(Long aLong) {
this.paramId = aLong;
}
}
DomainService
/**
* 领域服务,通常是跨实体(单聚合根)使用,但不可跨领域
*/
@Service
@Slf4j
public class DeStudentDomainService {
private final DeStudentRespository deStudentRespository;
private final DeStudentFactory deStudentFactory;
public DeStudentDomainService(DeStudentRespository deStudentRespository, DeStudentFactory deStudentFactory) {
this.deStudentRespository = deStudentRespository;
this.deStudentFactory = deStudentFactory;
}
/**
* 增加学生
*/
public long addStudent(DeStudentBo studentBo){
return deStudentRespository.insertStudent(deStudentFactory.createDeStudentPO(studentBo));
}
/**
* 删除学生
*/
public boolean delStudentById(Long sid){
return deStudentRespository.delStudentById(sid);
}
/**
* 批量删除学生
* @param ids
* @return
*/
public boolean delStudentByIds(List<Long> ids){
return deStudentRespository.delStudentByIds(ids);
}
/**
* 更新学生信息
*/
public boolean updateStudentById(DeStudentBo studentBo){
return deStudentRespository.updateStudentById(deStudentFactory.createDeStudentPO(studentBo));
}
/**
* 查询所有学生
*/
public List<DeStudentBo> getAllStudents(){
return deStudentFactory.getDeStudentBoList(deStudentRespository.getAllStudents());
}
/**
* 根据id查找学生
*/
public DeStudentBo getStudentById(Long sid){
return deStudentFactory.getDeStudentBo(deStudentRespository.getStudentById(sid));
}
public List<DeStudentBo> queryStudents(DeStudentBo studentBo){
return deStudentFactory.getDeStudentBoList(deStudentRespository.queryStudents(deStudentFactory.createDeStudentPO(studentBo)));
}
/**
* 分页查询
* @param page
* @param studentBo
* @return
*/
public Page<DeStudentBo> selectPage(Page page, DeStudentBo studentBo) {
return deStudentRespository.selectPage(page,deStudentFactory.createDeStudentPO(studentBo))
.map(entity->deStudentFactory.getDeStudentBo(entity));
}
}
infrastructure模块
infrastructure模块为基础设施层,主要是实现领域层定义的事件接口,通常会做操作数据库、mq等操作。
respository.impl
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class CallslipRespositoryImpl implements CallslipRespository {
private final CallslipMapper callslipMapper;
public CallslipRespositoryImpl(CallslipMapper callslipMapper) {
this.callslipMapper = callslipMapper;
}
@Override
public Long insertCallslip(Callslip callslip) {
callslipMapper.insert(callslip);
return callslip.getId();
}
@Override
public boolean delCallslip(Long id) {
return retBool(callslipMapper.deleteById(id));
}
@Override
public boolean updateCallslip(Callslip callslip) {
return retBool(callslipMapper.updateById(callslip));
}
@Override
public Callslip getCallslipById(Long id) {
return callslipMapper.selectById(id);
}
@Override
public Page<Callslip> selectPage(Page page, Callslip callslip) {
PageUtil.translateSortPropertyToColumn(page,Callslip.class,null);
return (Page<Callslip>) callslipMapper.selectPage(page,new QueryWrapper<>(callslip));
}
@Override
public Page<Callslip> selectPageByIds(Page page, Callslip callslip, List<Long> ids) {
PageUtil.translateSortPropertyToColumn(page,Callslip.class,null);
QueryWrapper<Callslip> queryWrapper = new QueryWrapper<>(callslip);
queryWrapper.in("id",ids);
return (Page<Callslip>) callslipMapper.selectPage(page,queryWrapper);
}
@Override
public boolean delCallslipByIds(List<Long> ids) {
return retBool(callslipMapper.deleteBatchIds(ids));
}
}
Mapper
public interface DeStudentMapper extends WfBaseMapper<DeStudent> {
}
xml
其实底层操作数据库是用的mybatisplus,这里就不详细贴了,当然,还有很多操作事件的event,有需要的朋友再联系我