做者:小傅哥
博客:https://bugstack.cnhtml
沉澱、分享、成長,讓本身和他人都能有所收穫!
在上一章節介紹了領域驅動設計的基本概念以及按照領域驅動設計的思想進行代碼分層,可是僅僅只是從一個簡單的分層結構上依然無法理解DDD以及如何去開發這樣的微服務。另外每每按照這樣分層後依然感受和MVC也沒有什麼差異,也沒有感覺到帶來什麼很是大的好處。那麼問題出在哪呢?我我的以爲DDD學起來更像是一套指導思想,不斷的將學習者引入到領域觸發的思惟中去,而這偏偏也是最難學習的地方。時而感受會了,而實際開發中又不對,原本已經拆解清晰了,但怎麼又那麼像MVC了。甚至懷疑本身,我在幹嗎?java
不管是DDD、MVC,他們更像是家裏三居或者四局的格局,每一種格局方式都是爲了更好的實現對應架構下的設計思想。但,不是說給你一個通用的架構模式,你就能開發出乾淨(高內聚)、整潔(低耦合)、漂亮(模塊化)的代碼。這就像是你家住三居、他家也住三居,可是大家屋子的溫馨狀況就同樣嗎?{再有,你家裏會把廁所安在廚房嗎?但你的代碼是否這麼幹過,不合理的擺放致使重構延期。}node
另外DDD之因此看着簡單但又不那麼好落地,我的認爲很重要就是領域思想,DDD只是指導可是不能把互聯網天下每個業務行爲開發都拿出來舉例子給你看,每一個領域須要設計。因此須要一些領域專家{產品+架構+不是槓精的程序猿}來討論梳理,將業務形態設計出合理的架構&代碼。mysql
本案例經過一個商品下單規則的場景來進行演示DDD;程序員
如圖;DDD分層結構 | 指導設計架構
經過領域驅動設計的思想,從領域知識中提取和劃分爲一個一個的子領域(核心子域,通用子域,支撐子域),並在子領域上創建模型。那麼在技術實現上就須要去支撐這種建模,以使咱們的代碼模塊獨立、免污染、易於擴展。web
在上面咱們提到須要開發一個可擴展使用的規則樹,那麼若是隻是單純的一次性需求,最快的方式是if語句就搞定了。可是爲了使這個領域服務具有良好的使用和擴展性,咱們須要作些拆分,那麼以下;
一、你是否想過系統在過濾過則的時候其實就像執行一棵二叉樹同樣非左即右側,每一條線上都有着執行條件,經過判斷來達到最終的結果。
二、按照樹形結構咱們將定義出來四個類;樹、節點、果實、指向線(From-To),用於描述咱們的規則行爲。
三、再此基礎上須要實現一個邏輯定義與規則樹執行引擎,經過統一的引擎服務來執行咱們每次配置好的規則樹。spring
如圖;領域開發設計服務
itstack-demo-ddd-02 └── src ├── main │ ├── java │ │ └── org.itstack.demo │ │ ├── application │ │ │ ├── MallRuleService.java │ │ │ └── MallTreeService.java │ │ ├── domain │ │ │ ├── rule │ │ │ │ ├── model │ │ │ │ │ ├── aggregates │ │ │ │ │ │ └── UserRichInfo.java │ │ │ │ │ └── vo │ │ │ │ │ ├── DecisionMatter.java │ │ │ │ │ ├── EngineResult.java │ │ │ │ │ ├── TreeNodeInfo.java │ │ │ │ │ ├── TreeNodeLineInfo.java │ │ │ │ │ └── UserSchool.java │ │ │ │ ├── repository │ │ │ │ │ └── IRuleRepository.java │ │ │ │ └── service │ │ │ │ ├── engine │ │ │ │ │ ├── impl │ │ │ │ │ └── EngineFilter.java │ │ │ │ ├── logic │ │ │ │ │ ├── impl │ │ │ │ │ └── LogicFilter.java │ │ │ │ └── MallRuleServiceImpl.java │ │ │ └── tree │ │ │ ├── model │ │ │ │ ├── aggregates │ │ │ │ │ └── TreeCollect.java │ │ │ │ └── vo │ │ │ │ ├── TreeInfo.java │ │ │ │ └── TreeRulePoint.java │ │ │ ├── repository │ │ │ │ └── ITreeRepository.java │ │ │ └── service │ │ │ └── MallTreeServiceImpl.java │ │ ├── infrastructure │ │ │ ├── common │ │ │ │ └── Constants.java │ │ │ ├── dao │ │ │ │ ├── RuleTreeDao.java │ │ │ │ ├── RuleTreeNodeDao.java │ │ │ │ └── RuleTreeNodeLineDao.java │ │ │ ├── po │ │ │ │ ├── RuleTree.java │ │ │ │ ├── RuleTreeConfig.java │ │ │ │ ├── RuleTreeNode.java │ │ │ │ └── RuleTreeNodeLine.java │ │ │ ├── repository │ │ │ │ ├── cache │ │ │ │ │ └── RuleCacheRepository.java │ │ │ │ ├── mysql │ │ │ │ │ ├── RuleMysqlRepository.java │ │ │ │ │ └── TreeMysqlRepository.java │ │ │ │ ├── RuleRepository.java │ │ │ │ └── TreeRepository.java │ │ │ └── util │ │ │ └── CacheUtil.java │ │ ├── interfaces │ │ │ ├── dto │ │ │ │ ├── DecisionMatterDTO.java │ │ │ │ └── TreeDTO.java │ │ │ └── DDDController.java │ │ └── DDDApplication.java │ └── resources │ ├── mybatis │ └── application.yml └── test └── java └── org.itstack.demo.test └── ApiTest.java
演示部分重點代碼塊,完整代碼下載關注公衆號;bugstack蟲洞棧 | 回覆DDD落地sql
application/MallRuleService.java | 應用層定義接口服務,也能夠適當作簡單包裝
/** * 商超規則過濾服務;提供規則樹決策功能 * 微信公衆號:bugstack蟲洞棧 | 專一原創技術專題案例 * 論壇:http://bugstack.cn * Create by 小傅哥 on @2019 */ public interface MallRuleService { /** * 決策服務 * @param matter 決策物料 * @return 決策結果 */ EngineResult process(final DecisionMatter matter); }
domain中有兩個領域服務;規則樹信息領域、規則執行領域,經過合理的抽象化來實現高內聚、低耦合的模塊化服務數據庫
domain/service/MallRuleServiceImpl.java | 領域層中的service來實現應用層接口
/** * 規則樹服務;提供規則規律功能 * * 一、rule包下只進行規則決策領域的處理 * 二、封裝決策行爲到領域模型中,外部只須要調用和處理結果便可 * 三、能夠擴展不一樣的決策引擎進行統一管理 * * 微信公衆號:bugstack蟲洞棧 | 專一原創技術專題案例 * 論壇:http://bugstack.cn * Create by 小傅哥 on @2019 */ @Service("mallRuleService") public class MallRuleServiceImpl implements MallRuleService { private Logger logger = LoggerFactory.getLogger(MallRuleServiceImpl.class); @Resource(name = "ruleEngineHandle") private EngineFilter ruleEngineHandle; @Override public EngineResult process(DecisionMatter matter) { try { return ruleEngineHandle.process(matter); } catch (Exception e) { logger.error("決策引擎執行失敗", e); return new EngineResult(false); } } }
domain/service/logic/LogicFilter.java | 邏輯決策定義
/** * 微信公衆號:bugstack蟲洞棧 | 專一原創技術專題案例 * 論壇:http://bugstack.cn * Create by 付政委 on @2019 */ 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 | 引擎執行定義
/** * 微信公衆號:bugstack蟲洞棧 | 專一原創技術專題案例 * 論壇:http://bugstack.cn * Create by 小傅哥 on @2019 */ public interface EngineFilter { EngineResult process(final DecisionMatter matter) throws Exception; }
infrastructure/repository/RuleRepository.java
/** * 微信公衆號:bugstack蟲洞棧 | 專一原創技術專題案例 * 論壇:http://bugstack.cn * Create by 小傅哥 on @2019 */ public interface EngineFilter { EngineResult process(final DecisionMatter matter) throws Exception; }
interfaces/DDDController.java
** * 微信公衆號:bugstack蟲洞棧 | 歡迎關注學習專題案例 * 論壇:http://bugstack.cn * Create by 小傅哥 on @2019 */ @Controller public class DDDController { private Logger logger = LoggerFactory.getLogger(DDDController.class); @Resource private MallTreeService mallTreeService; @Resource private MallRuleService mallRuleService; /** * 測試接口:http://localhost:8080/api/tree/queryTreeSummaryInfo * 請求參數:{"treeId":10001} */ @RequestMapping(path = "/api/tree/queryTreeSummaryInfo", method = RequestMethod.POST) @ResponseBody public 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) @ResponseBody public 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); } } }
規則樹結構{數據庫轉Json} | 可自行定義
{ "treeNodeMap": { "1": { "nodeType": 1, "ruleDesc": "用戶性別[男/女]", "ruleKey": "userGender", "treeId": 10001, "treeNodeId": 1, "treeNodeLineInfoList": [ { "nodeIdFrom": 1, "nodeIdTo": 11, "ruleLimitType": 1, "ruleLimitValue": "man" }, { "nodeIdFrom": 1, "nodeIdTo": 12, "ruleLimitType": 1, "ruleLimitValue": "woman" } ] }, "11": { "nodeType": 1, "ruleDesc": "用戶年齡", "ruleKey": "userAge", "treeId": 10001, "treeNodeId": 11, "treeNodeLineInfoList": [ { "nodeIdFrom": 11, "nodeIdTo": 111, "ruleLimitType": 3, "ruleLimitValue": "25" }, { "nodeIdFrom": 11, "nodeIdTo": 112, "ruleLimitType": 3, "ruleLimitValue": "25" } ] }, "12": { "nodeType": 1, "ruleDesc": "用戶年齡", "ruleKey": "userAge", "treeId": 10001, "treeNodeId": 12, "treeNodeLineInfoList": [ { "nodeIdFrom": 12, "nodeIdTo": 121, "ruleLimitType": 3, "ruleLimitValue": "25" }, { "nodeIdFrom": 12, "nodeIdTo": 122, "ruleLimitType": 3, "ruleLimitValue": "25" } ] }, "111": { "nodeType": 2, "nodeValue": "果實A", "treeId": 10001, "treeNodeId": 111, "treeNodeLineInfoList": [ ] }, "112": { "nodeType": 2, "nodeValue": "果實B", "treeId": 10001, "treeNodeId": 112, "treeNodeLineInfoList": [ ] }, "121": { "nodeType": 2, "nodeValue": "果實C", "treeId": 10001, "treeNodeId": 121, "treeNodeLineInfoList": [ ] }, "122": { "nodeType": 2, "nodeValue": "果實D", "treeId": 10001, "treeNodeId": 122, "treeNodeLineInfoList": [ ] } }, "treeRoot": { "treeId": 10001, "treeName": "購物分類規則樹", "treeRootNodeId": 1 } }
經過postman調用 | raw => json
查詢規則樹信息
測試接口:`http://localhost:8080/api/tree/decisionRuleTree
請求參數:{"treeId":10001}
apache
{ "treeInfo": { "treeId": 10001, "treeName": "購物分類規則樹", "treeDesc": "用於分類不一樣類型用戶可購物範圍", "nodeCount": 7, "lineCount": 6 }, "treeRulePointList": [ { "ruleKey": "userGender", "ruleDesc": "用戶性別[男/女]" }, { "ruleKey": "userAge", "ruleDesc": "用戶年齡" } ] }
規則樹行爲信息決策
測試接口:http://localhost:8080/api/tree/decisionRuleTree
請求參數:{"treeId":10001}
{ "userId": "fuzhengwei", "treeId": 10001, "nodeId": 112, "nodeValue": "果實B", "success": true }
服務端
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.5.RELEASE) 2019-10-19 18:22:05.672 INFO 13820 --- [ main] org.itstack.demo.DDDApplication : Starting DDDApplication on fuzhengwei-PC with PID 13820 (E:\itstack\itstack.org\itstack-demo-ddd-02\target\classes started by fuzhengwei in E:\itstack\itstack.org\itstack-demo-ddd-02) 2019-10-19 18:22:05.675 INFO 13820 --- [ main] org.itstack.demo.DDDApplication : No active profile set, falling back to default profiles: default 2019-10-19 18:22:05.952 INFO 13820 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3c4297f: startup date [Sat Oct 19 18:22:05 CST 2019]; root of context hierarchy 2019-10-19 18:22:07.756 INFO 13820 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2019-10-19 18:22:07.870 INFO 13820 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2019-10-19 18:22:07.870 INFO 13820 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.34 2019-10-19 18:22:07.896 INFO 13820 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [D:\Program Files Java\Java\jdk1.8.0_162\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;D:\Program Files Java\SlikSvn\bin;D:\Program Files Java\MySQL Server 5.1\bin;D:\Program Files Java\TortoiseGit\bin;D:\Program Files\nodejs\;D:\Program Files Java\Java\jdk1.6.0_24\bin;D:\Program Files Java\apache-maven-3.2.3\bin;C:\Users\fuzhengwei\AppData\Roaming\npm;D:\Program Files Java\Git\cmd;;.] 2019-10-19 18:22:08.040 INFO 13820 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2019-10-19 18:22:08.040 INFO 13820 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2088 ms 2019-10-19 18:22:08.102 INFO 13820 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/] 2019-10-19 18:22:08.126 INFO 13820 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*] 2019-10-19 18:22:08.127 INFO 13820 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2019-10-19 18:22:08.127 INFO 13820 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*] 2019-10-19 18:22:08.127 INFO 13820 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*] 2019-10-19 18:22:09.118 INFO 13820 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2019-10-19 18:22:09.383 INFO 13820 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3c4297f: startup date [Sat Oct 19 18:22:05 CST 2019]; root of context hierarchy 2019-10-19 18:22:10.261 INFO 13820 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/tree/decisionRuleTree],methods=[POST]}" onto public org.springframework.http.ResponseEntity org.itstack.demo.interfaces.DDDController.decisionRuleTree(org.itstack.demo.interfaces.dto.DecisionMatterDTO) 2019-10-19 18:22:10.263 INFO 13820 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/tree/queryTreeSummaryInfo],methods=[POST]}" onto public org.springframework.http.ResponseEntity org.itstack.demo.interfaces.DDDController.queryTreeSummaryInfo(org.itstack.demo.interfaces.dto.TreeDTO) 2019-10-19 18:22:10.272 INFO 13820 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2019-10-19 18:22:10.274 INFO 13820 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2019-10-19 18:22:10.309 INFO 13820 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2019-10-19 18:22:10.309 INFO 13820 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2019-10-19 18:22:16.272 INFO 13820 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2019-10-19 18:22:16.273 INFO 13820 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Bean with name 'dataSource' has been autodetected for JMX exposure 2019-10-19 18:22:16.279 INFO 13820 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource] 2019-10-19 18:22:16.375 INFO 13820 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2019-10-19 18:22:16.381 INFO 13820 --- [ main] org.itstack.demo.DDDApplication : Started DDDApplication in 11.458 seconds (JVM running for 20.584) 2019-10-19 18:22:31.336 INFO 13820 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 2019-10-19 18:22:31.336 INFO 13820 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2019-10-19 18:22:31.372 INFO 13820 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 36 ms 2019-10-19 18:22:32.427 INFO 13820 --- [nio-8080-exec-1] o.itstack.demo.interfaces.DDDController : 規則樹行爲信息決策10001Begin req:{"treeId":10001,"userId":"fuzhengwei","valMap":{"gender":"man","age":"25"}} 2019-10-19 18:22:32.508 INFO 13820 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2019-10-19 18:22:32.956 INFO 13820 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2019-10-19 18:22:33.028 INFO 13820 --- [nio-8080-exec-1] o.i.d.d.rule.service.engine.EngineBase : 樹引擎=>Test分類規則樹 userId:fuzhengwei treeId:10001 treeNode:11 ruleKey:userGender matterValue:man 2019-10-19 18:22:33.028 INFO 13820 --- [nio-8080-exec-1] o.i.d.d.rule.service.engine.EngineBase : 樹引擎=>Test分類規則樹 userId:fuzhengwei treeId:10001 treeNode:112 ruleKey:userAge matterValue:25 2019-10-19 18:22:33.039 INFO 13820 --- [nio-8080-exec-1] o.itstack.demo.interfaces.DDDController : 規則樹行爲信息決策10001End res:{"treeId":10001,"nodeValue":"果實B","success":true,"nodeId":112,"userId":"fuzhengwei"} 2019-10-19 18:23:36.989 INFO 13820 --- [nio-8080-exec-5] o.itstack.demo.interfaces.DDDController : 查詢規則樹信息10001Begin req:{"treeId":10001} 2019-10-19 18:23:37.006 INFO 13820 --- [nio-8080-exec-5] o.itstack.demo.interfaces.DDDController : 查詢規則樹信息10001End res:{"treeInfo":{"treeId":10001,"treeName":"購物分類規則樹","treeDesc":"用於分類不一樣類型用戶可購物範圍","nodeCount":7,"lineCount":6},"treeRulePointList":[{"ruleDesc":"用戶性別[男/女]","ruleKey":"userGender"},{"ruleDesc":"用戶年齡","ruleKey":"userAge"}]}