领域驱动设计(DDD Domain-Drivern-Design)
当下敏捷开发备受各大公司推崇,在需求快速迭代的过程中必然会对代码有所腐坏,增加维护的难度。微服务的大环境下,我们就需要一个更有套路的设计方式,DDD就是其中之一。
演进
注:
DDD和MVC并非互斥的,在DDD项目中,针对一些直接读库的操作。也可以沿用MVC设计,直接从应用层直接调用基础数据层获取。
贫血模式和充血模式
贫血模式:只赋予get、set等方法,单纯的做数据的载体。
优点:将业务逻辑分离出去,降低系统耦合。
缺点:只有属性,没有行为的对象是不具有生命的,不符合OOP的思想,是阉割版本。增加了业务层的复杂度。
@Data
@Table(name = "t_user")
public class UserDDD {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
private String username;
private String password;
}
充血模式:不仅仅包含set、get方法,还提供一系列创建、修改、检查、计算等方法
优点:既有属性,又有行为,是完整的对象,符合OOP的思想。
缺点:在实际运用中难以完全区分业务和对象的行为。
@Data
@Table(name = "t_user")
public class UserDDD {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
private String username;
private String password;
public static void check(OpenUser openUser) {
// do check
return;
}
public static User InitUser(OpenUser openUser){
check(openUser);
User user = new User();
user.setPassword(openUser.getPassword());
user.setUsername(openUser.getUsername());
return user;
}
}
DDD的示例代码
视图层/API层/consumer
// 目录 import com.company.teamname.login 登录微服务
@Slf4j
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private ILoginService loginService;
@RequestMapping("/toLogin")
public ModelAndView ToLogin() {
ModelAndView mv = new ModelAndView();
//存入
mv.setViewName("/login.html");
//返回test.html
return mv;
}
}
应用层-调用多个domain
// 应用层,依赖领域层和基础数据层
// 目录 import com.company.teamname.login.user.service
@Service
public class LoginService {
@Autowired
private IUserImpl userImpl;
@Autowired
private IRoleImpl roleImpl;
public void createUser(int id) {
UserContext context = UserContext.initContext(id);
userImpl.getRoleIdAndUpdateLoginTime(context);
RoleContext roleContext = RoleContext.initContext(context.GetRoleId());
roleContext.checkPermission(roleContext);
}
}
领域界限上下文
// DTO 用户上下问
// 目录 import com.company.teamname.login.user 登录微服务-用户模块上下文
@Data
class UserContext {
private int id;
private int roleId;
public static UserContext initContext(int id) {
UserContext context = new UserContext();
context.setId(id);
return context;
}
}
领域层-领域服务
// 应用层,依赖领域层和基础数据层
// 目录 import com.company.teamname.login.user.service
@Service
public class LoginService {
@Autowired
private IuserDaoImpl userDao;
public void getRoleIdAndUpdateLoginTime(UserContext context) {
User user = baseDao.getUserById(context.GetId());
// do some thing
user.refreashLoginTime();
baseDao.update("UserMapper.updateModel", user);
context.setRoleId(user.GetRoleId());
}
}
领域层-领域对象
// 目录 import com.company.teamname.login.user.domain
import com.company.teamname.login.user.domain.entity
// 对外暴漏封装好的操作对象的方法,只包含基础包,不能引入任何注解依赖,以便可以移植到任何项目中都不报错
// DO
@Data
public class UserDDD {
private String username;
private String password;
private int roleId;
private int loginTime;
public void check(UserContext context) {
// do check
return;
}
public void refreashLoginTime(){
this.loginTime = now();
}
public void setRoleId(UserContext context){
context.SetRoleId(this.roleId);
}
}
Interface UserDao {
}
领域层-基础数据库
// 目录 import com.company.teamname.login.user.repo
// 可以在查询同时实现一些缓存等逻辑
class IuserDaoImpl Impl UserDao {}
领域层-数据防腐
// 目录 import com.company.teamname.login.user.facade 登录业务的用户模块上下文
// 由于UserContext在某些场景下,需要访问外部上下文获取数据,采用防腐层进行转义,防止外部APi变更导致我们跟着批量变更
@Component
class PressAddress {
@Autowired
private addressService IAddressService;
public CityInfo getCity(UserContext context){
City city = addressService.transformCity(xxx);
return buildCityInfo(city);
}
public CityInfo buildCityInfo(City city){
// xxx
}
}