领域驱动设计并不是新的架构设计理论,从Eric Evans提出至今已经有十多年历史。由于微服务架构的兴起,DDD常用于指导微服务边界划分,并重新广泛进入软件研发大众的视野。DDD的理念及应用普及在国外相对成熟,在国内尚处于初期发展阶段。国内的很多社区以及企业组织内部近几年对于DDD的探讨和应用逐渐火热,许多架构师以及开发人员对DDD充满了学习和实践的热情。而像敏捷一样,不同团队对其认知水平和实践水平不尽相同,有的成功,大多数可能是失败的。
领域驱动设计(Domain Driven Design),简称DDD, Eric Evans 2004年的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中第一次提出。领域驱动设计是一种用于指导软件设计的方法论,也是一种设计思维方式,用于解决软件复杂性问题,旨在加速那些必须处理复杂领域的软件项目的开发。
实践DDD的第一步不在于如何编写代码,而首先需要拉齐对领域驱动设计的认知。后续的系列文章将围绕领域驱动设计进行不同视角探讨,以期帮助大家对其有更深入的认识,并能应用的实际的研发工作中。

问题空间:Problem Space,是当前环境下业务所面临的一系列问题和问题背后的需求,通常是业务和产品领域专家主导问题、需求的收集描述和分析。问题空间框定了我们要解决的问题的上下文,这种上下文环境不是软件工程或是领域驱动所独有的,而是通用的共性的元素。工程实践必然处于某种上下文环境之下。
解决方案空间:Solution Space,解决方案空间是针对问题空间的解决方案,属于工程实现阶段,由技术专家主导方案设计。
软件开发过程,本质上可以看作是问题空间到解决方案空间的一个映射转化:问题空间,找出业务挑战及其对相关需求场景用例分析解空间,通过具体的技术工具手段来进行设计实现
领域:Domain
“领域”是“知识或活动的集合”,相对于软件系统而言,领域就是软件应用所要解决的现实问题区域。领域对应于问题空间,是一个特定范围边界内的业务需求的总和。
领域模型:Domain Model
抽象是一种化繁为简的能力,是人类认识世界的利器,也是一种生物本能。在有限的脑容量的前提下,人类不可能存储记忆所有的细节,海量信息已经超出人脑存储限制而无法容纳和有效获取。抽象使得人类可以屏蔽无关细节信息,抽取高层的有效信息进行记忆存储。试想,如果脑机接口技术有所突破,在人脑背后链接的是海量的高效的计算机集群,这种无限的存储、计算和检索能力的增强,“抽象能力也许会被弱化”。
模型被用来表述人们所关注的现实或想法的某个方面,本质上是一种抽象过程的产物,把与解决方案密切相关的方面抽象出来,而忽略无关细节。
聚焦在软件工程领域,要想构建满足需求的软件系统,开发团队需要软件面向的领域所涉及的知识可能非常庞大和复杂,而模型正是解决这种信息超载问题的有效工具。
对领域进行模型设计的过程就是领域建模,领域建模的目的并非是要建立一个百分之百符合“现实”的模型,理论上,我们也无法实现这种对现实的完全建模,而只能是对现实某种程度的模拟。

领域建模的输出即领域模型,领域模型是针对特定领域里的关键事物及其关系的可视化表现,属于解决方案空间范畴。为了准确定义需要解决问题而构造的抽象模型,为软件系统的构建目标统一认知,是业务功能场景在软件系统里的映射转化。领域模型并非领域专家头脑中的知识,而是对这些知识进行严格的组织和有选择的抽象。
同时,领域模型并非某种特殊的图、文字或者代码,而是他们所传达的思想,图、文字或代码都可以作为模型的表示或传达形式,但他们不是模型,而是不同维度的模型视图。

领域驱动设计强调领域模型的重要性,并通过模型驱动设计来保障领域模型与程序设计的一致。领域驱动设计首先从业务需求中提炼出统一语言,并建立领域模型指导着程序设计以及编码实现;最后,又通过重构来发现隐式概念,并不断解决领域领域模型相关的新问题。本质上,领域驱动设计也是从问题空间映射到解决方案空间。
领域驱动设计结合了宏观和微观两个层面的设计,分别对应于领域驱动概念中的战略设计和战术设计。
战略设计的初衷是要保持模型的完整性,主要从下面两个方面来考量的:
战略设计的初衷是要保持模型的完整性,通过战略设计将整个软件系统分解为多个限界上下文,然后对每个界限上下文进行战术设计。对每个限界上下文进行战术设计。Eric Evans提供的模型驱动设计的构造要素以及要素间的关系如下图所示:

上述DDD战术设计的模式标识了进行设计时的一些关键模式,但并非说是一定要严格使用和遵循的,也不是遵循了所有的战术设计模式就是符合领域驱动设计。因为,实践DDD关键不在于这种战术层面模式的落地,而是在于其宏观的领域驱动设计思想的遵循,比如统一语言、领域模型与代码间的一致、子域及上下文的拆分以及映射、领域模型与技术关注点的分离等等。另外,随着DDD的不断发展,一些新的构建模式已经涌现,老的构造模型不一定能符合团队研发的要求。
领域驱动设计实践依赖不断迭代和持续集成来构建高可扩展的项目,但是这种基于迭代和持续集成的时间,在某些团队中落地可能会存在阻碍,特别是如果过去经验是建立在僵化的开发模型上,比如瀑布模型。
领域驱动设计适合于具有非常高领域复杂性(业务逻辑复杂)的应用,但不适用于领域复杂性很低但技术复杂性很高的领域。DDD着重强调需要领域专家以便构造出项目依赖的统一语言和领域模型,但是如果项目的技术复杂性很高,领域能否理解是一种挑战。当全体团队成员没有完全理解技术需求或限制时可能会导致问题
团队对领域驱动设计的认知不够,精力没有聚焦在问题域拆分、统一语言、模型与技术关注点分离等核心原则上,而是一开始便从实现的角度触发,过度强调战术设计模式的落地,从而陷入无尽的技术细节之中
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?