DDD架构思想专栏二《领域层的决策设计思想详解》

如果不了解DDD基本概念的读者可以去看这篇文章,传送门:DDD架构思想专栏一《初识领域驱动设计DDD落地》-CSDN博客

前言介绍

在上一章节介绍了领域驱动设计的基本概念以及按照领域驱动设计的思想进行代码分层,但是仅仅只是从一个简单的分层结构上依然没法理解DDD以及如何去开发这样的微服务。另外往往按照这样分层后依然感觉和MVC也没有什么差别,也没有感受到带来什么非常大的好处。那么问题出在哪呢?我个人觉得DDD学起来更像是一套指导思想,不断的将学习者引入到领域触发的思维中去,而这恰恰也是最难学习的地方。时而感觉会了,而实际开发中又不对,本来已经拆解清晰了,但怎么又那么像MVC了。甚至怀疑自己,我在干嘛?

无论是DDD、MVC,他们更像是家里三居或者四局的格局,每一种格局方式都是为了更好地实现对应架构下的设计思想。但,不是说给你一个通用的架构模式,你就能开发出干净(高内聚)、整洁(低耦合)、漂亮(模块化)的代码。这就像是你家住三居、他家也住三居,但是你们屋子的舒适情况就一样吗?(再有,你家里会把厕所安在厨房吗?但你的代码是否这么干过,不合理的摆放导致重构延期。)

另外DDD之所以看着简单但又不那么好落地,个人认为很重要就是领域思想,DDD只是指导但是不能把互联网天下每一个业务行为开发都拿出来举例子给你看,每个领域需要设计。所以需要一些领域专家来讨论梳理,将业务形态设计出合理的架构&代码。

实战演示

设计要求

下面我会通过实战来演示一个领域是如何依靠领域层的决策设计思想设计出来的。

本案例通过一个商品下单规则的场景来进行演示DDD领域设计实战;

  1. 假设产品需求业务运行人员可以对不同的商品配置一些规则,这些规则可以满足不同用户类型可以下单不同商品。
  2. 另外一些行为规则是会随着业务发展而增加或者变动的,所以不能写死。
  3. 数据库的PO类不应该被外部服务调用,这也是必须的。如果你开发过很多系统,那么可能已经吃过亏并意识到这个问题。
  4. 按照DDD思想我们尝试需要设计一个规则引擎的服务,通过给外部提供非常简单的接口(application)来获取最终结果。
  5. 通过这样的案例可以很容易的感受到目前的四层架构确实在实现DDD思想上有很多的帮助。

设计思路

那么我来讲一下我的设计思路吧

通过领域驱动设计的思想,从领域知识中提取和划分为一个一个的子领域(核心子域,通用子域,支撑子域),并在子领域上建立模型。那么在技术实现上就需要去支撑这种建模,以使我们的代码模块独立、免污染、易于扩展。

那么我们应该采用怎样的结构来设计项目的分层使各模块独立、免污染、易于扩展呢?我们可以想到开发一个可扩展使用的规则树,由于树的特殊结构我们可以在树的底部或者任意节点对业务逻辑(行为规则)进行扩充。

那么我们应该怎么将整条服务流程拆分成一个规则树呢?

  • 业务执行流程其实就是不断地在已有的小模块中(每个模块中对应一段业务逻辑)中进行选择,将这些选择的小模块合起来就是一条完整的业务流程,也就是规则树的一条遍历路径。因此我们可以通过在树的每一条线上都设置相关的执行条件,如果满足该执行条件则沿着这条线到达下一个子节点,通过判断来达到最终的结果。
  • 定义相关的实体类(注意要为充血模型)按照树形结构我们将定义出来四个类;树、节点、果实、指向线(From-To),用于描述我们的规则行为。
  • 需要实现一个逻辑定义与规则树执行引擎,通过统一的引擎服务来执行我们每次配置好的规则树

如下图就是一个领域层的简单规则树的示例

代码演示

下面仅展示部分核心代码,想要完整代码的请下载绑定资源

application应用层

application/MallRuleService.java | 应用层定义接口服务,也可以适当做简单包装

package com.kjz.application;import com.kjz.domain.rule.model.vo.DecisionMatter;
import com.kjz.domain.rule.model.vo.EngineResult;/*** 商超规则过滤服务;提供规则树决策功能*/
public interface MallRuleService {/*** 决策服务* @param matter 决策物料* @return       决策结果*/EngineResult process(final DecisionMatter matter);}
domain领域层

domain中有两个领域服务;规则树信息领域、规则执行领域,通过合理的抽象化来实现高内聚、低耦合的模块化服务

domain/service/MallRuleServiceImpl.java | 领域层中的service来实现应用层接口

package com.kjz.domain.rule.service;import com.kjz.application.MallRuleService;
import com.kjz.domain.rule.model.vo.DecisionMatter;
import com.kjz.domain.rule.model.vo.EngineResult;
import com.kjz.domain.rule.service.engine.EngineFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** 规则树服务;提供规则规律功能** 1、rule包下只进行规则决策领域的处理* 2、封装决策行为到领域模型中,外部只需要调用和处理结果即可* 3、可以扩展不同的决策引擎进行统一管理**/
@Service("mallRuleService")
public class MallRuleServiceImpl implements MallRuleService {private Logger logger = LoggerFactory.getLogger(MallRuleServiceImpl.class);@Resource(name = "ruleEngineHandle")private EngineFilter ruleEngineHandle;@Overridepublic EngineResult process(DecisionMatter matter) {try {return ruleEngineHandle.process(matter);} catch (Exception e) {logger.error("决策引擎执行失败", e);return new EngineResult(false);}}}

domain/service/logic/LogicFilter.java | 逻辑决策定义

package com.kjz.domain.rule.service.logic;import com.kjz.domain.rule.model.vo.DecisionMatter;
import com.kjz.domain.rule.model.vo.TreeNodeLineInfo;import java.util.List;public interface LogicFilter {/*** 逻辑决策器* @param matterValue          决策值* @param treeNodeLineInfoList 决策节点* @return                     下一个节点Id*/Long filter(String matterValue, List<TreeNodeLineInfo> treeNodeLineInfoList);/*** 获取决策值** @param decisionMatter 决策物料* @return               决策值*/String matterValue(DecisionMatter decisionMatter);}

domain/service/engine/EngineFilter.java | 引擎执行定义

package com.kjz.domain.rule.service.engine;import com.kjz.domain.rule.model.vo.DecisionMatter;
import com.kjz.domain.rule.model.vo.EngineResult;public interface EngineFilter {EngineResult process(final DecisionMatter matter) throws Exception;}
infrastructure基础层

1、实现领域层仓储定义 2、数据库操作为非业务属性的功能操作 3、在仓储实现层进行组合装配DAO&Redis&Cache等

infrastructure/repository/RuleRepository.java

package com.kjz.infrastructure.repository;import com.kjz.domain.rule.model.aggregates.TreeRuleRich;
import com.kjz.domain.rule.repository.IRuleRepository;
import com.kjz.infrastructure.repository.cache.RuleCacheRepository;
import com.kjz.infrastructure.repository.mysql.RuleMysqlRepository;
import org.springframework.stereotype.Repository;import javax.annotation.Resource;@Repository("ruleRepository")
public class RuleRepository implements IRuleRepository {@Resource(name = "ruleMysqlRepository")private RuleMysqlRepository ruleMysqlRepository;@Resource(name = "ruleCacheRepository")private RuleCacheRepository ruleCacheRepository;@Overridepublic TreeRuleRich queryTreeRuleRich(Long treeId) {TreeRuleRich treeRuleRich = ruleCacheRepository.queryTreeRuleRich(treeId);if (null != treeRuleRich) return treeRuleRich;return ruleMysqlRepository.queryTreeRuleRich(treeId);}}
interfaces接口层

1、包装应用接口对外提供api 2、外部传输对象采用DTO类,主要为了避免内部类被污染(不断的迭代的需求会在类中增加很多字段) 3、目前依然是提供的Http服务,如果提供的rpc服务,将需要对外提供可引用jar

interfaces/DDDController.java

package com.kjz.interfaces;import com.alibaba.fastjson.JSON;import com.kjz.application.MallRuleService;
import com.kjz.application.MallTreeService;
import com.kjz.domain.rule.model.vo.DecisionMatter;
import com.kjz.domain.rule.model.vo.EngineResult;
import com.kjz.domain.tree.model.aggregates.TreeCollect;
import com.kjz.interfaces.dto.DecisionMatterDTO;
import com.kjz.interfaces.dto.TreeDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;import javax.annotation.Resource;@Controller
public class DDDController {private Logger logger = LoggerFactory.getLogger(DDDController.class);@Resourceprivate MallTreeService mallTreeService;@Resourceprivate MallRuleService mallRuleService;/*** 测试接口:http://localhost:8080/api/tree/queryTreeSummaryInfo* 请求参数:{"treeId":10001}*/@RequestMapping(path = "/api/tree/queryTreeSummaryInfo", method = RequestMethod.POST)@ResponseBodypublic ResponseEntity queryTreeSummaryInfo(@RequestBody TreeDTO request) {String reqStr = JSON.toJSONString(request);try {logger.info("查询规则树信息{}Begin req:{}", request.getTreeId(), reqStr);TreeCollect treeCollect = mallTreeService.queryTreeSummaryInfo(request.getTreeId());logger.info("查询规则树信息{}End res:{}", request.getTreeId(), JSON.toJSON(treeCollect));return new ResponseEntity<>(treeCollect, HttpStatus.OK);} catch (Exception e) {logger.error("查询规则树信息{}Error req:{}", request.getTreeId(), reqStr, e);return new ResponseEntity<>(e.getMessage(), HttpStatus.OK);}}/*** 测试接口:http://localhost:8080/api/tree/decisionRuleTree* 请求参数:{"treeId":10001,"userId":"fuzhengwei","valMap":{"gender":"man","age":"25"}}*/@RequestMapping(path = "/api/tree/decisionRuleTree", method = RequestMethod.POST)@ResponseBodypublic ResponseEntity decisionRuleTree(@RequestBody DecisionMatterDTO request) {String reqStr = JSON.toJSONString(request);try {logger.info("规则树行为信息决策{}Begin req:{}", request.getTreeId(), reqStr);DecisionMatter decisionMatter = new DecisionMatter();decisionMatter.setTreeId(request.getTreeId());decisionMatter.setUserId(request.getUserId());decisionMatter.setValMap(request.getValMap());EngineResult engineResult = mallRuleService.process(decisionMatter);logger.info("规则树行为信息决策{}End res:{}", request.getTreeId(), JSON.toJSON(engineResult));return new ResponseEntity<>(engineResult, HttpStatus.OK);} catch (Exception e) {logger.error("规则树行为信息决策{}Error req:{}", request.getTreeId(), reqStr, e);return new ResponseEntity<>(e.getMessage(), HttpStatus.OK);}}}

测试验证

查询规则树信息 

测试接口:http://localhost:8080/api/tree/decisionRuleTree 请求参数:{"treeId":10001}

{"treeInfo": {"treeId": 10001,"treeName": "购物分类规则树","treeDesc": "用于分类不同类型用户可购物范围","nodeCount": 7,"lineCount": 6},"treeRulePointList": [{"ruleKey": "userGender","ruleDesc": "用户性别[男/女]"},{"ruleKey": "userAge","ruleDesc": "用户年龄"}]
}

利用postman测试得出的结果如下

规则树行为信息决策 

测试接口:http://localhost:8080/api/tree/decisionRuleTree 请求参数:{"treeId":10001}

{"userId": "fuzhengwei","treeId": 10001,"nodeId": 112,"nodeValue": "果实B","success": true
}

postman测试结果如下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/206077.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Flink系列三】数据流图和任务链计算方式

上文介绍了如何计算并行度和slot的数量&#xff0c;本文介绍Flink代码提交后&#xff0c;如何生成计算的DAG数据流图。 程序和数据流图 所有的Flink程序都是由三部分组成的&#xff1a;Source、Transformation和Sink。Source负责读取数据源&#xff0c;Transformation利用各种…

Remix IDE 快速开始Starknet

文章目录 一、Remix 项目二、基于Web的开发环境Remix 在线 IDE三、Starknet Remix 插件如何使用使用 Remix【重要】通过 Starknet by Example 学习一、Remix 项目 Remix 项目网站 在以太坊合约开发领域,Remix 项目享有很高的声誉,为各个级别的开发人员提供功能丰富的工具集…

JS中深拷贝与浅拷贝

定义 深拷贝&#xff08;Deep Copy&#xff09;和浅拷贝&#xff08;Shallow Copy&#xff09;是在编程中常用的两种对象复制方式。 浅拷贝&#xff08;Shallow Copy&#xff09;&#xff1a; 浅拷贝是创建一个新的对象&#xff0c;将原始对象的属性值复制到新对象中。如果属…

Smart Link和Monitor Link

Smart Link和Monitor Link简介 Smart Link&#xff0c;又叫做备份链路。一个Smart Link由两个接口组成&#xff0c;其中一个接口作为另一个的备份。Smart Link常用于双上行组网&#xff0c;提供可靠高效的备份和快速的切换机制。 Monitor Link是一种接口联动方案&#xff0c;它…

nodejs流

什么是流 stream 流是用于在 Node.js 中处理流数据的抽象接口。 node:stream 模块提供了用于实现流接口的 API。 什么是流数据 流数据是指一组顺序、大量、快速、连续到达的数据序列&#xff0c;一般情况下数据流可被视为一个随时间延续而无限增长的动态数据集合。流数据应用…

【keil备忘录】2. stm32 keil仿真时的时间测量功能

配置仿真器Trace内核时钟为单片机实际的内核时钟&#xff0c;需要勾选Enable设置&#xff0c;设置完成后Enable取消勾选也可以&#xff0c;经测试时钟频率配置仍然生效&#xff0c;此处设置为48MHZ: 时间测量时必须打开register窗口&#xff0c;否则可能不会计数 右下角有计…

第十四章 : Spring Boot 整合spring-session,使用redis共享

第十四章 &#xff1a; Spring Boot 整合spring-session,使用redis共享 前沿 本文重点讲述&#xff1a;spring boot工程中使用spring-session机制进行安全认证&#xff0c;并且通过redis存储session&#xff0c;满足集群部署、分布式系统的session共享。 基于SPringBoot 2.3.2…

[linux运维] 利用zabbix监控linux高危命令并发送告警(基于Zabbix 6)

之前写过一篇是基于zabbix 5.4的实现文章&#xff0c;但是不太详细&#xff0c;最近已经有两个小伙伴在zabbix 6上操作&#xff0c;发现触发器没有str函数&#xff0c;所以更新一下本文&#xff0c;基于zabbix 6 0x01 来看看效果 高危指令出发问题告警&#xff1a; 发出邮件告…

学好操作系统需要的前置知识

1. 态度&#xff1a;不要等一切都准备好了再前行 如果把一切你可能会说&#xff0c;没有这些基础知识&#xff0c;我每看一篇文章&#xff0c;知识就铺天盖地席卷过来&#xff0c;仿佛每一个知识点都准确地打在了自己的盲点上&#xff0c;这该怎么办呢&#xff1f; 我非常能理…

AI助力智慧农业,基于YOLOv8全系列模型【n/s/m/l/x】开发构建不同参数量级的识别系统

智慧农业随着数字化信息化浪潮的演变有了新的定义&#xff0c;在前面的系列博文中&#xff0c;我们从一些现实世界里面的所见所想所感进行了很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《自建数据集&#xff0c;基于YOLOv7开发构建农田场景下杂草…

05 JQuery基础入门

文章目录 一、jQuery介绍1. 简介2. 版本介绍3. 相关网站4. HTML引入方式 二、基础语法1. 顶级对象$2. 与DOM对象转化3. 选择器4. 事件5. 动画6. 修改样式7. 修改属性 一、jQuery介绍 1. 简介 jQuery是JavaScript编程语言底层库&#xff0c;它是一个快速&#xff0c;简洁的Jav…

ERPNext SQL 注入漏洞复现

0x01 产品简介 ERPNext 是一套开源的企业资源计划系统。 0x02 漏洞概述 ERPNext 系统frappe.model.db_query.get_list 文件 filters 参数存在 SQL 注入漏洞,攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权…

基于springboot实现的仿天猫商城项目

一、系统架构 前端&#xff1a;jsp | js | css | jquery 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.7 | mysql | maven 二、代码及数据库 三、功能介绍 01. web端-首页 02. web端-商品查询 03. web端-商品详情 04. web端-购物车 05. web端-订单…

集合的几个遍历方法

1. 集合的遍历 1.0 创建集合代码 List<String> strList new ArrayList<>(); strList.add("huawei"); strList.add("xiaomi"); strList.add("tencent"); strList.add("google"); strList.add("baidu");1.1 fo…

复杂gRPC之go调用go

1. 复杂的gRPC调用 我们使用了一个较为复杂的proto文件&#xff0c;这个文件的功能主要是用来定位的&#xff0c;详细内容可以看代码中的注解 syntax "proto3"; //指定生成的所属的package&#xff0c;方便调用 option go_package "./"; package route…

Redis和MySQL双写一致性实用解析

1、背景 先阐明一下Mysql和Redis的关系&#xff1a;Mysql是数据库&#xff0c;用来持久化数据&#xff0c;一定程度上保证数据的可靠性&#xff1b;Redis是用来当缓存&#xff0c;用来提升数据访问的性能。 关于如何保证Mysql和Redis中的数据一致&#xff08;即缓存一致性问题…

labelme等标注工具/数据增强工具输出JSON文件格式检查脚本

标注的文件太多了&#xff0c;还有用数据增强工具生成了一票的新数据。在转换或使用训练时候会报错&#xff0c;错误原因是json中语法有问题&#xff0c;这样会中断程序运行&#xff0c;调试造成很大困扰。 检查确实最后有问题&#xff0c;多写了一次 写一个脚本&#xff0c;用…

Python-滑雪大冒险【附源码】

滑雪大冒险 《滑雪大冒险》是一款充满趣味性和挑战性的休闲竞技游戏&#xff0c;在游戏中&#xff0c;玩家将扮演一位勇敢的滑雪者&#xff0c;在雪山上展示他们的滑雪技巧&#xff0c;游戏采用2D图形界面&#xff0c;以第三人称视角呈现 运行效果&#xff1a;用方向键及方向键…

flask 数据库迁移可能出现的六大问题,生成requirements文件夹方式,flask项目复写,

今日任务 项目分级显示 — app — — admin 代表 — — auth 代表用户的点赞 评论 登录等等 — — blog 代表blog的网页 首先单独把auth运行出来 第一步 1. 生成requirements文件夹 2.在一个新的虚拟环境里面完成requirements依赖下载 3.完成项目的复写 1. 生成requ…

算术运算(这么简单?进来坐坐?)

先热热身 算术运算&#xff0c;也称为四则运算&#xff0c;包括加法、减法、乘法和除法。此外&#xff0c;算术运算还包括乘方和开方。 在算术中&#xff0c;加减被视为一级运算&#xff0c;乘除被视为二级运算&#xff0c;乘方和开方被视为三级运算。在一道算式中&#xff0c;…