-- 知识铺
置顶 A__17 已于 2024-04-08 17:22:44 修改
目录
一、前言
宏观上:
- 互联网业务发展初期“小步快跑,迭代试错”的情形下要求系统需要快速迭代,但是随着互联网公司逐渐深入实体经济,业务日益复杂,我们在开发中也越来越多地遇到传统行业软件开发中所面临的问题。
- 传统的研发模式中,系统分析和设计是分开的,导致需求和成品非常容易出现偏差,两者相对独立,还会导致沟通困难。
- DDD打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。
微观上:
- Action/Service/DAO这种分层架构下,开发模式通常是面向过程、面向数据的。这种开发模式下,对象只是数据的载体,没有行为。整个开发的过程都是以数据为中心,以数据库ER设计为驱动,面向数据进行开发,即数据驱动设计。
- 业务逻辑都是写在Service中,简单的业务系统采用这种贫血模型和过程化设计是没有问题的,但当业务逻辑复杂了,业务逻辑、状态会散落到在大量方法中,原本的代码意图会渐渐不明确,我们将这种情况称为由贫血症引起的失忆症。
- 随着业务的不断发展,业务逻辑变得越来越复杂,系统的实现逻辑也变得越来约复杂,最后的结果就是没有人能够描述清楚每个细节,当对现有功能进行迭代时,光回顾该功能涉及的流程(涉及的改动点)就需要很长的时间,而且还容易出现梳理遗漏的情况。
- 这种情形下,rd往往通过[新写一套逻辑+开关控制]的方式来完成这次的迭代,该功能之后再经历几次迭代后,同一功能在系统中可能存在多个版本的代码,这样就形成恶性循环,每次迭代都需要花费大量的时间去回顾之前多个版本的代码。
- 最终,当迭代的成本超过了重构的成本,rd就开始对该功能进行重构,重构是克服演进式设计中大杂烩问题的主力,通过在单独的类及方法级别上做一系列小步重构来完成,我们可以很容易重构出一个独立的类来放某些通用的逻辑,但是,**重构时会发现你很难给它一个业务上的含义,只能给予一个技术维度描绘的含义。这会带来什么问题呢?新同学并不总是知道对通用逻辑的改动或获取来自该类。显然,制定项目规范并不是好的idea。**我们又闻到了代码即将腐败的味道。
- 更好的做法是采用领域模型的开发方式,将数据和行为封装在一起,并与现实世界中的业务对象相映射。各类具备明确的职责划分,将领域逻辑分散到领域对象中。
其它分享:
- 数据驱动设计 VS 领域驱动设计:领域驱动设计峰会-领域驱动设计大揭秘
二、DDD是什么?
- DDD(Domain-Driven Design):领域驱动设计是一套应对复杂软件系统分析和设计的建模方法论。
三、DDD的职责:
复杂系统的应对:
- 系统的复杂度越来越来高是必然趋势,原因可能来自自身业务的演进,也有可能是技术的创新,然而一个人和团队对复杂性的认知是有极限的,就像一个服务器的性能极限一样,解决的办法只有分而治之,将大问题拆解为小问题,最终突破这种极限,架构设计的4个层面:
- 业务架构——根据业务需求设计业务模块及其关系 ,关注系统的功能。
- 系统架构——设计系统和子系统的模块,关注系统的整体性能。
- 技术架构——决定采用的技术及框架,关注系统的实现方案(框架/缓存/..)
- 工程架构——代码的分层设计
微服务架构:
- 应对系统架构、技术架构上的挑战(主要是可用性/性能),通过注册中心、负载均衡、限流、熔断等方案来应对。
DDD架构:
- 应对业务架构、工程架构上的挑战:通过战略建模和战术建模的方式将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。
四、DDD相关概念:
领域
概念:
- 业务范围以及在其中所进行的活动称为领域。
分类:
- 领域可以划分为子域,在领域划分过程中,会不断划分子域,子域按重要程度会被划分成三类:
- 核心域:核心竞争力,业务成功的主要促成因素。
- 通用域:不是核心,被整个业务系统使用。
- 支撑域:不是核心,可以通过购买满足需求。
注意:
- 在建设一个领域模型时,我们通常只关注核心业务(核心域),而不是试图创建一个全功能的领域模型,因为那样是十分困难的并且还容易导致建模失败。
限界上下文(Bounded Context)
概念:
- 业务流程的实现方案/业务问题的解决方案(eg:软件系统等)称为限界上下文。
作用:
- 限界上下文明确了领域模型的边界。
统一语言
-
定义上下文的含义
-
价值是可以解决不同角色(RD、PM、QA)间的交流障碍
五、DDD的实现:
战略建模
内容:
- 于高层次、宏观上去划分和集成限界上下文。
划分限界上下文
- 每个限界上下文专注于解决某个特定的子域的问题,每个子域都对应一个明确的问题,提供独立的价值,每个子域都相对独立,故各个限界上下文之间也是相对独立的。
限界上下文之间的映射关系(Context Mapping)
- 合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。
- 共享内核(Shared Kernel):两个上下文依赖部分共享的模型。
- 客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。
- 遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。
- 防腐层(Anticorruption Layer):一个上下文通过一些适配和转换与另一个上下文交互。
- 开放主机服务(Open Host Service):定义一种协议来让其他上下文来对本上下文进行访问。
- 发布语言(Published Language):通常与OHS一起使用,用于定义开放主机的协议。
- 大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。
- 另谋他路(SeparateWay):两个完全没有任何联系的上下文。
战术建模:
这篇文章不错:DDD 战术设计
内容:
通过模块、聚合、实体、值对象、领域服务、领域事件等对象来细化限界上下文。
模块(Module):
- 是一种控制限界上下文的手段,在工程中我们一般使用一个模块来表示一个领域的限界上下文。
- 一般的工程中包的组织方式为{com.公司名.组织架构.业务.上下文.*},这样的组织结构能够明确的将一个上下文限定在包的内部。
实体(Entity):
- 当一个对象由其标识(而不是属性)区分时,这种对象称为实体。
值对象(Value Objects)
- 当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象。
- 在实践中,需要保证值对象创建后就不能被修改,即不允许外部再修改其属性
聚合(Aggregate)
- 聚合是一组相关对象(实体、值对象)的集合,作为一个整体被外界访问,聚合是为了保证领域内对象之间的一致性问题。
- 如何创建好的聚合?
- 聚合边界内必须明确有哪些信息,如果没有这些信息就不能称为一个有效的聚合。
- 设计小聚合:
- 大部分的聚合都可以只包含根实体(Aggregate Root),而无需包含其他实体。
- 即使一定要包含,可以考虑将其创建为值对象。
- 通过唯一标识来引用其他聚合或实体:
- 当存在对象之间的关联时,建议引用其唯一标识而非引用其整体对象。
- 如果是外部上下文中的实体,引用其唯一标识或将需要的属性构造值对象。
- 如果聚合创建复杂,推荐使用工厂方法来屏蔽内部复杂的创建逻辑。
- 边界内的内容具有一致性:
- 在一个事务中只修改一个聚合实例。
- 如果你发现边界内很难接受强一致,不管是出于性能或产品需求的考虑,应该考虑剥离出独立的聚合,采用最终一致的方式。
- 常见场景:一个主记录对应多条明细记录的场景。
- 设计方案:
- 若可以有效控制明细数量,且明细数量较小时,主记录和明细记录可以设计为一个聚合。
- 若明细数量可能很大时,考虑到性能的问题,我们应该将主记录和明细记录设计为两个聚合,当明细记录更新后发布领域事件,主记录根据明细更新事件做出相应的修改,这样就可以保证主记录和明细记录之间互不依赖。
领域服务(Domain Services)
- 业务逻辑优先在聚合根边界内完成;聚合根中不合适放置的业务逻辑才考虑放到 DomainService 中。
领域事件(Domain Events)
- 领域事件是对领域内发生的活动进行的建模。
代码分层架构:
DDD落地应对的挑战:
参考:
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240627/DDD-%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1_ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91---%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com