最新要闻
- 看热讯:小孩飞机票怎么收费
- 1000N连续旋转爆震 国内全新发动机点火成功:颠覆性优势
- 自爆卡车?奔驰EQE车库逆行:反怪特斯拉Model 3车主不让路
- 精选!男子在电竞酒店枕头下发现一窝老鼠 官方回应引网友吐槽:怎么能住
- 微头条丨8只海豚在美国海滩搁浅全部死亡 6只被安乐死
- 焦点速递!吉利全新SUV博越COOL官图发布:四出排气、1.5升发动机
- 要去海南旅游的老友们请注意,海南离岛免税购物,有新变化!
- 环球动态:以太网发明者鲍勃·梅特卡夫获图灵奖 计算机界的诺贝尔奖
- 又一股惨遭退市!市值暴跌99%
- 世界热议:《CS2》地图对比:起源2加持画质更明亮、细节丰富
- 去年净赚499亿元创纪录!保时捷2.7万名员工每人6.7万元奖金
- 环球时讯:琥珀手串会变色吗 琥珀手串会越戴越亮吗
- 环球看点!全球首个空中飞行出租车来了:巴黎开测 2024奥运会要用
- 【天天报资讯】抗早泄国产“伟哥”药物上市 市场有多大?专家称将翻倍增长 国内患者众多
- 全球首搭帝瓦雷音响!比亚迪腾势N7猎跑SUV亮相:运动低趴
- 当前速讯:鸡鸣寺游客爆满设反悔门引导离寺 网友神评:我佛果然慈悲 给反悔机会
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球微头条丨领域驱动设计DDD应用与最佳实践
领域驱动设计(Domain Driven Design,简称:DDD)设计思想和方法论早在2005年时候就被提出来,但是一直没有重视和推荐使用,直到2015年之后微服务流行之后,再次被人重视和推荐使用。
下面我来介绍一下DDD设计思想和方法论,同时结合我们在实际项目中应用总结和思考。
(资料图)
目录
1、为什么要用DDD
2、DDD架构设计
3、领域建模方法
4、代码实践
5、问题总结
1、为什么要用DDD
面向过程很多时候,我们都是操着面向对象的语言干着面向过程的勾当。大部分的系统都是从单一业务开始的。但是随着支持的业务越来越多,代码里面开始出现大量的if-else逻辑,这个时候代码开始有坏味道,没闻到的同学就这么继续往上堆,闻到的同学会重构一下,但因为系统没有统一的可扩展架构,重构的技法也各不相同,这种代码的不一致性也是一种理解上的复杂度。
分层不合理分层太多和没有分层都会导致系统复杂度的上升。
随心所欲随心所欲是因为缺少规范和约束。
面向对象深入的理解面向对象设计原则、模式、方法论有很多,同时要具备非常好的业务理解力和抽象能力。 SOLID是单一职责原则(SRP),开闭原则(OCP),里氏替换原则(LSP),接口隔离原则(ISP)和依赖倒置原则(DIP)的缩写,原则是要比模式更基础更重要的指导准则,深入理解面向对象设计原则极大的提升我们的面向对象设计的能力和代码质量。
分层设计分层最大的好处就是分离关注点,让每一层只解决该层关注的问题,从而将复杂的问题简化,起到分而治之的作用。
领域建模领域模型可以准确地反映业务语言,使业务语义显性化,而传统的J2EE或Spring+Hibernate/Mybatis等事务性编程模型只关心数据,这些数据对象除了简单setter/getter方法外,没有任何业务方法,被称为成贫血模式。
规范设计各归其位、命名约定、通用业务状态码约定等。
DDD概念定义
领域驱动设计:一种软件开发方法,是一种软件核心复杂性应对之道,它可以帮助我们设计高质量的软件模型。
DDD目的
- DDD是为了解决复杂业务逻辑的问题
- DDD的核心问题不是技术问题,而是关于讨论、聆听、理解、发现业务价值的问题
- DDD是技术人员拥有产品思维的一个体现
- DDD的学习曲线很陡主要是因为它是一种方法论,每个人对它的理解存在差异
领域模型与事务脚本对比
富血模型:就是属性和方法都封装在Domain Object里,所有业务都直接操作Domain Object。 service层只是完成一些简单的事务之类的,甚至可以不用service层。也就是直接从action->entity。
贫血模型:就是一个对象里只有属性,要实现业务,要依靠service层来实现相关方法,service层的实现是面向过程的,大量传统的分层应用action->service->dao->entity的方式就是这种贫血的模式实现。
举个例子,银行转账实现类
各位看官看到上面的代码是不是有一种似曾相似的感觉?咋一看也没有啥问题,能正常运行,能快速交付业务。如果仅是为了应付需求任务交差,当然没有什么啥问题了。但是从设计角度来看确定存在许多问题,代码没有全面糊在一起,没有分层,伪面向对象的方法。
但是我说我们码农总得要有自己一点的追求,为伟大代码事业创造一点艺术贡献!
- 如果业务需求快速变化怎么办,需求越来越复杂怎么办?
- 如果团队里面有多人协作,代码需要经过多人反复修改接手传承,你敢保证后面接手的人不会骂你吗?
- 难道设计上有没有可以改进的空间?
答案是可以有的!
客观来说上面的这段代码不算复杂,也算写得中规中举。下面我来让贴一段曾经在我们实际生产系统跑了将近一年的神代码,你才知道什么叫复杂和神奇!16层if..else+for循环!就问你怕了没有?
请看神代码片段
if (contentOld != null && contentNew != null) { map.put("ModelId", contentNew.getModelId());//ModelId map.put("name", contentNew.getName());//Name map.put("Description", contentNew.getDescription());//Description map.put("fujianGroup", attachments);//附件组信息 for (int i = 0; i < contentOld.getGroups().size(); i++) {//数据库保存 组 MscsApiShowerRoomModelGroups groupsOld = contentOld.getGroups().get(i); if ("纳米易结".equals(groupsOld.getGroupName())) {//新增的组信息 map.put("nanometerGroup", groupsOld);//纳米易结 } else if ("石基先发".equals(groupsOld.getGroupName())) { map.put("stoneGroupFirst", groupsOld);//石基先发 } else if ("客户其它内容".equals(groupsOld.getGroupName())) { map.put("otherGroup", groupsOld);//客户其它内容 } else {//原来的组 通过组ID 来判定 if ("1".equals(groupsOld.getGroupId())) {//产品规格组 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { groups.setOtherValue(groupsOld.getOtherValue());//设置单选按钮 if (groupsOld.getGategories() != null && groups.getGategories() != null) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 for (MscsApiShowerRoomModelProperties properties : options.getProperties()) { for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) { if (properties.getPropertyId() != null && properties.getPropertyId().equals(properties2.getPropertyId())) { properties2.setDefaults(properties.getDefaults()); } } } } else { options2.setIsDefault(false); } } } } } } } } map.put("productGroup", groups);//产品规格组 } }//map.put("productGroup",null);//产品规格组 } else if ("2".equals(groupsOld.getGroupId())) {//开门方向与产品方向 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } map.put("doorGroup", groups);//开门方向与产品方向 } } } else if ("3".equals(groupsOld.getGroupId())) {//产品颜色 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } map.put("productColorGroup", groups);//产品颜色 } } } else if ("14".equals(groupsOld.getGroupId())) {//玻璃工艺 String name = ""; for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { groups.setOtherValue(groupsOld.getOtherValue()); if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getName() != null && categories.getName().equals(categories2.getName())) { name = categories.getName(); if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } //设置 默认数据的位置 选中的数组 为第一个 默认显现 if (groups.getGategories() != null && !"".equals(name)) { List arry = new ArrayList(); List arry1 = new ArrayList(); List arry2 = new ArrayList(); for (MscsApiShowerRoomModelCategories categories : groups.getGategories()) { if (name.equals(categories.getName())) { arry1.add(categories); } else { arry2.add(categories); } } for (MscsApiShowerRoomModelCategories a : arry1) { arry.add(a); } for (MscsApiShowerRoomModelCategories a : arry2) { arry.add(a); } groups.setGategories(arry); } map.put("glassGroup", groups);//玻璃工艺 } }//map.put("glassGroup", null);//玻璃工艺 } else if ("4".equals(groupsOld.getGroupId())) {//玻璃贴膜 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { groups.setOtherValue(groupsOld.getOtherValue()); if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getName() != null && categories.getName().equals(categories2.getName())) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } map.put("filmGroup", groups);//玻璃贴膜//break ; } } } else if ("5".equals(groupsOld.getGroupId())) {//玻璃厚度 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getName() != null && categories.getName().equals(categories2.getName())) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } map.put("thicknessGroup", groups);//玻璃厚度 } } } else if ("6".equals(groupsOld.getGroupId())) {//拉手款式 String name = ""; for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getName() != null && categories.getName().equals(categories2.getName())) { name = categories.getName(); if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } //设置 默认数据的位置 选中的数组 为第一个 默认显现 if (groups.getGategories() != null && !"".equals(name)) { List arry = new ArrayList(); List arry1 = new ArrayList(); List arry2 = new ArrayList(); for (MscsApiShowerRoomModelCategories categories : groups.getGategories()) { if (name.equals(categories.getName())) { arry1.add(categories); } else { arry2.add(categories); } } for (MscsApiShowerRoomModelCategories a : arry1) { arry.add(a); } for (MscsApiShowerRoomModelCategories a : arry2) { arry.add(a); } groups.setGategories(arry); } map.put("handleGroup", groups);//拉手款式 } }//map.put("handleGroup", null);//拉手款式 } else if ("7".equals(groupsOld.getGroupId())) {//石基 String name = ""; for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getName() != null && categories.getName().equals(categories2.getName())) { name = categories.getName(); if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } //设置 默认数据的位置 选中的数组 为第一个 默认显现 if (groups.getGategories() != null && !"".equals(name)) { List arry = new ArrayList(); List arry1 = new ArrayList(); List arry2 = new ArrayList(); for (MscsApiShowerRoomModelCategories categories : groups.getGategories()) { if (name.equals(categories.getName())) { arry1.add(categories); } else { arry2.add(categories); } } for (MscsApiShowerRoomModelCategories a : arry1) { arry.add(a); } for (MscsApiShowerRoomModelCategories a : arry2) { arry.add(a); } groups.setGategories(arry); } map.put("stoneGroup", groups);//拉手款式 } } } else if ("8".equals(groupsOld.getGroupId())) {//层架 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 for (MscsApiShowerRoomModelProperties properties : options.getProperties()) { for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) { if (properties.getPropertyId() != null && properties.getPropertyId().equals(properties2.getPropertyId())) { properties2.setDefaults(properties.getDefaults()); } } } } else { options2.setIsDefault(false); } } } } } } } } map.put("shelfGroup", groups);//层架 } }//map.put("shelfGroup", null);//层架 } else if ("9".equals(groupsOld.getGroupId())) {//木架包装 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 } else { options2.setIsDefault(false); } } } } } } } } map.put("mujiaGroup", groups);//木架包装 } } } else if ("32".equals(groupsOld.getGroupId())) {//玻璃宽度 for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGroupId().equals(groups.getGroupId())) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { if (groupsOld.getGategories() != null && groups.getGategories() != null) { for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) { for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) { if (categories.getOptions() != null && categories2.getOptions() != null) { for (MscsApiShowerRoomModelOptions options : categories.getOptions()) { for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) { if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) { options2.setIsDefault(true);//设置为默认选项 if (options.getProperties() != null) { for (MscsApiShowerRoomModelProperties properties : options.getProperties()) { for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) { if (StringUtils.isNotEmpty(properties.getCode()) && properties.getCode().equals(properties2.getCode())) { properties2.setDefaults(properties.getDefaults()); } } } } } else { options2.setIsDefault(false); } } } } } } } } map.put("glassWidthGroup", groups);// 玻璃宽度 } } } } } } }
各位看官你们看,像上面这种代码请问谁敢去维护?谁看谁骂,谁改谁死!这种神代码绝对是可以拿来当教材用的~(在这里展示这段代码主要为了说明如果系统设计和分层不合理,将会带来严重的后果。代码就像狗屎一样)
PS:说明一下写这些代码的作者本人因为一些原因离职了,我们当年系统上线后将近一年多的时间里不敢去修改这神代码,也没有人敢改。
如果业务需求一直稳定不改,那可以还勉强维持着,但是那有正常业务不改需求的,天底下应该没有!所以它始终像一颗大雷在我们的头顶上滚动着!后来经过两次重大重构设计之后,把它消灭了!篇幅原因这里的故事就不展开讲了(将来哪天有空专门写写:那些年那些神代码!)。
回到主题上,银行转账那一段代码对比之下是不是小屋见大屋?下面我们基于银行转账那一段代码,展示一下怎么拆解和设计,变成领域设计模型。
转变成领域设计如下:
重新拆解,转换之后,设计领域对象,代码分层结构清晰了很多,从此世界也变得清新了。你说香不香吗?
2、领域驱动设计架构设计
领域驱动设计分层
User Interface为用户接口层,也是经常我们看到Controller层类似,主要负责系统入口协议等,该层不处理任何业务逻辑,只负责调用接入和应用转发。
Application是应用层,和以往事务脚本的Service是截然不同的,该层中并不做详细的业务逻辑的封装,而是创建Domian并调用Domain中的相应方法完成业务逻辑处理,并调用Infrastructure完成Domain的持久化操作,该层需要负责事务的控制,保证数据的一致性。
Domain层是核心领域层,核心的业务逻辑应该以Domain为对象进行分类封装,Domain的划分以业务为基准,Domain层在技术层面的建模通用技巧在下面会做详细介绍,该层只能向下调用Infrastructure完成自身模型的初始化和持久化。
Infrastructure层类似于以往事务脚本的Dao层,以往的面向事务脚本的编程中,以数据表为主,所有的事务脚本直指目的就是完成表的CURD,而DDD中以模型为核心,Infrastructure层是为了重建已有的Domain,并在退出时持久化内存中的Domain对象。Infrastructure层不仅包含对关系数据库的处理,还包括对分布式缓存处理、对外系统的接入(integration)以及分布式消息队列的push操作。
一些常用的关键术语:
- 实体 - Entity
- 值对象 - Value Objects
- 领域服务 - Domain Services
- 领域事件 - Domain Events
- 模块 - Modules
- 聚合 - Aggregate
- 资源库 - Repository
实体和值对象在代码中皆表示为一个类(对象),从业务上来讲也分别表示一个业务实体。但是两者是有区别,实体是一个完整的具有生命周期的可以通过唯一标识来识别东西的类(对象),而值对象则为了度量和描述领域中的一个属性,将不同的相关属性组合成一个概念整体的类(对象) 。
例如,客户可以认为是一个实体,一个客户就是具有生命周期的东西,具有唯一的标识可以将A客户和B客户分开(唯一标识:身份证号码),而这个客户的地址(例如:广州市/白云区/欧派软件园)就应该定义为一个值对象,当我们定义好一个地址,它既可以是A客户的地址也可以是B客户的地址,当我需要更新A客户地址时,可以直接销毁A客户关联的地址对象然后重新创建一个地址对象关联到A客户上。
领域事件是由一个特定领域因为一个用户Command触发的发生在过去的行为产生的事件,而这个事件是系统中其它部分感兴趣的。
在现在的分布式环境下,业务系统与业务系统之间并不是割裂的,而消息绝对是系统之间耦合度最低,最健壮,最容易扩展的一种通信机制。
领域服务和以往事务脚本的Service只能说非常像,唯一不同的是,以往事务脚本的Service是自己全权负责业务逻辑,而领域服务不仅编写业务逻辑,还会调用实体和值对象的方法来协调实体和值对象,从而避免实体和值对象的耦合。当我们建模之后发现有些业务既不单单属于A领域对象,又不单单属于B领域对象,我们可以那么我们可以抽象出一个Service来完成此项业务,那么这个类就是领域服务。
资源库也叫数据仓库,主要是完成领域对象的重建以及对象的持久化操作。资源库的设计主要是为了调用基础设施层来完成表的CURD、缓存的操作以及外系统接口的调用。
领域驱动设计架构图
这里引用阿里团队开源的可乐(Cola)领域设计架构示图,如下图所示:
DDD最核心思想是设计内部六边形结构。
从内往外看,最内层也是最核心就是Domain层(包括:Domain model和Domain Service);
第二层是Application层(包括:Application Service);
第三层业务逻辑层(Business Logic也可以叫基础设施层)主对外提供服务接口,对内解决基础服务包装构建。
DDD分层设计之调用示图
上图已经非常清楚展示了分层设计以及各层调用关系,一图胜千言,大家认真详细看图就可以理解了。
操作命令和对象抽象,通过命令与查询分离设计方式实现服务接口调用。
CQRS(Command Query Separation,命令查询分离) 基本思想在于,任何一个对象的方法可以分为两大类:
- 命令(Command):不返回任何结果(void),但会改变对象的状态
- 查询(Query):返回结果,但是不会改变对象的状态,对系统没有副作用
扩展点功能概念
领域驱动设计另外一个重要的地方是扩展点。
可乐包在扩展点功能设计中引入的概念:业务、用例、场景。
- 业务(Business):就是一个自负盈亏的财务主体,比如tmall、淘宝和零售通就是三个不同的业务。
- 用例(Use Case):描述了用户和系统之间的互动,每个用例提供了一个或多个场景。比如,支付订单就是一个典型的用例。
- 场景(Scenario):场景也被称为用例的实例,包括用例所有的可能情况(正常的和异常的)。比如对于“订单支付”这个用例,就有“可以使用花呗”,“支付宝余额不足”,“银行账户余额不足”等多个场景。
例如我们要实现右图中所展示的扩展:在tmall这个业务下——下单用例——88VIP场景——用户身份校验进行扩展,我们只需要声明一个如下的扩展实现(Extension)就可以了。
3、领域建模
领域架构设计并不复杂,复杂的业务需求怎么转化为领域模型,这也是最难的地方,需要业务梳理足够清晰,同时需要有足够抽象能力和领域划分。
领域建模基于业务的建模的基础上,需要花较重的时间比例在梳理业务和模型设计上面;而同时领域建没有通用的统一结构设计,得看具体业务具体分析。下面举个例子说明一下领域建模方法。希望能够各位得到一点启动。
领域模型不是简单POJO或数据实体对象,他还有行为和状态,主要体现在事件机制、值对象上面。
这里先不深入讨论,先抛个影子,后面抽空补上更详细的说明。
4、领域驱动设计实现
4.1、分层设计
各层分工:
- Application层主要负责获取输入,组装context,做输入校验,发送消息给领域层做业务处理,监听确认消息;
- Domain层主要是通过领域服务(Domain Service),领域对象(Domain Object)的交互,对上层提供业务逻辑的处理,然后调用下层Repository做持久化处理;
- Infrastructure层主要包含Repository,Config,Common和message,Repository负责数据的CRUD操作,数据来源可以是MySQL,NoSql,Search,甚至是HSF等;
- Config负责应用的配置;Common是一写工具类;负责message通信的也应该放在这一层。
4.2、建立二方库组件
二方库组件:api:存放的是应用对外的接口。dto.domainmodel:用来做数据传输的轻量级领域对象。dto.domainevent: 用来做数据传输的领域事件。Application里的组件:service:接口实现的facade,没有业务逻辑,可以包含对不同终端的adapter。eventhandler:处理领域事件,包括本域的和外域的。executor:用来处理(Command)和查询(Query)。interceptor:COLA提供的对所有请求的AOP处理机制。Domain里的组件:domain:领域实体,允许继承domainmodel。domainservice: 领域服务,用来提供更粗粒度的领域能力。gateway:对外依赖的网关接口,包括存储、RPC、Search等。
大部分情况下,二方库的确是用来定义服务接口和数据协议的。但是二方库区别于JSON的地方是它不仅仅是协议,它还是一个Java对象,一个Jar包。既然是Java对象,就意味着我们就有可能让DTO升级为一个Domain Model(有数据,有行为,有继承) 。Domain Model用到的所有数据如果都是自恰的,那么这些计算是不需要借助外面的辅助,自己就能完成。比如CustomerCO拥有身份证号码,那么判断当前这个CustomerCO的性别、年龄等信息时是依靠自己(身份证号码)就能完成的。是一种共享内核, CustomerCO把自己领域的知识(语言、数据和行为)通过二方库暴露出去了,假如有100个应用需要获取性别或年龄时做判断,客户端就不需要自己实现了。
应用不同Bounded Context之间的协作,要充分利用好二方库的桥梁作用。其协作方式如下图所示:
4.3、主要组件依赖关系
依赖关系示例,如以Customer会员业务对象举例如下图所示
4.4、上下文接口
4.5、代码实现
对外接口代码示例
参数校验
API接口服务层示例
命令总线示例
全局异常捕获示例代码
5、问题与总结
软件的世界里没有银弹,因此,领域模型还是事务脚本没有对错之分, 关键看是否合适:- 对于简单的业务场景,使用事务脚本,其优点是简单、直观、易上手;
- 对于复杂的业务场景,比较有效的治理办法就是领域建模,在封装业务逻辑的同时,提升了对象的内聚性和重用性,因为使用了通用语言,使得隐藏的业务逻辑得到显性化表达,使得复杂性治理成为可能。
领域驱动设计结构非常清晰,适合复杂业务场景,代码结构清晰,代码维护代会低很多。当然也需要业务建模与抽象能力,与传统面象数据开发方法有本质的不同,需要转变开发者的思维方法,对团队有一定学习成本。
要求开发者:- 有开发卓越软件的激情和毅力
- 渴望学习和进步
- 有能力理解软件模式,并懂得如何应用这些模式
- 有发掘不同的设计方法能力和耐性
- 勇于改变现状
- 希望编写出更好的代码
关键词:
-
世界观察:Android使用SurfaceView实现签名板
SurfaceView使用首先创建一个SurfaceViewSign类,继承SurfaceView类,继承SurfaceHolder Callback和Runnable接口,代码如下:impor
来源: -
每日聚焦:机器学习算法(三):基于horse-colic数据的KNN近邻(k-nearest neighbors)预测分类
机器学习算法(三):基于horse-colic数据的KNN近邻(k-nearestneighbors)预测分类
来源: -
环球观天下!Linux安装Redis教程
举例版本Redis版本5 0 4服务器版本LinuxCentOS7 664位下载Redis进入官网找到下载地址https: redis io download右键Downloa
来源: 世界观察:Android使用SurfaceView实现签名板
每日聚焦:机器学习算法(三):基于horse-colic数据的KNN近邻(k-nearest neighbors)预测分类
全球微头条丨领域驱动设计DDD应用与最佳实践
环球观天下!Linux安装Redis教程
看热讯:小孩飞机票怎么收费
1000N连续旋转爆震 国内全新发动机点火成功:颠覆性优势
自爆卡车?奔驰EQE车库逆行:反怪特斯拉Model 3车主不让路
精选!男子在电竞酒店枕头下发现一窝老鼠 官方回应引网友吐槽:怎么能住
微头条丨8只海豚在美国海滩搁浅全部死亡 6只被安乐死
焦点速递!吉利全新SUV博越COOL官图发布:四出排气、1.5升发动机
要去海南旅游的老友们请注意,海南离岛免税购物,有新变化!
今日热闻!数据库系统原理之关系数据库
深入消息队列MQ,看这篇就够了!
全网最详细中英文ChatGPT-GPT-4示例文档-从0到1快速入门条目分类应用——官网推荐的48种最佳应用场景(附python/node.js/curl命令源
环球动态:以太网发明者鲍勃·梅特卡夫获图灵奖 计算机界的诺贝尔奖
又一股惨遭退市!市值暴跌99%
世界热议:《CS2》地图对比:起源2加持画质更明亮、细节丰富
去年净赚499亿元创纪录!保时捷2.7万名员工每人6.7万元奖金
Java八股文之基础篇
全球简讯:在 Arch Linux 中安装 GNOME 桌面所需步骤介绍
当你对 redis 说你中意的女孩是 Mia
环球时讯:琥珀手串会变色吗 琥珀手串会越戴越亮吗
环球看点!全球首个空中飞行出租车来了:巴黎开测 2024奥运会要用
【天天报资讯】抗早泄国产“伟哥”药物上市 市场有多大?专家称将翻倍增长 国内患者众多
全球首搭帝瓦雷音响!比亚迪腾势N7猎跑SUV亮相:运动低趴
当前速讯:鸡鸣寺游客爆满设反悔门引导离寺 网友神评:我佛果然慈悲 给反悔机会
全球今日报丨读C#代码整洁之道笔记04_重构C#代码识别代码坏味道
北方多地遭遇沙尘暴 PM爆表:气象台再发预警 还没退去
环球头条:流浪小狗乞求收养者一起带走玩具熊:画面让养狗人士泪目 为何被遗弃
今日关注:iPhone 14首发的车祸检测功能让人崩溃!苹果iOS 16.4将优化升级
世界热讯:读Java性能权威指南(第2版)笔记25_性能测试方法上
每日头条!LOL2023狗熊怎么出装(上单狗熊2023出装顺序)
精选!等了 11年 《CSGO2》电竞网游终于官宣:画质大升级 免费更新
天天简讯:使用C#开发微信公众号对接ChatGPT和DALL-E
今日关注:关于人工智能的思考,写在chatGPT爆火之时
Vue——initRender【八】
环球快讯:NVIDIA突然复活SLI!但不是你想的那样
每日看点!比尔·盖茨谈ChatGPT 赞其1980年以来最革命性技术进步
天天即时:爱子飞机上死亡:母亲怒告世界最大航司美国航空
谷歌的“GPT”终于憋出来了!但是 也不比百度强多少啊
当前热议!抖音救人一命!男子发头孢配酒视频获救:客服教科书式报警
头条:交700个税的工资多少_j700
今日讯!SEO优化:友情链接!真心换真心?
通过 poe 免费使用ChatGPT、GPT-4
天天观察:71.C++标准库类型string
CTAS建表时报错ORA-65114
Go HTTP编程
天天热点!苹果官方推荐!iNote灵感笔记新版发布:超紧凑模式来了
热讯:金士顿无敌了!拿下2022年全渠道SSD市场占有率第一
高德、口碑正式合并:阿里旗下本地到店业务将统一整合
热推荐:国科微:目前晶圆产能较前两年已趋于缓和
全球最新:关于基于AWS-Cli的方式对RDS资源批量添加tag的方法
全球微速讯:小红书去水印技巧合集(亲测有效!!!)
每日热点:Git基本使用
RHEL无法配置网关问题一则
世界快看点丨加快步伐!腾讯高管:“生成式AI”或纳入微信和QQ
天津金逸影城
2023年3月22日(软件工程日报)
阿里云大使和代理商的优惠的区别与选择
动态焦点:美债市场危机四伏 “安全资产”吸引力或进一步下降
3GB显存被封杀!《光环无限》都不让玩
Opera浏览器推送97.0.4719.26更新:集成ChatGPT与AI总结功能
世界速递!碧桂园打造湖湘人居典范 护航品质生活
世界新动态:70.C++ using的三种用法详解
环球资讯:农业银行发行700亿元二级资本债进一步增强资本实力
3599元 铭凡NUCG5迷你主机上架:碳纤维机身、下压式散热
世界百事通!比RTX 4080小一半!NVIDIA发布RTX 4000 SFF半高卡:功耗仅70瓦
【世界新要闻】专家:沙尘天气里最好别戴隐形眼镜
焦点播报:成龙和甄子丹"打架"谁会赢?甄子丹:我老了 他更老
性价比更高 有哪些散片CPU值得入手?
每日精选:Swift 备忘清单_开发速查表分享
全球信息:利用“姿态相似度比较”功能,对运动(动作)识别检测“秒”适配
国民第一APP坐实了 微信月活破13亿无可匹敌
天天时讯:堆料最多的小米手机!曝小米13 Ultra最高配1TB UFS 4.0闪存
iPhone 15 Pro工业设计敲定!这外观果粉会打多少分?
1499元!微星发布MD271UL 4K显示器:P3色域可达99%
399元1TB 速度5000MB/s!影驰星曜X4 PRO SSD图赏
世界热推荐:IDC 预计 2023 年全球可穿戴设备出货量预计将出现反弹
微头条丨低代码开发,一场深度的IT效率革命
混合多云第二课——混合技术如何每年为京东节省上亿元成本?
【天天新视野】记录--我在前端干工地(three.js)
数据库系统原理之数据库编程
当前滚动:债市日报:3月22日
女子应聘被HR质疑23岁用苹果手机:称其年纪小 手机贵
天天速讯:四川一高校放7天春假 鼓励大家去恋爱!网友:坚持一个四川原则
环球速递!腾讯2022年人均年薪102.5万!一年增加近20万
世界资讯:“长胡子”了 比亚迪海豚泰国版发布:售价约16万人民币
【全球新视野】被马化腾视为腾讯希望!微信视频号使用时长已超朋友圈
世界看热讯:农历中的惊蛰到底有什么寓意?
天天快讯:Kruise Rollout v0.3.0:教你玩转 Deployment 分批发布和流量灰度
ThreadPool实现机制
焦点热议:nodejs处理嵌套对象的npm包:dot-prop
中国西北地区发现广泛分布的侏罗纪被子植物化石
3年陈花香白牡丹:董德福鼎白茶200g装128元大促(200元券)
环球快看:188GB显存 800W功耗!NVIDIA发布H100 NVL加速卡:ChatGPT提速2倍
世界新资讯:美国两囚犯用牙刷挖洞越狱成功:墙内钢筋松了!职能部门颜面扫地
当前观察:9岁女孩豪掷18.9万打赏游戏主播 偷偷记下密码还删除转账记录
天天速讯:新能源汽车高速没电竟然倒车回服务区充电 后方车辆纷纷避让
Geyser - Minecraft Server 基岩版与Java版互通教程
世界速讯:Rust 知识积累(5)