1.学习背景
最近在翻阅博客园的文章的时候,不断的有这么几个字冲击我的眼球"领域驱动模型","DDD"等相关字眼。对此产生浓厚的兴趣,想要深入了解下,这些高大上的名词背后究竟有什么东西让人如何着魔!学习书籍《实现领域驱动设计》,开篇先对领域驱动设计的一些基本词汇进行解释下,就如同武侠小说一般,先对主角的武功进行一些简介。让我们一起探索领域驱动的世界!
2.什么是领域驱动设计
2.1.定义
领域驱动设计(Domian Driven Design,简称:DDD)是一种软件开发方法,请注意它的修饰词,仅仅是一种方法。它可以帮助我们设计高质量(可测试的、可伸缩的、组织良好)的软件模型。
2.2.误区
DDD首先并不是关于技术的,而是关于讨论、聆听、理解、发现和业务价值的。DDD是关于某个特定业务领域的软件模型,通常是通过对象模型来实现。这些对象同时包含了数据和行为,并且表达了准确的业务含义。
3.为什么要学习领域驱动设计
使领域专家和开发者在一起工作
准确的传达业务规则
可以帮助业务人员自我提升
对于知识的集中
领域专家、开发者及软件本身之间不存在"翻译"
设计就是代码,代码就是设计
DDD同时提供了战略设计和战术设计两种方式。战略设计帮助我们理解哪些投入最重要等等,战术设计则帮助我们创建DDD模型中的各个部件
4.领域、子域和限界上下文
4.1.领域
从广义上讲,领域即是一个组织所做的事情以及其中所包含的一切,每个组织都有它自己的业务范围和做事方式,这个业务范围以及在其中所进行的活动便是领域。当你为某个组织开发软件时,你面对便是这个组织的领域。
4.2.子域
在DDD中,一个领域被分割为若干的子域,领域模型在限界上下文中完成开发
4.3.限界上下文
限界上下文是一个显式的边界,领域模型便存在这个边界之内。然而限界上下文并不旨在创建单一的项目资产,它并不是单独的组件、文档、或者框图。举个例子来形象的说明下限界上下文的含义。例如一个账户模型在银行上下文(Banking
Context)和博客园上下文(Cnblog Context),其中在银行上下文中,账户表示的是客户在银行中的收入及支出的信息。而博客园上下文,是文章作者记录自己的工作心得或学习心得的记录。这样得出一个事实:上下文才是王道!
这样的好处就是我们安全没有必要在制定命名的时候刻意与其他的上下文不同。如同上面的例子我们没有必要命名成:BankAccount、CnblogAccout来进行区别。
4.3.1协作上下文
业务协作工具对于创建一个协作式工作环境来说非常重要,有助于增加工作效率,促进观点共享。
而在进行任务的分割的时候需要把模型的职责划分清楚,例如具体示例中身份验证模块不能放入商品模块中,会员注册的逻辑也不能放在核心模块。尽量在对业务逻辑分析清楚的时候把对应模块的职责也划分清楚。
4.3.2敏捷项目管理上下文
在具体示例中,商品模块属于敏捷项目管理上下文,因为在经营的过程中,有可能会对商品进行促销及相对应的优惠或者不同会员VIP的折扣等等。所以,商品模块需要使用Scrum作为迭代和增量式的项目管理。这样就能保证对待定项的评估来对业务价值的分析来确定
5.小结
这篇文章只是简单的对领域驱动设计有个简单性的理性认识,知道这是什么,里面大概有什么内容,至于领域驱动的设计里面核心的部分例如:上下文映射图、架构、实体、值对象、领域服务、领域事件、模块、聚合等等一些概念,我会继续在接下的时间内去学习并写成博客,形成一个系列。时间不早了,各位早点睡觉吧。
学习领域驱动设计(二)之上下文映射图及架构
1.上下文映射图
1.1上下文映射图为何如此重要
当项目中开始采用DDD时,首先你应该为当前的项目绘制一个上下文映射图,该图主要描述当前项目中的限界上下文之间的集成关系!而上下文映射图的作用就是帮助我们从解决方案空间的角度来看待问题。(限界上下文已在上篇文章中介绍了)
U表示上游(Upstream)、D表示下游(Downstream)
1.2绘制上下文映射图
上下文映射图表现的是项目当前的状态,如果项目在将来发生变化,你可以到那时才对上下文映射图做相应的更新,关注当前的项目状态可以帮助你了解你所处的位置,并帮助你决定如何走出下一步。上下文映射图并不是一种企业架构,也不是系统拓扑图!上下文映射图展示了一种组织动态能力(Organizational
dynamic),它可以帮助我们识别出有碍项目进度的一些管理问题。
这里介绍一个有意思的名词:大泥球(Big Ball Of Mud),当我们检查已有的系统时,经常会发现系统中存在混杂在一起的模型,他们之间的边界是非常模糊的。此时你应该为整个系统绘制一个边界,然后将其归纳在大泥球范围之内。往往在我们所在的项目中,经常是项目版本的迭代的时候出现这样的情况,导致后期维护代码越来越困难。所以我们需要在迭代的时候不断的对问题空间进行评估!
举个上下文映射图的例子:例如某网站的电商网站,身份认证模块与商品模块(涉及商品的购买加入购物车功能)。根据以上的信息构建一个上下文映射图。
会员模块/身份认证/商品模块,这三个不同的模块若是三个不同的小组同时去开发,那么这个会员账户模块作为上游,则需要考虑其他两个模块。那么则会员模块则定义一个接口,让其他两个模块通过接口来查询会员账户信息。这种方式叫做:开放主机服务:该模式可以通过REST实现,亦可以通过消息机制实现。
而会员会员接口信息的发布涉及到发布语言,常见的有XML、JOSN。而发布语言可以使用事件驱动架构。
身份认证的这块的防腐层的含义就是我这边的身份验证逻辑不能完全的依赖于上层。若完全的依赖的话就导致上层的更改,身份认证的逻辑就需要不断的变化。
2.架构
2.1简单描述
在没有具体的功能需求时候,我们是不能对软件的质量做出评估,也不能做出正确的架构选择。所以没有最好的架构模型,只有合适的架构模型。
2.2分层
分层架构是所有架构的始祖。它支出N层架构系统,因此被广泛的应用于Web、企业级应用、桌面应用等等系统中。例如我们熟知的三层架构。在分层架构中,我们将领域模型和业务逻辑分离出来,并减少对基础设施、用户界面甚至应用逻辑的依赖。传统的分层架构:用户接口层(User
Interface)、应用层(Application Layer)、领域层(Domain Layer)、基础设施层(Infrastructure
Layer)。分层重要的原则就是:每层只能与位于下方的层发生耦合。
用户界面只用于处理用户显示和用户请求,它不应该包含领域和业务逻辑。可能有这样的误区:用户界面需要对用户的输入进行验证,那么它就应该包含业务逻辑。事实上,用户界面所进行的验证和对领域模型的验证是不同的,用户输入的验证只是领域的模型的验证。而业务逻辑可能是用户购买多少金额而立减多少这样的业务逻辑。
应用服务是位于应用层中的,应用服务可以控制持久化事务和安全认证。最佳方案就是应用服务使用工厂模式或者聚合函数实例化对象。
2.3改进版分层—依赖倒置原则(Dependency Inversion
Principle)
1.高层模块不应该依赖于底层模块,两者都应该依赖于抽象
2.抽象不应该依赖于细节,细节应该依赖于抽象
应用层可以采用不同的方式来获取这些实现,包括依赖注入(Dependency Injection)、服务工厂(Service
Factory)、插件(Plug In)
2.4六边形架构
六边形架构又称:端口与适配器,是一种具有对称性特征的架构风格。不同的客户通过"平等"的方式与系统交互,当产生一个新的客户的时候,只需要添加一个新的适配器就可以了。
2.5面向服务架构
面向服务架构(Service-Oriented Architecture)有以下服务设计原则
1.服务契约
2.松耦合
3.服务抽象
4.服务重用性
5.服务自治性
6.服务无状态性
7.服务可发现性
8.服务组合性
2.6REST
在开放主机服务中可以使用REST,REST是Web架构的一种架构风格,准确的说REST是构建Web服务的一种方式。
2.7命令和查询职责分离—CQRS
在项目我们通常创建一个实体Model,该Model可能既用于插入又用于显示。这时候就有一个问题,那就是当业务需要要求显示的展示不同的时候,那么我就需要修改该Model,可能修改的时候还会影响插入功能。这时候我们就需要考虑命令和查询分离。
一个方法修改了对象的状态,该方法成为命令(Command),一般该方法返回void 。如果一个方法返回了数据,该方法便是一个查询(Query),该方法返回数据类型。命令模型(Command
Model )一般包含add()、save()及主键查询方法。而查询模型(Query Model)则创建就是我们需要显示的模型实体。查询模型并不是一种规范的数据模型,它并不是反映领域行为,只是用于数据显示。
2.8事件驱动架构
事件驱动架构(Event-Driven Architecture)是一种处理事件的生成、发现和处理任务的软件架构。其中事件驱动最简单的实现就是管道和过滤器的组合。管道模式的升级版本就是"长时处理过程"。
设计长时处理过程的三个方法:
1.将处理过程设计成一个组合任务,使一个执行组件进行跟踪,并对各个步骤和任务完成情况进行持久化
2.将处理过程设计成一个组合,这些聚合在一系列的活动中相互协作,一个或多个聚合实例充当执行组件并维护整个过程的状态
3.设计一个无状态的处理过程,其中每一个消息都对所接受到的消息进行扩充—即向其中加入额外的信息—然后再将消息发送到下一个处理组件。
3.总结
第二点的介绍的架构里面,当然还有本文中没有介绍到的架构,其中介绍的到也只是一些简单的定义描述,具体的怎么使用还是需要看实际的需求。而我认为目前需要掌握的就是命令与查询职责的分离。对于一个业务项目经常变化的来说的项目,命令与查询职责的分离能够很好的减少后期的代码的维护,也比较灵活的适合业务的发展的需求。好了,时间也不早了,今天文章先写到这里。下篇将介绍实体。
学习领域驱动设计(三)之实体
当我们在考虑一个对象的个性特征,或者需要区分不同的对象时,我们需要引入实体的概念。
一个实体是一个唯一的东西,并且可以在相当长的时间内持续发生变化。我们可以对实体进行多次修改,故实体对象可能和它先前的状态不大相同。但是,由于它们拥有相同的身份标识(Identity),它们依然是同一个实体。
唯一的身份标识和可变性是实体对象与值对象的根本区别
1.唯一标识
在设计实体时,我们首先需要考虑实体的本质特征,而不是一开始便关注实体的属性和行为。找到能够实现唯一标识性的方式,同时还要考虑如何在实体的生命周期内维持它的唯一性。
唯一标识用于实体实体匹配通常取决于标识的可读性。
1.1生成唯一标识的方式
1.用户提供唯一的标识
让用户手动输入对象标识看起来是一种很直接的做法,但是这种方式也可能变得很复杂。复杂性在于:1.需要用户自己生成高质量的标识,有可能输入的标识是唯一性,但也有可能是不正确的。
2.应用程序生成唯一标识
有很多可靠的方法都可以自动生成唯一标识,但是如果应用程序处于集群环境或者分布在不同的计算节点,我们就需要小心。以下方式可以生成1.计算机节点的当前时间,以毫秒记
2.计算机节点的IP地址 3.GUID
3.持久化机制生成唯一标识
4.另一个限界上下文提供唯一标识
5.委派标识
委派标识说白点就是数据库的自增主键,利用自增主键的特性生成唯一标识。在开发过程中我们大多应用场景都是采用这个方式来生成唯一标识来关联实体。
1.2发现实体及本质特征
1.2.1角色和职责
建模的一个方面是发现对象的角色和职责,通常来说,对角色和职责分析师可以应用在领域对象上。
领域对象扮演多种角色
在面向对象编程中,通常由接口来定义实现类的角色。在正确的设计的情况下,一个类对于每一个它所实现的接口来说,都存在一种角色。如果一个类没有显式的角色即该类没有实现任何显式接口,则默认情况下,它扮演的是本来的角色。
Public interface User{}//User对象
User类没有实现任何接口,但是它仍然扮演的是User角色。
1.2.2 验证
验证的主要目的在于检查模型的正确性,检查的对象可以是某个属性,也可以是整个对象,甚至是多个对象的组合。
1.验证属性,在C#中又称契约验证
2.验证整体对象
有时候实体中的所有单个的属性都是合法的,但是并不意味着整个实体就是合法的。要验证整个实体,我们需要访问整个对象的状态即所有对象的属性
3.验证对象组合
在需要对复杂对象进行验证时,我们可以使用延迟验证。因为有时候我们关注的并不是单独的实体的是否合法,还需要关注多个实体的组合是否合法。
|