前言
一、DDD是什么


二、架构演进-六边形架构




三、编码实战




ddd-demo/├── ddd-order-demo-api ------ 用户界面层-REST│ ├── src│ │ └── main│ │ ├── java│ │ │ └── cn│ │ │ └── huolala│ │ │ └── demo│ │ │ ├── DemoApiApplication.java ------ starter│ │ │ └── api│ │ │ └── controller ------ 客户端接口│ │ │ ├── UserController.java│ │ │ └── OrderController.java│ │ └── resources│ │ └── application.yml
ddd-demo/├── ddd-order-demo-application ------ 应用服务层,主要做业务编排,协调领域层做业务聚合│ ├── src│ │ └── main│ │ └── java│ │ └── cn│ │ └── huolala│ │ └── demo│ │ └── application│ │ ├── convertor ------ 模型转换器│ │ │ ├── OrderConvertor.java│ │ │ └── UserConvertor.java│ │ ├── pojo│ │ │ ├── cmd ------ 用户界面层写请求参数│ │ │ │ ├── OrderChangeItemCmd.java│ │ │ │ ├── OrderCreateCmd.java│ │ │ │ └── co ------ cmd的子结构│ │ │ │ └── OrderCreateProductItemCO.java│ │ │ └── query ------ 用户界面层读请求参数│ │ │ │ ├── UserQuery.java│ │ │ │ └── qo ------ query的子结构│ │ │ └── response ------ 用户界面层的响应模型│ │ │ ├── OrderResponse.java│ │ │ └── UserResponse.java│ │ └── service ------ 应用服务层service,业务编排│ │ ├── OrderService.java│ │ └── UserService.java
ddd-demo/├── ddd-order-demo-domain ------ 领域层,承载当前限界上下文的所有业务逻辑│ ├── src│ │ ├── main│ │ │ ├── java│ │ │ │ └── cn│ │ │ │ └── huolala│ │ │ │ └── demo│ │ │ │ └── domain│ │ │ │ ├── convertor│ │ │ │ ├── infra ------ 基础设施相关接口│ │ │ │ │ ├── mq ------ 基础设施-消息事件│ │ │ │ │ │ └── OrderMessageProducer.java│ │ │ │ │ ├── remote ------ 基础设施-远程调用│ │ │ │ │ │ ├── IProductRemoteService.java│ │ │ │ │ │ └── IScheduleRemoteService.java│ │ │ │ │ └── repository ------ 基础设施-资源库│ │ │ │ │ ├── IDeliveryAddressRepository.java│ │ │ │ │ ├── IOrderRepository.java│ │ │ │ │ └── IUserRepository.java│ │ │ │ ├── model ------ 领域模型│ │ │ │ │ ├── order ------ 子域-核心域│ │ │ │ │ │ ├── DeliveryInfo.java│ │ │ │ │ │ ├── LockInventoryResponse.java│ │ │ │ │ │ ├── Order.java ------ 实体│ │ │ │ │ │ ├── OrderDomainEvent.java│ │ │ │ │ │ ├── OrderEvent.java│ │ │ │ │ │ ├── OrderEventTypeEnum.java│ │ │ │ │ │ ├── OrderItem.java│ │ │ │ │ │ ├── OrderStateEnum.java│ │ │ │ │ ├── product ------ 子域│ │ │ │ │ │ └── Product.java│ │ │ │ │ └── user ------ 子域│ │ │ │ │ ├── User.java│ │ │ │ │ └── UserStateEnum.java│ │ │ │ └── service ------ 领域服务│ │ │ │ ├── OrderDomainService.java│ │ │ │ └── ProductDomainService.java
ddd-demo/├── ddd-order-demo-infra-remote ------ 基础设施层-远程服务调用;访问外部限界上下文│ ├── pom.xml│ ├── src│ │ └── main│ │ └── java│ │ └── cn│ │ └── huolala│ │ └── demo│ │ └── remote│ │ ├── client ------ 外部服务接口定义(REST)│ │ │ ├── ProductApiClient.java│ │ │ ├── ScheduleApiClient.java│ │ │ ├── request ------ 外部服务调用请求参数│ │ │ │ ├── BaseRemoteRequest.java│ │ │ │ └── LockInventoryRequest.java│ │ │ └── response ------ 外部服务调用响应体│ │ │ ├── BaseRemoteResponse.java│ │ │ └── ResponseMessageEnum.java│ │ ├── convertor ------ 转换器,用于外部请求参数或响应体与领域模型的转换│ │ │ └── OrderConvertor.java│ │ ├── service ------ 防腐层,实现了领域层Remote接口,所有外部服务的调用入口│ │ │ ├── ProductRemoteService.java│ │ │ └── ScheduleRemoteService.java├── ddd-order-demo-infra-repository ------ 基础设施层-资源库;数据模型与持久化相关│ ├── pom.xml│ ├── src│ │ └── main│ │ ├── java│ │ │ └── cn│ │ │ └── huolala│ │ │ └── demo│ │ │ ├── convertor ------ 转换器,用于数据模型和领域模型的转换│ │ │ │ ├── DeliveryAddressConvertor.java│ │ │ │ ├── OrderConvertor.java│ │ │ │ ├── OrderItemConvertor.java│ │ │ ├── entity ------ 数据模型│ │ │ │ ├── DeliveryAddressEntity.java│ │ │ │ ├── OrderEntity.java│ │ │ │ ├── OrderItemEntity.java│ │ │ └── repository ------ 资源库│ │ │ ├── DeliveryAddressRepository.java│ │ │ ├── OrderRepository.java│ │ │ ├── OrderRepositoryES.java│ │ │ └── mapper│ │ │ ├── DeliveryAddressMapper.java│ │ │ ├── OrderItemMapper.java│ │ │ ├── OrderMapper.java

public class OrderController {OrderService orderService;public OrderResponse create(OrderCreateCmd cmd) {return OrderConvertor.convertToResponse(orderService.createOrder(cmd));}}
public Order createOrder(OrderCreateCmd cmd) {String orderNo = UUID.randomUUID().toString();//从资源库获取用户Optional<User> userOptional = userRepository.findById(cmd.getUserId());User user = userOptional.orElseThrow(() -> new DemoBusinessException("用户不存在"));//把参数转换为领域模型,可以定义convertor来进行转换List<OrderItem> orderItemList = makeOrderItems(cmd.getProductItems(), orderNo);//商品域-检查库存,product和OrderItem属于两个域,且使用了外部product服务,所以使用领域服务orderDomainService.checkInventoryAndAssembleOrderItems(orderItemList);//配送地址Optional<DeliveryAddress> deliveryInfoOptional = deliveryAddressRepository.findById(cmd.getDeliveryAddressId());DeliveryAddress deliveryAddress = deliveryInfoOptional.orElseThrow(() -> new DemoBusinessException("配送信息不存在"));//创建订单,最好的方式是使用工厂Order order = Order.create(orderNo, deliveryAddress, orderItemList, user.getUserId());//调度域-锁定库存,用到了远程服务,所以放到了领域服务orderDomainService.lockInventory(order);//创建订单orderRepository.createOrder(order);//发布订单创建事件orderMessageProducer.publish(order, OrderEventTypeEnum.INIT);//返回领域模型,由用户界面层决定转为什么样的模型, 从架构层面限制模型滥用和模型滥转return order;}
/*** 检查库存,组装订单项* @param orderItems* @return*/public void checkInventoryAndAssembleOrderItems(List<OrderItem> orderItems) {if (CollectionUtils.isEmpty(orderItems)) {throw new DemoBusinessException("未选择商品");}//从商品服务获取商品信息List<Long> productIds = orderItems.stream().map(OrderItem::getProductId).collect( Collectors.toList());List<Product> productList = productRemoteService.getProductInfos(productIds);if (CollectionUtils.isEmpty(productList)) {throw new DemoBusinessException("未查询到商品信息");}Map<Long, Product> productMap = productList.stream().collect(Collectors.toMap(Product::getProductId, p -> p));//库存校验for (OrderItem item : orderItems) {Product product = productMap.get(item.getProductId());if (product == null)throw new DemoBusinessException("商品[" + item.getProductName() + "]不存在");if (product.getInventoryCount() < item.getCount())throw new DemoBusinessException("商品[" + product.getProductName() + "]库存不足");//组装订单项信息item.setPrice(product.getPrice());item.setProductName(product.getProductName());}}/*** 锁定库存** @param order*/public void lockInventory(Order order) {Optional<LockInventoryResponse> lockInventoryDTOOptional = scheduleRemoteService.lockInventory(order);LockInventoryResponse lockInventoryResponse = lockInventoryDTOOptional.orElseThrow(() -> new DemoBusinessException("库存锁定失败"));if (lockInventoryResponse.getLockEndTime().before(new Date())) {throw new DemoBusinessException("库存锁定失败");}order.setLockInventoryEndTime(lockInventoryResponse.getLockEndTime());}
//……省略其他方法/*** 创建订单* @param orderNo* @param deliveryAddress* @param orderItemList* @param userId* @return*/public static Order create(String orderNo, DeliveryAddress deliveryAddress, List<OrderItem> orderItemList, Long userId) {Order order = new Order();order.setOrderNo(orderNo);order.setDeliveryAddress(deliveryAddress);order.setOrderItemList(orderItemList);order.setUserId(userId);order.initialize();return order;}//订单状态初始化public void initialize() {this.setStatus(OrderStateEnum.INIT.getValue());this.setOrderTime(new Date());calculateTotalPrice();}public void setTotalPrice(Long totalPrice) {if (Objects.isNull(totalPrice) || totalPrice < 0)throw new DemoBusinessException("Illegal totalPrice");this.totalPrice = totalPrice;}public void setOrderNo(String orderNo) {if (StringUtils.isBlank(orderNo))throw new DemoBusinessException("orderNo can not be null");this.orderNo = orderNo;}//……省略其他方法
public class ProductRemoteService implements IProductRemoteService {ProductApiClient productApiClient;public List<Product> getProductInfos(List<Long> productIds) {BaseRemoteResponse<List<Product>> response = productApiClient.getProductInfos(productIds);if (response == null || response.failed()) {log.error("getProductInfos error,request:{},response:{}", productIds, response);return Collections.emptyList();}return response.getData();}}
public class OrderRepository implements IOrderRepository {OrderMapper orderMapper;OrderItemMapper orderItemMapper;private RedisTemplate<String, Object> redisTemplate;private ElasticsearchTemplate<OrderEntityES, Long> orderESTemplate;public Optional<Order> findById(long orderId) {return Optional.ofNullable(OrderConvertor.convertToDO(elasticsearchTemplate.getById(orderId,OrderEntity.class)));}/*** 创建订单* @param order*/public void createOrder(Order order) {//保存订单OrderEntity orderEntity = OrderConvertor.convertToEntity(order);orderMapper.insert(orderEntity);order.setOrderId(orderEntity.getId());//保存订单项List<OrderItem> orderItemList = order.getOrderItemList();orderItemList.forEach(orderItem -> {orderItem.setOrderId(orderEntity.getId());orderItemMapper.insert(OrderItemConvertor.INSTANCT.convertToEntity(orderItem));});}}
四、货拉拉用户CRM改造实践




public MerchantBasicResponse createVisit(CreateVisitRequest request) {EmployeeInfo info = EmployeeUtils.get();//……省略部分代码// 商户验证,存在才能新增拜访MerchantEntity merchant = merchantBaseValidator.checkMerchantExist(request.getMerchantId());// 只能拜访本人的商户if (!Objects.equals(info.getHllerId(), merchant.getOwnerId())) {log.warn("owner not current user, merchant: {}", merchant);throw new ForceException(ErrorCodeEnum.OWNER_NOT_YOU);}//拜访定位异常判定if (Objects.nonNull(merchant.getLongitude()) && Objects.nonNull(merchant.getLatitude())) {double distance = ForceDistanceUtil.distance(merchant.getLongitude().doubleValue(),merchant.getLatitude().doubleValue(), request.getLongitude().doubleValue(), request.getLatitude().doubleValue());long maxVisitDistance = LaLaConfigClient.getAppLongConfigValue(MAX_VISIT_DISTANCE_KEY, DEFAULT_MAX_VISIT_DISTANCE);log.info("visit distance: {}", distance);// ……省略部分代码}// 张贴海报参数验证VisitValidator.validateActivityType(request);// 物资校验validateMaterialDelivery(request);// 坐标更新if (Objects.nonNull(merchant.getDataType()) && merchant.getDataType() == 2&& Objects.nonNull(merchant.getRelocation()) && Objects.equals(Boolean.FALSE, merchant.getRelocation())) {merchant.setLongitude(request.getLongitude());merchant.setLatitude(request.getLatitude());merchant.setRelocation(true);merchant.setLocation(request.getAddress());merchantDao.updateById(merchant);abnormalVisitPosition = false;}// 创建拜访实体VisitEntity visit = VisitConvert.convertToEntity(request);visit.setRegionId(merchant.getRegionId());visit.setCityId(merchant.getCityId());visit.setMarketId(merchant.getMarketId());visit.setOpName(info.getUserName());//密集拜访LocalDateTime nowTime = LocalDateTime.now();//往前推最大间隔int minVisitCommitInterval = LaLaConfigClient.getAppIntConfigValue(MIN_VISIT_COMMIT_INTERVAL_KEY, DEFAULT_MIN_VISIT_COMMIT_INTERVAL);LocalDateTime deadline = nowTime.minusSeconds(minVisitCommitInterval);List<Visit> latestVisitList = visitDao.getLatestVisitList(info.getAccount(), deadline);//密集拜访判定intensiveVisit = CollUtil.isNotEmpty(latestVisitList);visitDao.insert(visit);// 商户状态校验boolean isBuilding = Objects.equals(MerchantStatusEnum.BUILDING.getStatus(), merchant.getStatus());if (isBuilding) {VisitValidator.validateFirstVisit(request);MerchantEntity entity = MerchantConvert.convertToCompleteEntity(request, visit.getVisitTime());merchantDao.updateById(entity);List<MerchantPictureEntity> list = MerchantConvert.convertToMerchantPictureEntity(request);merchantPictureService.saveBatch(list);} else {MerchantEntity entity = new MerchantEntity();entity.setId(request.getMerchantId());entity.setLastVisitTime(visit.getVisitTime());merchantDao.updateById(entity);}// 商户操作日志List<MerchantOperateLogEntity> logs = new ArrayList<>();//处理拜访质检标签saveInterviewee(request, logs, visit, merchant, abnormalVisitPosition, intensiveVisit);//拜访照片处理List<VisitPictureEntity> pictures = new ArrayList<>();pictures.addAll(convertToVisitPicture(visit.getId(), request.getFacadePictures(), PictureTypeEnum.FACADE));pictures.addAll(convertToVisitPicture(visit.getId(), request.getIndoorPictures(), PictureTypeEnum.INDOOR));if (null != request.getPosterPictures()) {pictures.addAll(convertToVisitPicture(visit.getId(), request.getPosterPictures(), PictureTypeEnum.POSTER));}visitPictureDao.batchInsert(pictures);// 商户建档审批流程if (isBuilding) {//……省略部分代码}// 保存商户操作日志merchantOperateLogService.saveBatch(logs);// 统计新增拜访数MetricMonitor.monitor(MetricEnum.VISIT_CREATED_NUM_EVERY_DAY);// 商户拜访跳转到商户列表页,返回商户变更信息MerchantBasicResponse changeInfo = new MerchantBasicResponse();changeInfo.setMerchantId(merchant.getId());// 商户自动踢公时间changeInfo.setAutoReleaseTime(merchant.getAutoReleaseTime());// 今日已拜访changeInfo.setHasVisitToday(Boolean.TRUE);if (isBuilding) {// 建档中,商户状态变更changeInfo.setStatus(MerchantStatusEnum.APPROVAL.getStatus());// 用车情况changeInfo.setCarUsage(request.getCarUsage());//……省略部分代码}// 拜访计划完成。visitPlanService.completeVisitPlan(LocalDate.now(), info.getAccount(), request.getMerchantId());return changeInfo;}
public MerchantAppResponse createVisit(CreateVisitRequest request) {// 商户私海检查MerchantEntity merchant = merchantDomainService.checkMerchantIsPrivate(request.getMerchantId());// 建档状态boolean isBuilding = Objects.equals(MerchantStatusEnum.BUILDING.getStatus(), merchant.getStatus());// 首次建档校验VisitValidator.validateFirstVisit(merchant, request);// 海报活动校验VisitValidator.validateActivityType(request);// 物资校验materialDomainService.validateMaterialDelivery(request);// 创建拜访VisitEntity visit = VisitConvert.convertToEntity(request);// 保存拜访照片visitDomainService.saveVisit(visit, request);// 记录商户操作日志,批量保存List<MerchantOperateLogEntity> operateLogList = new ArrayList<>();// 保存拜访用户相关信息,并记录日志visitDomainService.saveVisitUserInfo(request, visit, merchant, operateLogList);// 装配拜访物资发放信息,并记录日志List<VisitUserMaterialEntity> visitUserMaterialList = visitDomainService.assembleVisitMaterialsAndRecordLog(request, visit.getId(), merchant, operateLogList);// 处理物资发放materialDomainService.handleVisitMaterialDelivery(visitUserMaterialList, visit);//更新拜访后的商户信息merchantDomainService.updateMerchantInfoWithVisitAndRecordLog(merchant, visit, request, operateLogList);// 拜访时间List<Long> userIdList = request.getInterviewees().stream().map(IntervieweeVO::getUserId).collect(Collectors.toList());userRepository.updateLastVisitTime(userIdList, request.getVisitTime());// 构造responseMerchantAppResponse response = MerchantAppResponse.createBaseResponse(merchant.getId(),merchant.getAutoReleaseTime(), true);// 保存商户操作日志merchantOperateLogRepository.saveBatch(operateLogList);// 拜访计划visitPlanRepository.completeVisitPlan(LocalDate.now(), EmployeeUtils.get().getAccount(), request.getMerchantId());// 埋点MetricMonitor.monitor(MetricEnum.VISIT_CREATED_NUM_EVERY_DAY);return response;}



五、总结
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721