您好,登录后才能下订单哦!
这篇文章主要介绍“对领域驱动设计的认识有哪些”,在日常操作中,相信很多人在对领域驱动设计的认识有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”对领域驱动设计的认识有哪些”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
一口吃不成胖子,一朝减不成瘦子。当前服务的开发遵循的都是controller、service、dao的模式,业务逻辑都在service中实现,虽有运用工厂、策略、适配器等设计模式,但依旧是面向对象下的过程式编程。相较于过程式的编程,面向对象的编程则对抽象有了更高的要求。完成思维模式的改变不是一朝一夕就可以的,需要一个循序渐进的过程。
将一个现有的服务系统做领域划分,简单的可以从服务提供的接口出发。首先对服务提供的接口进行划分,可以粗粒度的划分出应该有的领域,而且接口的现有上下文可以粗略的等同于服务领域应该有的限界上下文,再向下对领域中的每一个接口进行分析,提取其中的实体、值对象,最终在领域内容找出聚合根,并提炼出相应的工厂与领域服务。分析至此,领域化改造基本可以动手开始实施了。但此时的理论基础还是粗粒度的,在实施的过程中肯定会发现一些不合适的地方。
欲要成其事,必先利其器。为了实施过程中能顺利一些,可以在初步划分好领域之后,对领域进行组内的沟通,一方面是成员之间信息互通,统一领域专业术语;另一方面可以集思广益对领域的划分进行进一步的完善与细化。
领域就是对现实中一系列或一组问题的抽象及其“解”,如同一道完整的数学大题一样,有完整的题干(对现实问题的抽象描述)和解答过程。领域的概念是一种虚拟的概念,领域更多的框定出一系列或者一组抽象的内容或者说叫事物,在领域中解决问题需要依赖于界限上下文将问题限定在一定的范围之中。
限界:解决问题要在一定的界限范围内才能高效、准确的解决问题,若不能圈定一个范围,会将问题无限放大,所有的解决方案都会被各种条件限制。 上下文:即包含处理逻辑,包含逻辑处理过程中的数据状态。上下文存在于领域中,为解决领域中的特定问题框定一个范围,并且为解决当下的问题提供一个可以依赖的上下文。上下文的意思即为提供一个环境,在领域设计中就是为当前的动作提供一个环境。在一个上下文中会有多个动作,在每一个动作前提供当前动作所需的准备动作,在之后提供当前动作结果的后续处理,以此组成一个上下文。
要理解上下文,可以从现有的编程语言、系统设计中类比理解。先从一个点出发,再到一个面。
在前端HTML语言中,整个HTML文档和CSS样式、JS脚本共同构成了一个上下文,一个DIV标签代表的内容要在页面中如何显示,要依赖于其父节点的位置,以及为当前DIV设置的CSS样式和从父标签那继承来的CSS样式,以及对当前DIV指定的JS脚本,共同影响得到。这里的“父节点位置”、“为当前DIV设置的CSS样式和从父标签那继承来的CSS样式”、“ 对当前DIV指定的JS脚本”就是所谓的上下文。甚至于说整个DIV内部的自节点有什么内容或影响也是上下文的一部分。
在Spring中,也存在这上下文的概念。Spring中的上下文,即包含当前的运行环境,还有当前Spring可以扫描到的配置、bean等信息。当spring中获得或者初始化一个bean的时候,要明确当前的运行环境(是web环境还是本地应用),要明确是否与相关的配置或者约定会影响到bean的初始化,要明确初始化当前bean的依赖,要明确bean创建出来后有没有要执行的操作(默认执行的方法等),是否有其他的bean的初始化需要依赖当前bean。这里的“运行环境”、“配置、bean信息”、“依赖”、“初始化后的方法”、“被依赖”就共同组成来上下文。
实体通俗的表现即为一个bean.java文件,而且是一个充血的模型,其中有属性(唯一标识及其他字段),为实体赋予实际意义的动作(动作:工程表现就是Java文件中的方法)。实体中方法的命名一定是可以描述实际意义的动作描述,描述的出发角度就是以当前这个实体为第一人称视角,描述当前动作的实际意义。
因为实体为充血的模型,其中既有方法也有属性值,所以实体是有状态的对象,再使用Spring容器提供的单例模式已经不合适了,会引起严重的线程安全问题,所以要使用其他方法进行初始化。可以像传统设计中的数据库值映射对象一样在线程中的new出来,也可以使用工厂类生产出来,也可以使用Spring容器的原型模型提供,但是提供出来的对象是空的对象,所有的值都需要手动传入。
实体的一个重要且必须的字段就是——唯一标识。所以在实体对象初始化时,唯一标识是一个重要的字段,(虽然很重要,但是也是可以为空的,比如在创建新的实体的时候。)剩余的其他字段,应按需加载,当需要使用的时候再创建或恢复(从基础设置中获得),这样做的重要的一点儿就是性能优化,节约资源。
重要一点:实体的定义是以业务领域设计为出发点,其并不与数据库中的表(或者说是数据库中的表在工程中的映射对象)是一一对应的,严格来说是不能一一对应的,但处于现阶段现状的考虑,允许将实体与数据库中的表进行对应。但进行对应后就会引入一个新的问题:实体与数据库表映射对象会区分不清楚,造成设计思想理解上的障碍。
值对象就是一个没有唯一标识的实体,但也是有状态的,所以也不适用于Spring容器的单例模式,而更适用于使用new的方法在需要的时候手动创建出来。值对象要做到尽量的简单,具有实际业务意义的动作(即class文件中的方法),应尽可能的都放置到Entity或领域服务中,值对象中应最多只保留必要的数据校验的,且作用域也要局限在当前值对象内部,不要使用外部的值、变量等,但这不代表不能让值对象有动作表现,为了完成一个业务需求而执行一个动作,同时又没有一个实体、聚合根可以描述(定义)当前动作的时候,就应该由值对象来描述动作。
再领域改造的过程中,当一个“动作”已经明确无法由某个实体来描述,但有没有扎实的论据来证明这个“动作”可以由一个值对象来描述的时候,就先将这个动作交由领域服务来描述,直到随着系统的迭代演进有了足够的论据可以支持由某个值对象来描述这个动作,再将动作的描述从领域服务转移到值对象中。
聚合,就是将业务领域中具有强依赖关联性的实体、值对象进行组合,是一个高级抽象的概念。
对于聚合的简单、具像化理解可以理解为一个“锁”,Synchronize、lock是程序代码执行的锁,事务是数据库数据持久化的锁,聚合就是业务系统架构设计的锁。将在业务领域中有强依赖关系的对象归集到聚合根下,使用聚合根作为“锁对象”,所有的动作都从聚合根出发并在聚合根结束,保证业务领域设计的“原子性”。
但领域驱动设计中的聚合不仅仅是一个锁的概念,聚合并没有强制性的一致性,终其所有是代表了领域中的实体、值对象的组合。
聚合根可以为一个单独的java文件,也可以是一类特殊的Entity。前者是不建议的方案,后者也是通常的做法,即将一个关键的Entity作为当前领域的聚合根,此时聚合根也就具有类属性和赋予类实际意义的“动作”。使用一个关键的Entity作为聚合根,则此聚合根也就具有的状态,有状态的对象初始化是需要成本代价的,所以在某些简单场景可以允许领域服务等直接访问聚合根下的其它Entity,但再涉及到跨领域的业务处理时应使用聚合根进行处理,不适用于直接访问聚合根下其它Entity的情况。
当聚合根是一类特殊的Entity的时候,聚合根就是一个充血的即有属性又有实际意义动作的对象。实体(或者以实体作为载体的聚合根)中的动作应为仅完成一项动作,不要将多个动作合并到一个方法中,除非这个动作在业务领域设计中也是“原子性”的,否则在实体实现中应拆分为多个动作。
实体的每一个动作只完成一个“原子性”的行为,当一个业务操作需要对一个实体进行一连串的动作时,就要依靠领域服务,将实体提供的一系列“原子性”的动作封装为一个独立的动作,并暴露出去。对于领域外部仅仅是一个动作(即为一个方法的调用),但对于领域内的实体就是一连串的“原子”动作的集体表现。对于这个封装的过程,就已经将限界上下文的思想体现出来了,但这不是限界上下文的全部,在领域服务的这个方法之外,还会有别的逻辑,比如说操作日志的记录等,这些都是限界上下文的内容。
对于领域服务的方法,就要进行相应的事务控制。
领域事件在理解上可以分为两类:一类是抽象意义上的“事件”,在系统设计中有列出,但是不对应系统开发中的任何的类、组件、框架、中间件等实体的,是一种在设计层面de抽象的,偏向于领域专家的理解;另一种就是可以体现在组件、中间件上的“事件”,是有型的,开发人员可以实实在在看得到并能通过技术手段进行控制的,偏向于开发人员的理解。
前一种“事件”可以以后一种“事件”为载体在系统开发种体现出来,但也可以使用非后者的形式在系统中体现出来:可能就是系统中的一段逻辑。
业务逻辑处理中,Entity的动作处理的数据(即为Entity的属性的值)可以来自于入参,也可能是从某个服务或者某个持久化容器(缓存、数据库等)中拿到的,将这些服务的接口在领域中的映射|代理|实现统统划入基础设施,意为为领域提供基础支持。
基础设施的接口是由领域定义的,基础设施实现接口的定义为领域提供基础服务。基础设施在实现领域定义的接口时,应遵守领域中相关接口的约定,并以技术手段对保证约定的正常履行。例如:在将修改实体的状态更新为状态B时,领域的接口约定必须要在状态为A的情况下才能变更,从保证数据安全与效率的考量下,基础设施要保证接口相关约定的正常履行。
防腐层的设计意义就是为了保持领域内部设计的内聚性,不让领域外部的改变影响到领域内部的实现,使领域内部变得混乱,变得腐败。所以这一层换一个理解就是:为领域设计的适配器。 11.工厂-Factory 工厂作为直接的生产对象,用来完成各种实体、值对象、聚合根的初始化构建工作,既能提供单个对象的初始化构建,又能胜任批量对象的初始化构架。但是工厂除了对象的初始化构建之外不应该承担任何其他的动作,如果这项动作与对象的初始化构建无关,既是再小也不能侵入到工厂内,不然会使得工厂越来越腐烂,只到最后不能再称之为“工厂”。
工厂应该是以单例的形式存在,但是工厂生产出来的对象:实体、值对象、聚合根应该是模版模式生产出来的完全独立的对象。
实体与值对象的划分,在直觉上就是有无“唯一标识”,但这也不是唯一的标准。如果严格按照此一个唯一的标准进行划分也无可厚非,但实际开发中总有各种情况需要“妥协”,特别是现有的旧项目进行领域化改造的时候,这样的掣肘限制就更多一些。
可以将值对象简单的归结为贫血的且无唯一标识对象,如果一个对象,它有一个字段可以作为唯一标识,但是在系统中不会频繁的发生变化,类似于枚举,但又不是枚举,可能是在配置文件中进行配置在运行时动态生成的一些标识符对象,且又具有一定的“行为”,看起来很模糊,即可以定义为实体,也可以定义为值对象。这种情况下“有无‘唯一标识’”就不是划分实体与值对象的唯一标准了,而是从实际情况出发,辩证的进行划分。
基于现有的技术、组件与解决方案,数据的持久化还是离不开数据库,即使是NoSQL的新型的数据库,也会涉及到原子性的问题,由此在系统设计中永远离不开“事务”这样一个原子性集合操作。
领域设计中的事务边界,不应该超出一个“聚合根”,如果超出了一个“聚合根”的范围,则需要考虑聚合的是否合理,是不是少了聚合?或者是不是某个实体或值对象加入了错误的聚合根?如果聚合是合理的,那就考虑是否可以使用领域事件进行解耦?若使用领域事件进行解耦操作,带来的问题就是“以最终一致性代替了事务一致性”,只能算是一个后备的方案。若要保证强的事务的一致性,将事务添加到领域服务甚至是接口级别,也是一种用无可用的折衷方案。
与“实体与值对象的划分”中提到的一样,具体的划分还是要以标准规范为依据,从实际情况出发,辩证的进行事务边界的划分。
领域驱动设计的重点在“设计”而非“领域”,划分领域是手段,实现设计才是目的。领域设计中重要的参与角色有“领域专家”、“开发人员”,应该还包含一个“中间人”。为什么使用“中间人”这个词?在多数公司中,产品经理其实担任了“领域专家”的职能,同时将真正的领域专家——运营、销售、售后等人员——对开发人员隐藏了。开发人员所能接收到的领域专业知识大多都是经过产品经理转述的,所以该作为“中间人”的产品经理前侵成了“开发人员”所面对的“领域专家”。所以就将“中间人”的角色给弱化掉了,经过产品经理的转述,开发人员所理解的领域知识多多少少会有些失真。 要解决这个问题无非三个方法:
需求沟通时,业务、产品、研发一起参与
产品经理成为真正的领域专家
产品经理了解研发的设计
第一个方案不是最优的方案,越多人发言的会就越低效,这会产生更多的时间等成本;第二个方案,就是对产品经理提出了更高的要求,要产品经理深切的参与到运营活动中去,并且要理解未来的业务发展方向;第三个方案就要求产品经理要对研发人员的具体实现有一定的了解,不需要了解到每一个方法、每一行代码,但要了解到系统中有哪些对象、都提供了哪些功能,这对应到领域设计,就是要求产品经理要了解工程中有哪些实体等对象,提供了哪些领域服务,各个领域之间是如何协同工作的。
到此,关于“对领域驱动设计的认识有哪些”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。