领域驱动设计(Domain Driven Design)有一个官方的sample工程,名为DDDSample。
架构图
接下来,我们用一个学生例子来看整体的架构
业务分析:
假设,有多个学生,学生有成绩和基本信息组成,我们就做一个最基本的案例,查看和新增。
首先,构建最基本的设施层:
common:共通类存放地址
dao:数据库实现接口
po:映射数据库对象层
repository:服务层
package com.hikktn.infrastructure.common;
/**
* <p>
* 返回消息体
* common:共同使用类
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:23
*/
public class ResponseBase {
/**
* 状态码
*/
private int code;
/**
* 消息
*/
private Object obj;
public ResponseBase(int code, Object obj) {
this.code = code;
this.obj = obj;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
package com.hikktn.infrastructure.po;
/**
* <p>
* 学生实体类
* PO:数据库表结构到JAVA的映射类
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:48
*/
public class StudentPo {
/**
* 主键
*/
private int id;
/**
* 学生姓名
*/
private String name;
/**
* 学生性别
*/
private String sex;
/**
* 学生年龄
*/
private int age;
/**
* 学生身高
*/
private double stature;
/**
* 学生平均成绩
*/
private double pjcj;
/**
* 学生语文成绩
*/
private double ywcj;
/**
* 学生数学成绩
*/
private double sxcj;
/**
* 学生英语成绩
*/
private double yycj;
/**
* 兴趣爱好
*/
private String xqah;
/**
* 母亲姓名
*/
private String mqxm;
/**
* 父亲姓名
*/
private String fqxm;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getStature() {
return stature;
}
public void setStature(double stature) {
this.stature = stature;
}
public double getPjcj() {
return pjcj;
}
public void setPjcj(double pjcj) {
this.pjcj = pjcj;
}
public double getYwcj() {
return ywcj;
}
public void setYwcj(double ywcj) {
this.ywcj = ywcj;
}
public double getSxcj() {
return sxcj;
}
public void setSxcj(double sxcj) {
this.sxcj = sxcj;
}
public double getYycj() {
return yycj;
}
public void setYycj(double yycj) {
this.yycj = yycj;
}
public String getXqah() {
return xqah;
}
public void setXqah(String xqah) {
this.xqah = xqah;
}
public String getMqxm() {
return mqxm;
}
public void setMqxm(String mqxm) {
this.mqxm = mqxm;
}
public String getFqxm() {
return fqxm;
}
public void setFqxm(String fqxm) {
this.fqxm = fqxm;
}
}
package com.hikktn.infrastructure.dao;
import com.hikktn.infrastructure.po.StudentPo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
/**
* <p>
* 学生数据访问对象
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 17:32
*/
@Mapper
public interface StudentDao {
/**
* 新增学生的信息
* @param studentPo 学生的基本信息
*/
@Insert("insert ")
void save(StudentPo studentPo);
/**
* 根据id查询学生基本信息
* @param id 标识符
* @return 学生的基本信息
*/
@Select("select ")
StudentPo queryById(int id);
}
package com.hikktn.infrastructure.repository;
import com.hikktn.infrastructure.dao.StudentDao;
import com.hikktn.infrastructure.po.StudentPo;
import com.hikktn.domain.repository.IStudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
/**
* <p>
* 学生服务层实现类(基础CRUD)
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 17:33
*/
public class StudentRepositoryImpl implements IStudentRepository {
@Autowired
private StudentDao studentDao;
@Override
public void save(StudentPo studentPo) {
studentDao.save(studentPo);
}
@Override
public StudentPo queryById(int id) {
return studentDao.queryById(id);
}
}
如果没有复杂的业务,那么到这里就可以进行controller的返回调用:
dto:前端给后端传递的数据
facade:控制层
package com.hikktn.interfaces.dto;
/**
* <p>
* 学生请求参数
* DTO:前端给后端传递的数据
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:28
*/
public class StudentDto {
/**
* 标识符
*/
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
package com.hikktn.interfaces.facade;
import com.hikktn.application.service.IStudentService;
import com.hikktn.domain.repository.IStudentRepository;
import com.hikktn.infrastructure.common.ResponseBase;
import com.hikktn.interfaces.dto.StudentDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* <p>
* 学生控制层
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:02
*/
@Controller
public class StudentController {
/**
* 学生业务对象
*/
@Autowired
private IStudentRepository studentRepository;
/**
* 查询一个学生基本信息
* @param request 学生id
* @return 学生的信息
*/
@RequestMapping("/queryById")
public ResponseBase queryById(@RequestParam("id") StudentDto request) {
return new ResponseBase(HttpStatus.OK.value(), studentRepository.queryById(request.getId()));
}
}
当我们遇到复杂的业务时,比如我有两张表,学生基本信息表,学生成绩表,组成两个不同的对象,但要同时返回给前台,就要用到domain、interfaces层
application下的service:存放复杂接口
domain:
model:对象
aggregates:聚合,后端返回给前端的聚合信息体
vo:后端给前端传递的数据
repository:存放基本接口(CRUD)
service:复杂接口实现层
package com.hikktn.application.service;
import com.hikktn.domain.model.aggregates.StudentEverydayModel;
/**
* <p>
* 应用层用户服务
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:04
*/
public interface IStudentService {
/**
* 根据id查询学生日常
* @param id 标识符
* @return 学生的日常
*/
StudentEverydayModel queryStudentEverydayModelById(int id);
}
package com.hikktn.domain.model.vo;
/**
* <p>
* 学生基本信息
* VO:后端给前端传递的数据
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:35
*/
public class StudentDataVo {
/**
* 主键
*/
private int id;
/**
* 学生姓名
*/
private String name;
/**
* 学生性别
*/
private String sex;
/**
* 学生年龄
*/
private int age;
/**
* 学生身高
*/
private double stature;
/**
* 是否成年(根据年龄进行判断,该字段没有在PO对象中)
*/
private int isCn;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getStature() {
return stature;
}
public void setStature(double stature) {
this.stature = stature;
}
public int getIsCn() {
return isCn;
}
public void setIsCn(int isCn) {
this.isCn = isCn;
}
}
package com.hikktn.domain.model.vo;
/**
* <p>
* 学生成绩的信息
* VO:后端给前端传递的数据
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:36
*/
public class StudentScoreVo {
/**
* 学生平均成绩
*/
private double pjcj;
/**
* 学生语文成绩
*/
private double ywcj;
/**
* 学生数学成绩
*/
private double sxcj;
/**
* 学生英语成绩
*/
private double yycj;
/**
* 是否合格(根据平均成绩进行判断,该字段没有在PO对象中)
*/
private int isHg;
public double getPjcj() {
return pjcj;
}
public void setPjcj(double pjcj) {
this.pjcj = pjcj;
}
public double getYwcj() {
return ywcj;
}
public void setYwcj(double ywcj) {
this.ywcj = ywcj;
}
public double getSxcj() {
return sxcj;
}
public void setSxcj(double sxcj) {
this.sxcj = sxcj;
}
public double getYycj() {
return yycj;
}
public void setYycj(double yycj) {
this.yycj = yycj;
}
public int getIsHg() {
return isHg;
}
public void setIsHg(int isHg) {
this.isHg = isHg;
}
}
package com.hikktn.domain.model.aggregates;
import com.hikktn.domain.model.vo.StudentDataVo;
import com.hikktn.domain.model.vo.StudentScoreVo;
/**
* <p>
* 学生日常
* aggregates:聚合,后端返回给前端的聚合信息体
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 16:10
*/
public class StudentEverydayModel {
/**
* 学生基本信息
*/
private StudentDataVo studentDataVo;
/**
* 学生成绩的信息
*/
private StudentScoreVo studentScoreVo;
public StudentDataVo getStudentDataVo() {
return studentDataVo;
}
public void setStudentDataVo(StudentDataVo studentDataVo) {
this.studentDataVo = studentDataVo;
}
public StudentScoreVo getStudentScoreVo() {
return studentScoreVo;
}
public void setStudentScoreVo(StudentScoreVo studentScoreVo) {
this.studentScoreVo = studentScoreVo;
}
}
package com.hikktn.domain.repository;
import com.hikktn.infrastructure.po.StudentPo;
import org.springframework.stereotype.Repository;
/**
* <p>
* 学生的信息业务处理
* repository:后端传给业务处理的PO数据
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 17:17
*/
@Repository("studentRepository")
public interface IStudentRepository {
/**
* 新增学生的信息
* @param studentPo 学生的基本信息
*/
void save(StudentPo studentPo);
/**
* 根据id查询学生基本信息
* @param id 标识符
* @return 学生的基本信息
*/
StudentPo queryById(int id);
}
package com.hikktn.domain.service;
import com.hikktn.application.service.IStudentService;
import com.hikktn.domain.model.aggregates.StudentEverydayModel;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* <p>
* 应用层实现类(聚合对象)
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 17:04
*/
@Service("studentService")
public class StudentServiceImpl implements IStudentService {
@Resource(name = "iStudentService")
private IStudentService studentService;
@Override
public StudentEverydayModel queryStudentEverydayModelById(int id) {
return studentService.queryStudentEverydayModelById(id);
}
}
最后就是启动类和配置信息
package com.hikktn;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* <p>
* 主方法启动类
* </p>
*
* @author lisonglin
* @version 1.0
* @since 2022/1/24 15:59
*/
@SpringBootApplication
@MapperScan("com.hikktn.infrastructure.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
server:
port: 9000
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123