作者:anders小明
2009年5月7日
需求背景
现在的样子
如PoEAA中提到的:事务脚本(Transaction Script)和 表模型( Table Moduel)模式。
存在问题
事务脚本看到的是零散的数据,而 表模型 混合了下文要说领域模型和领域服务界限。
1. 两者都导致了分析和设计的割裂,领域模型只存在于分析中;数据间的内在关系无法通过代码体现;
2. 两者都无法有效的实现业务逻辑的具体的差异化和抽象的一致性;导致学习和维护成本的增加;
3. 两者都带来了测试上困难,增加了开发成本;
为何要面向对象?
1. 面向对象是自上而下的开发方法;这种方式对于迭代增进式的结构化过程来说成本是最低的;
2. 数据和业务逻辑的绑定性,采用面向对象容易操作和沟通(即实施成本低),同时有助于区分状态逻辑和任务逻辑;
3. 业务逻辑的差异性,同一个业务逻辑针对差异性数据有区分做法,采用面向对象容易维护;
什么是领域驱动设计
领域驱动设计的提出是由Eric Evans在其《领域驱动设计》一书提出。实质上是一种由内而外的设计方法,俗话说的先中间(模型和服务)后两边(界面,数据库以及集成)。
领域驱动设计的优势
传统的开发方式:基于数据库的设计开发。数据库提供的设计模型是表和字段两种粒度,这两种粒度有时并不合适于系统设计:
1. 模型的结构化能力
1.1. 同一组件下的设计优势,一个模型可以来自多张表的数据聚合而成,一张表可以聚合多个模型;一个逻辑是由几个固定字段或者非固定字段聚合;模型间的关联关系也是使用表无法展示的——外键的约束对于系统开发来说实在太有限了。而这些不论表还是字段粒度都无法支持的。
这里强调一下模型间的关 联关系,特别是和生命周期相关的聚合和组合关系。关系数据库中所保存的是系统分解后的表示,即关系被分解了而不是被表达了,外键对数据关系只起到一种约束 作用,它对于查询语句的构建并没有直接的影响。所有数据之间的关系都必须在查询的时候明确指定出来,即调用者必须拥有数据关系的知识,而不是数据本身拥有 这些知识.在如下的SQL语句中
“select * from a, b where a.fldA = b.fldB and a.fldC = 1 and b.fldD = 2”
其中,a.fldA = b.fldB 可以称为关联条件,而a.fldC=1可以称作是坐标条件。
SQL 的 复杂性很大程度上来源于我们频繁的需要在各处指定完全一样的关联条件而无法把它们抽象成可复用的组分.在ORM所提供的对象空间中,对象之间的两两关联只 要指定一次,就可以在增删改查等各种操作过程中起到作用,特别是在对象查询语句中,可以通过两两关联自动推导出多实体之间的关联关系,虽然推导出的结果未 必是最优化的.
1.2. 采用模型方式容易解决项目的集成问题(两个组件访问同一张表的情况)
2. 架构的结构化能力
事务脚本直接访问到表。换句话说,其架构只有服务+数据库表,这样的架构下数据库表可以说就是我们的模型。
这里看看在逻辑设计上,领域模型驱动设计对于架构影响
A. 服务, 模型和模型仓库(Repository),模型的重建和关联交由模型仓库完成,单个数据逻辑交给模型处理(支持泛化);
B. 测试的好处;
3. 分析和设计的统一
沟通的问题,客户关注于其业务,分析的模型接近于客户需求,设计也采用模型的方式,避免分析和设计的割裂。有助于开发人员间,开发人员和业务人员以及客户间的沟通
4. 其它好处
4.1. 采用领域模型可以屏蔽持久化信息。持久化设计的表设计是和DBA相关的,DBA对于表设计有权力的。采用模型可以有效隔离各自工作;
4.2. 模型可以通过很多的手段透明的解决性能问题,而采用表做模型导致容易导致性能问题,当然不是没有办法解决,一种是通过DataSet的方式,但这样的切换成本较高。
领域驱动设计的可能
上下文环境
领域模型存在于系统的各个地方,不过在不同地方有不同的映射实现。在通常的开发过程中,该映射存在于文档和开发人员的脑海中的。当要向客户展示时,就面临一个映射的关系。比如要允许客户可以在线编辑页面呈现的显示元素,在规则定义里使用对象系统时。
基础平台
随着ORM框架的发展,如hibernate,可以提供继承多态等能力,能够支持关联关系,特别是面向对象设计中生命周期相关的聚合关系,使得基于领域模型开发在技术上具备了可行性。
当前不足
不过ORM在集合上的处理不尽如人意。受限于JDK的集合,不能像DLinq那样提供集合的过滤和集合函数操作。使得一些设计,不得不屈从于性能问题,把领域模型的关联关系人为断开。
Ferrum 项目或许会是一个可行的方案。
领域驱动设计的技术分析
概述
整体结构
领域分两个含义:领域模型,领域服务,业务规则 和Repository;其中模型的持久化,重建和关联交由Repository完成;而单数据逻辑(依赖自身数据信息以及关联数据)则归于模型(支持泛化);服务则关注于任务处理,包括了多个模型处理,以及其它服务的调用。
如果把一个系统看作是一个机械组件的话,那么领域模型就相当于人的骨架;而流程逻辑相当于骨架上的肌肉;那么控制逻辑就相当于肌肉中的神经。
领域模型
概念上,一个领域模型和 普通的符合面向对象原则的对象有声明区别:领域模型是业务意义上,承载了业务数据(可以认为所有领域模型是有状态对象),从本质上说它直接来源于现实世 界,没有技术层次上的考虑,“符合面向对象原则的对象”是用面向对象方法分析得到的,是基于计算机领域技术的(这样的对象可以是无状态的);但反过来,符 合面向对象的对象不一定反映业务领域的模型。
技术上,领域模型是指那些包含需要被透明持久化的属性 ,以及相关业务逻辑 的 POJO 。一个领域模型包含了这些需要被持久化的业务数据,同时还包含了与之相关所有业务操作(即能操作所有属于本模型生命周期内的模型数据 ),并且有自己的继承体系。 Martin Fowler认为有了这些就可以称为是一个领域模型,因此在其 PoEAA中的 ORM包含了一些不透明的持久化方案。我认为一个真正的领域模型需要一个透明持久化。
注: 领域模型在不同视图下导致不同的内容。比如一个代理人 Agent对象,在 Party的视图下只拥有基本属性,而在 Sale channel视图下就保存了一些额外信息如:考核记录,优秀率等。
领域服务
领域服务包含的商业逻辑包含了两部分:流程逻辑。业务领域的流程逻辑(即业务流程)。指一系列的业务行为,包括维护一个或者多个领域模型。领域服务是一个 Unit Of Work模式。
领域模型中逻辑仅包括自身生命周期关联数据的操作,相当于原子操作 ;只有领域服务则代表完整的服务操作边界 ,例如一个领域服务会包含对一个领域模型的三个调用;这一边界通常也是事务的控制边界 ;
Rod Johnson 在《J2EE without EJB》第10章《持久化》里面比较清楚的论述了这个问题:
Workflow methods at the business facade level are still responsible for transaction demarcation, for retrieving persistent objects in the first place, and for operations that span multiple objects, but no longer for domain logic that really belongs to the domain model.
这段话明白无误的讲清楚 了领域模型应该包含什么逻辑,不应该包含什么逻辑。领域模型包含的逻辑,这里称之为“领域逻辑”,这些领域逻辑应该最小化的依赖于DAO,而我们讨论的那 些需要持久化操作参与的事务性逻辑,这里称之为“workflow methods”,这些“workflow methods”应该放在“business facade”,而不应该放在领域模型里面。
What logic to put into domain classes versus what to put into workflow controllers should be decided case by case. Operations that are reusable across multiple use case s should normally be part of domain classes. Single use case that are hardly reusable are candidates for controller methods; so are operations that span multiple domain objects.
最后Rod Johnson给出来一个区分的“business workflow logic”和“领域逻辑”的准则:视具体情况而定,紧密结合领域模型的,可重用度很高的操作可能是领域逻辑,应该放在领域模型里面;比较难重用的控制逻 辑方法,特别是可跨越多个领域模型的操作则放在business facade object里面。
业务规则
这里单列出来,很多时候业务规则是附属在领域服务中的,但在一些特定项目中,业务规则会被单独维护。
1. 产生一些控制信息,限制或者触发某些行为的执行( A rule is a declarative statement that applies logic or computation to information values);
2. 产生一些状态信息,提供给业务人员参考操作( A rule results either in the discovery of new information or a decision about taking action.)。
其它技术问题
应用职责角色分层,必然涉及到两种对象,一种是用于展示信息的结构—— VO(边界外通过 编码方式 使用的),一种是 DAO对象。这两种职责角色对象,严格的说不算是业务设计需要关心的。然而却和系统开发息息相关。而业务设计中的变化导致相关的工作量却是巨大的。
VO 对象是为了集成而存在的;其意义是:1. 保护系统的信息边界,提供一种结构可以使其它系统或者组件通过编码方式 获取系统内信息的方式;2. 保护系统的事务边界,领域对象技术上携带着持久化信息,通过VO得以屏蔽。常见的VO对象存在于Web层和Domain层。
因此,VO对象的存在只是为了集成而存在,其是否存在的取决于两个方面1. 集成的设计结构;2. 框架的两个能力——对象路径访问能力以及事务边界管理。
Domain 层VO对象,通常是用于不同领域组件间的交互,但随着架构的改进,集成代码独立存在而不再嵌入到组件内部,组件的边界问题保护不复存在;更进一步的是,框架提供自动化的接口适配映射能力的增强。因而VO对象失去存在的意义。
Web 层 VO对象,以SWF为例,早在SWF 1.x时代,框架就提供了丰富的对象路径访问能力,但其Web交互是典型的MVC2方式,事务边界在view的render前关闭,因而导致需要特定的 VO对象来避免持久化信息问题;而SWF 2.x时代,view的render是在事务边界内,VO不 再 需要。
针对维护 Dao对象而产生成本的一种解决方法是:代码生成。生成的策略分为两种环境,开发环境和生产环境。开发环境实时动态生成,可以采用动态代理机制;而生产环境要求性能,采用预编译生成能力。
另外 Web层或者 UI层,不需要额外的 VO对象的另一个理由是:通常 Web层的独立维护的成本大于其复用的价值。在开发中, Web层需要的信息都来自 Domain层,这样容易出现 Web层的 VO对象和 Domain对象结构一致,虽然这样便于学习和简化开发,但导致维护成本的提高,每当页面需要一个新的属性就需要改变太多的类,同时 Domain对象设计容易限制于 Web层,而实践中 Web层的变化更多于 Domain层。正确的路径是很好的维护 Domain层,同时不维护 Web层。
设计开发
领域模型的四种类型
领域模型可以分为四种类型:
0. 全局常量对象
1. 长生命周期业务对象(类似保单对象);
2. 交易过程的事务对象,几乎没有生命周期的;
3. 业务请求对象和业务反馈对象。这类对象以前没有识别的,通常和VO混在一起;但是在IAA中以及电信业的模型是这类对象是独立存在,并被持久化的;业务请求对象建立在增量更新上很有用。当然他们也是几乎没有生命周期的。
并非所有的业务系统都拥有这四类对象!相当一部分的业务系统,并没有显著的长生命周期对象,因而没有明确的增量变更操作类型及其规则,业务操作是直接更新业务对象,也就没有业务请求以及业务反馈对象;同时此类业务系统的事务对象也通常不存在;
这里要额外补充说明的是:
对于 业务请求 ,每个 业务请求 必需记录下业务时间;对于 业务处理 ,每个 业务处理 还可能保留一定的人工干预控制信息,也将同生命周期的输入数据一起记录;对于生命周期,每个生命周期状态的变化都可能会有独立的数据需要记录。
领域模型的级别
不论是那种类型对象,都拥有一个属性,对象等级;对于保险系统来说,保单对象,产品对象以及组织对象是一级对象,而险种和角色等都是二级对象,其生命周期附属于一级对象;这点对于设计Repository以及服务粒度都有影响。
领域模型 的动静之分
在系统运行期间,被频繁建立和更新的称为“动态”,而在较长的一段时间内保持稳定的称为“静态”。
通常而言,“动态”的领域模型群通常代表了系统的核心业务对象。而“静态”的领域模型则在业务上代表了系统的依存关系。
领域模型的分析过程
1. 设计一个贫血的领域模型,包含所有需要被持久化的数据,除了用例显示要求的和隐式要求的(根据业务分析出需要的辅助数据,主要是特定的控制信息),以及与其它领域模型的关联关系;
2. 考虑是否支持多态
3. 分析潜在的性能问题,根据需要人为断开存在的关联关系;
4. 列出相关的逻辑,利用面向对象设计原则,决定哪些逻辑属于该领域,并加入该对象模型;
5. 分析是否实现该对象的依赖关系注入。
业务请求对象的虚实之道
业务请求 的概念 ,与HTTP请求是不同的。为避免误解,特意加上业务一词修饰。所谓虚实是指是否将业务请求概念实例化。不做实例化的理由时处理简单;实例化则有助于处理业务事务控制以及应用 账目模式。一个业务操作上的 业务请求 可能包括多个 请求对象,与核心业务对象对应, 例如 : 在线订单 , 就包括了购买物品及其数量和折扣 , 支付协议和发货协议等。
对于没有实例化 业务请求 的情况下 , 在实际业务操作时 , 对每一个HTTP 表单 的操作都需要一个物理的 事务 来支持。这样做的问题是 ,由于 没有记录 业务请求, 直接操作业务对象 , 在做业务日志时只能记录操作前以及操作后的信息 ( 既 “ 减肥前 , 减肥后 ”); 同时 跨越 多个 物理事务,要支持 查询到一次 业务请求 所有操作的信息 ,需要新建一个日志索引或者类似的手段,在业务开始时获取注册一个索引,所有日志操作中引用这个索引,在业务结束后关闭该索引。虽然如此,也 带来的是业务上做 回退(undo) 以及重做( redo ) 操作的不便。
但是如果实例化 业务请求, 就很容易处理这两样操作。建立一个 业务请求, 同时记录所涉及的 请求数据 。这样做的好处是:可以很容易的记录一些额外的信息;同时可以很容易的支持审批操作(既俗话说的“管帐的不管钱,管钱的不管帐”)。
另 外,业务请求对象附加好处是,由于某个领域模块的增量操作通常从一个根对象(即一级对象)开始,所依赖的过滤条件可以从业务请求中加以识别并通过框架提前 加载,而领域服务对象的方法接受传递对象而不再关心对象的加载工作;同时也可以通过框架处理基本数据复制工作,这样程序只关心关联对象的操作。
业务请求对象 比较适合明确增量变化的业务系统,通常这样业务系统在处理变更时,规则和关联处理很多;对于全量数据,采用 业务请求对象 得不偿失,最终两个对象设计会趋于一致;对于不确定性变化系统,如果应用 业务请求对象 会导致捕获变化数据的困难,应该考虑采用更好的业务组织方式(如SpringSide的设计实现,但SS没有考虑批改的流程和日志)。
不过目前大部分的系统都没有处理业务请求实例化,不是所有的业务操作需要审批,另外实例化的麻烦是,已经处理了一个日志对象,再处理一个业务请求对象总是让人多少心里有点不爽;
一个可能的持久化方式是:记录对象路径及其对应的值;通过自动化的系统扫描,获取对象路径结构,以Key和Value的形式记录在数据库中,可以避免复杂的持久化处理;
Repository (Dao)的设计开发
Repository 是一种特殊的Service,不做任务处理;而是提供模型的持久化,重建和查询工作。由于企业应用大都通过数据库实现持久化,因而Repository和传统的Dao间的集成设计就非常重要。
已知的有三种设计方式:
方式 |
描述 |
优缺点 |
1. 两个接口两个实现类 |
Repository 和Dao各自独立接口,而通过Repository实现类转发请求给Dao实现类 |
这种方式虽然正统,但是维护成本太高;一次更新最多要改四处地方 |
2. 两个接口一个实现类 |
Repository 和Dao各自独立接口,一个实现类同时实现两个接口 |
这种方式就大大简化维护成本;一次更新最多只改一个接口和一个实现类 |
3. 两个接口一个实现类 |
与方式2不同是,Dao接口继承Repository接口,一个实现类实现Dao接口 |
这种方式的维护成本和方式2差不多,但是当接口方法在这个两个接口间流动时,可以通过开发工具完成 |
另外Dao实现类也是工作中开发维护成本较高的一部分,可以通过代码生成降低开发成本。已知的是JDBC 4.0规范和iBatis 3.0的实践。
领域服务的设计开发
业务服务是整个领域设计中另一个重要的元素;业务服务的如何设计,并无定论,但有原则和分类,最重要的是围绕着业务流程设计,而其基础建立于底层模型自身业务逻辑的原子操作。
按粒度划分:
1. 原子服务,业务服务的原子操作;在产品化设计中,该层次服务应该拥有扩展点和参数化能力;
2. 组合服务,封装业务服务的组合操作;在产品化设计中,拥有参数化,扩展点,事件和集成能力;
3. 还有一类业务服务设计是实现于工作流,该层次逻辑关注于系统集成上;在产品化设计中,该层次应该拥有事件;
实际上,原子服务和组合服务的粒度划分并不具备可操作性;只不过加以标识试图进一步的分析,并为产品化设计做基础;
按事务划分:
1. 事务服务,事务服务和持久化操作有关,提供事务边界;通常是聚合服务;
2. 计算服务,也算是read-only的事务服务;计算服务的粒度不一定;
由于服务是针对领域,因 而事务服务不关注于工作流的流程状态,只关心相关领域中长生命周期领域模型的生命周期;而计算服务更不关心流程相关,只验证输入合法性,做出计算,返回结 果,完全是无状态的;工作流则关心的是相关领域中的request对象的业务状态,对于同一业务对象的并发处理,应该通过业务来控制;
领域服务的运行模式
简单的说,业务处理 将被细化成 处理控制器和具体处理器。 在 这 级别 ,处理 对于 请求 的响应处理已知的有三种 模式:
事件模式(Observer Pattern)、职责链模式(Chain of responsibility Pattern) 以及数据流模式 (Pipes and Filters Pattern)。 这几个 模式 处理的各自不同的 场景。 其中 数据流模式 很适合需要处理大量数据的情况。
相关推荐
领域驱动设计(DDD)是一种软件开发方法,它强调以业务领域为中心进行系统设计,通过创建领域模型来理解和解决复杂业务问题。DDD的核心理念是将业务逻辑转化为可执行的代码,从而确保软件系统能够准确地反映业务需求...
LED在可携式产品中背光源的地位已经不可动摇,即便是在大尺寸LCD的背光源当中,...驱动电路是LED(发光二极体)产品的重要组成部分,无论在照明、背光源还是显示板领域,驱动电路技术架构的选择都应与具体的应用相匹配。
当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决。比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品。...
例如,在一些小型照明设备或装饰灯具中,线性驱动因其简单的设计而被广泛应用。 #### 三、开关型驱动 开关型驱动是一种更为高效的LED驱动方式,它通过控制开关元件(如MOSFET)的导通和截止状态来实现电流调节。...
标题中的“浅谈人工智能驱动的人文社会科学研究转型”揭示了本次讨论的核心——人工智能(AI)在人文社会科学领域的应用及其带来的变革。这篇论文或报告可能深入分析了AI如何改变研究方法、数据分析、理论构建以及...
电力驱动系统电气工程与自动化控制是现代工业和交通运输领域中不可或缺的一部分。随着技术的不断进步,对电力驱动系统的要求也越来越高,尤其在自动化控制方面,PLC(Programmable Logic Controller)技术的应用已...
领域模型是什么?...很多人一上来理解领域驱动设计(DDD),基本都是一头雾水,因为模型设计的初衷并不是围绕性能、架构、分层等软件概念展开的,而是从边界、内聚等抽象概念开始讲起。理解领域模型,并不是通过
【FPGA设计工具浅谈】 FPGA(Field-Programmable Gate Array)作为一种可编程逻辑器件,因其灵活性和高性能在电子系统设计中扮演着日益重要的角色。随着技术的进步,FPGA不仅可作为协处理器和DSP引擎,甚至可以作为...
而且,手术机器人通常是电力驱动,如果散热不及时,可能会导致电池寿命减少,甚至自燃引发爆炸等严重后果。因此,通过设计合适的保险产品可以为这些潜在风险提供保障。 其次,医疗事故责任的判定也是保险设计中的...
文章《浅谈提高机械设计制造及其自动化的有效途径》通过对自动化技术的深入分析,探讨了提高机械设计制造自动化水平的有效方法,并指出了当前机械设计制造及其自动化的发展现状和未来趋势。 一、自动化技术在机械...
同样地,在软件开发领域中,诸如领域驱动设计(DDD)、模型视图控制器(MVC)等模式也被视为特定领域的架构设计。 ### 三、架构的本质 架构的概念最初源自建筑行业,指代建筑物的设计和构造过程。将这一概念应用于...
首先,汽车被定义为由动力驱动,有四个或以上车轮,非轨道承载的车辆。汽车行业的核心部分主要包括整车、汽车零部件和其他相关服务。在整车领域,汽车被分为两大类:乘用车和商用车。 乘用车是主要针对个人或家庭...
综上所述,220V光耦驱动继电器电路是电力控制和自动化领域的一种常见解决方案,通过光耦的隔离作用,确保了控制系统的安全性和可靠性。了解并掌握这种电路的设计原理和实践技巧,对于提升电子工程师的技能水平至关...
5. **数据驱动决策**:在“浅谈品管圈”的讲座中,可能会讲解如何利用数据分析来支持决策,比如通过监控系统日志、用户反馈等数据,发现潜在的问题并采取相应措施。 6. **IT服务管理**:在ITSM(IT Service ...