什么是领域驱动?
领域驱动设计 (domain-driver-design) 是有别于MVC开发模式的一种思想,它是面向对象编程的一种表现形式,请记住:领域驱动是一种思想,而不是技术!
领域驱动核心是通过对模型抽象出属性和行为进行开发。
如果没有数据库?
为什么使用领域驱动?
1、建立统一语言,为了团队更好的沟通。
2、通过模型来表达需求,模型是模块化、可扩展、易于维护的。
3、业务实现和技术实现隔离。
4、通过建模达到解耦的目的
领域驱动中的一些概念
1、统一语言
2、领域实体(聚合根)
3、值对象(value-object)
4、模型(失血模型、充血模型、胀血模型)
5、边界上下文
6、领域知识
7、领域事件
层级划分
1、表现层
2、应用服务层
3、领域层(领域实体和领域服务)
4、基础设施层(仓储、工具、rpc、第三方api)
通过一个 用例来展现领域驱动
一个登陆业务:
1、用户(User)输入账号(account)和密码(password)。
2、需要校验account和password正确性。
3、“7天未登陆” 或者 “更换设备”需要手机(phone)验证码(captha)校验。
4、登陆成功后做一些操作(比如:发个推送消息、做一条记录)
public UserLoginInfoRespDto login(UserLoginReqDto userLoginReqDto) {
User user = userService.doLogin(userLoginReqDto);
userRepository.updateUserData(user);
String token = authService.createToken(user.getUserId(), user.getUserData().getDeviceId());
//查询更新后的用户信息
UserLoginInfoRespDto userLoginInfoRespDto = userAssembler.assemblerUserLoginInfo(
userService.getWithFormsAndNotNull(user.getUserId())
);
userLoginInfoRespDto.setLoginToken(token);
domainEventPublisher.publish(new UpdateResourcesEvent(this,
Lists.newArrayList(user.getUserData().getUserName()),
userLoginInfoRespDto.getUserId(),
ResourceModeType.LOGIN.getValue()));
return userLoginInfoRespDto;
}
public User doLogin(UserLoginReqDto loginReqDto) {
String userName = loginReqDto.getUserName();
String deviceId = loginReqDto.getDeviceId();
String captcha = loginReqDto.getCaptcha();
String regChatId = baiduAIApiService.faceQuery(userName);
if (StringUtil.isEmpty(regChatId)) {
throw new BusinessException(ResponseStatus.USER_IS_NOT_REG);
}
User regUser = Optional.ofNullable(userRepository.getByChatId(regChatId))
.orElseThrow(() -> new BusinessException(ResponseStatus.USER_IS_NOT_REG));
regUser.validateNotLoginForLongTime();
regUser.validateChangeDevice(deviceId,captcha);
regUser.updateLoginData(loginReqDto);
return regUser;
}
让你的getter/setter丰满起来
领域驱动中,模型的getter/setter是可以具有逻辑的
public List<UserForm> getUserForms() {
if (this.userForms == null) {
if (this.userId == null) {
this.userForms = new ArrayList<>();
} else {
this.userForms = SpringBeanUtils.getBean(IUserRepository.class)
.getForms(this.userId);
}
}
return this.userForms;
}
让应用层参与协调
应用层是很薄的一层,不包含任何逻辑,仅用于协调和发布命令
public Object prePay(PayReqDto payReqDto) {
Fund fund = new Fund(payReqDto);
payBizFactory.handleBiz(fund);
Object prePayResult = payTypeFactory.prePay(fund);
//保存日志时保存为负数
fund.setUserFund(-fund.getUserFund());
fundRepository.add(fund);
return prePayResult;
}
领域实体和领域服务
整个domain层都是逻辑的封装,domain是独立的,不能被其他层侵入,持久化通过接口隔离。
何时使用领域服务?
- 领域行为需要多个领域实体参与协作
- 领域行为与状态无关
- 领域行为需要与外部资源(尤其是DB)协作
面向资源库
把数据库(文件、nosql或者是其他持久化库)看作是一个集合,领域实体是其中的一个元素,整体持久化
@Override
public void add(User user) {
UserDataEntity dataEntity = userConverter.convertUserDataEntityFromUser(user);
userDataJpaRepo.save(dataEntity);
user.setUserId(dataEntity.getUserId());
UserInfoEntity infoEntity = userConverter.convertUserInfoFromUser(user);
userInfoJpaRepo.save(infoEntity);
UserAppendEntity appendEntity = new UserAppendEntity();
appendEntity.setUserId(user.getUserId());
appendEntity.setPhone(infoEntity.getPhone());
if (dataEntity.getRegType() == RegisterType.WX.getValue()) {
appendEntity.setPhone(dataEntity.getUserName());
}
userAppendJpaRepo.save(appendEntity);
UserEngagementAwardEntity awardEntity = userConverter.convertEngagementAwardFromUser(user);
userEngagementAwardJpaRepo.save(awardEntity);
}
领域层隔离带来的问题
领域对象展示和持久化需要进行转换,Assembler 和 Translater
通过测试来快速验证模型
@Test
public void testLogin(){
User user = userService.getWithFormsAndNotNull(68L);
user.validateNotLoginForLongTime();
}
领域驱动是银弹吗?
如果你只是做一个简单的CRUD系统,我并不推荐。用这个思想开发起来并不快,而且具有一定的门槛。
不要生搬硬套其中的概念,灵活运用。
领域驱动的乐趣在于领域划分、建模,应对复杂业务变化。
题外话
设计模式、微服务划分