首页 - 信息 - DDD 战术:领域模型的应用

DDD 战术:领域模型的应用

2023-10-02 05:15
领域驱动设计 DDD 提供了战术建模(以下简称建模,除非另有说明)中的元模型系统(如下所示)。通过这个元模型,我们将策略建模过程中识别出的问题子域进行抽象,并用抽象来指导解决方案的实现。 (DDD构建的元模型元素脑图) 我们这里所说的战术阶段其实就是这样一个抽象的过程。由于元模型的存在,这个抽象过程实际上在一定程度上被模式化了。这样做的好处是不仅技术人员可以参与建模,而且业务人员经过一定的培训后也能完全理解。在带领很多团队实践建模的过程中,我也要求业务人员参与战术设计。 由于已经有很多书籍介绍 DDD 的元模型,因此这里不再赘述,而是谈谈人们在这个抽象过程中经常遇到的一些困惑。这些比较常见的问题可能需要在DDD元模型未来的演进中得到解决,但我们仍然需要关注业务问题和架构设计的多样性,不要过度标准化到走得太远的地步。 业务对象抽象 通过将业务问题划分为子域,我们发现了一些关键的业务对象。开始抽象之前的必要步骤是“讲故事”! 你应该讲什么故事?关于该子域解决的业务问题或其提供的业务功能的故事。既然是故事,就必须有清晰的业务场景以及业务对象之间的交互。这件事看起来是那么的自然和简单,但是一个团队里能站出来有条不紊地解释清楚的人并不多。读到这里的读者不妨停下来尝试一下,你能用两三分钟描述一下你现在正在做的业务吗? 这样做的明显目的是让我们更全面地思考我们想要细化和抽象的业务对象。只有能够清晰地“讲述”业务场景,我们才可以开始抽象步骤。对于业务对象,我们常见的抽象可以是“实体”和“值对象”。 这两种抽象方法在定义上的区别在于,实体需要被赋予唯一的标识,而值对象则不需要(可以通过属性集合来标识)。当然,经常被引用的另一个区别是实体应该具有连续的生命周期。例如,如果我们在订单追踪领域将订单抽象为一个实体,那么每个订单都应该有一个唯一的标识号,订单也应该有一个唯一的标识号。从订单创建到最终交付完成的生命周期。 显然,如果不加上其他约束,值对象的抽象是没有意义的。我们不能只使用实体吗?但如果我们思考一个实体的管理成本,比如我们需要保证生命周期内实体状态的一致性,那么我们会发现值对象变得非常简单可爱。当一个对象在我们的(抽象)世界中无法改变时,一切都变得简单。该对象只有在创建后才能被引用。当没有引用时,我们可以将其交给垃圾收集来自动处理。 随着高并发和分布式系统的流行,其实我们思考业务对象抽象的第一步就是能否使用值对象。如果你实现的技术架构采用函数范式语言(类似于Closure),那么首先考虑值对象抽象可能是一个建模原则。 对象抽象初步完成后,我们必须重复前面的故事来回顾我们的建模。经过这个抽象过程后,参与讨论的每个人都应该发现自己对业务需求以及需要提供哪些功能有了更清晰的了解。 聚合封装 DDD元模型中的一个核心概念称为“聚合”。这个来自建筑的名词非常形象。在建筑学中,我们将其翻译为“聚合”。它是混凝土形成的重要元素,也是混凝土如此坚固的基础。 (混凝土中的骨料) 同样,在DDD建模中,聚合也是我们构建领域模型的基础,每一次聚合都是一个高度内聚的组合。聚合本身就完成了我们对骨干业务规则的封装,减少了我们实施过程中出错的可能性。 以上面的订单跟踪字段为例,假设我们允许一个订单下存在多个子订单,并且每个子订单也可以独立发货。在本例中,我们抽象了实体“子订单”。显然订单和子订单之间的业务逻辑是一致的。没有订单时不应创建子订单。更新子订单时,应同时“通知”相应的订单。这时候就需要使用一个聚合订单和子订单的包。 使用聚合抽象的结果是访问每个子订单都需要相关的订单条目(即订单是聚合根)。访问时,我们以这个聚合为基本单位,其中包括订单以及该订单下的所有子订单。 。显然,这样做的好处是,在订单跟踪的领域模型中,订单作为聚合存在。我们只需要一次性理清订单和子订单之间的逻辑关系,以后就不需要考虑每个引用中的业务规则了。 。 (订单追踪字段中的订单聚合) 许多团队在建模过程中并没有努力考虑聚合的存在。封装作为技术实现领域的一个基本原则,在建模时却很少被认真对待。一开始就提到,战术建模过程中强调业务现场人员的参与,也是为了解决这个问题。聚合的标识实际上是为了业务规则的封装。当我们不了解业务规则时,我们无法判断是否封装。 简而言之,识别聚合就是识别潜在核心业务规则的过程,定义的聚合是基于大家共识对核心业务规则的封装。领域服务的定义 在最初的元模型定义中,许多人都在为领域服务而苦苦挣扎。一个典型的例子就是账户管理领域对“转账”这一业务行为的抽象。由于转账本身至少应用于两个账户,因此将转账视为一个账户显然是不合适的。那么如果我们将转移名义化并抽象成一个实体呢?感觉挺别扭的,毕竟转账是依赖账户存在的。 这时候DDD在元模型中提出了服务(Service)的抽象,传输被抽象成服务,感觉流畅多了。同样的道理,在上面的订单追踪领域,如果追踪过程中需要短信通知,更好的建模是抽象出一个“通知”服务来完成。 我经常使用静态方法来帮助技术人员理解服务的抽象(尽管服务不一定使用静态方法来实现)。服务本身就像一个静态方法,有一定的逻辑但不保存任何信息。从整个领域来看,同一个服务不存在不同的“版本”。 经常困扰大家的一个问题就是Service这个词的局限性。在一些分层架构设计中,会出现领域服务(Domain Service)和应用服务(Applicaiton Service)。大多数时候,应用服务位于领域服务之上,直接向外界提供接口。如果有这样的分层,那么领域服务就不应该直接暴露给外界,而应该通过应用服务来提供。 例如,如果前面的订单消息通知是域服务,当订单状态发生变化时创建通知消息,将***通知以短信的形式发送给设定的人群,那么应该有一个相应的应用程序。服务包括具体的业务场景处理逻辑。后面可能还会有一个邮件通知应用服务,同样调用这个通知字段服务,但是通过邮件通道完成最终的业务场景。 由于微服务架构的流行,各个子域的粒度变得相当细。在许多情况下,领域服务和应用程序服务之间不存在这种区别。当然,从简单的角度来看,这是一件好事。在整个建模过程中,服务的抽象往往是最不确定的,也是最值得反复考虑的。 存储库的使用 存储库是一个很容易被误解的抽象,很多人都会直接将它与具体的数据存储联系起来。我早期在使用DDD建模时,常常刻意回避这种抽象,以免混淆大家的思维。 这个抽象概念实际上可以追溯到 Martin Fowler 的对象查询模式。另一个相关的概念是DAO(Data Access Object),用于简化需要存储的数据与对应的业务对象之间的映射关系。不同之处在于存储库旨在更粗粒度的抽象。在 DDD 方法中,我们可以将映射对象视为我们的聚合。实现时可以为每个实体创建对应的DAO(例如使用Hibernate等ORM框架),但显然这不是我们在建模过程中需要关注的。 那么为什么需要对存储库进行抽象呢?让我们回到订单跟踪的例子。通知订单状态变化的服务在发送通知之前需要定位订单信息(可能包括订单相关利益相关者和子订单的信息)。 )。作为一项服务,通知不应包含特定的订单信息。这时候我们就需要利用Repositories的抽象来建立一个订单的聚合查询,即有一个订单的repo,具体的查询逻辑应该在这个repo中。 当涉及到存储和查询值对象时,这种抽象也是必要的。假设我们分析订单查询的字段。在这个字段中,订单记录显然不再允许修改。自然的抽象方法就是值对象。查询服务保存特定的查询逻辑(例如按时间或用户)也是合理的。外部应用程序直接调用查询服务(接口)并给出指定的参数。我们需要一个订单记录的存储库来保存与存储相关的查询逻辑。当然,这并不是说有查询就一定有对应的repo。如果查询的逻辑很简单,可能无法让服务直接实现数据存储。请记住,我们抽象的目标是使建模更容易,并且抽象过程应该保持灵活性。 有界上下文的含义 经过10多年的演变,我们对于如何支撑组织规模已经达成了一些基本共识。我们知道微服务架构(Microservices)可以帮助我们组织成百上千的工程师,而小团队的自组织至关重要。我们也逐渐在技术团队和业务团队之间如何清晰沟通“架构”这个难题上找到了DDD。那么DDD和微服务架构有什么关系呢?很多人会提到有界上下文(Bounded Context)。 我曾经专门写过一篇关于这个主题的文章(DDD&Microservices)。有界上下文封装了相对独立的子域的领域模型和服务。有界上下文映射描述了各个子域之间的集成调用关系。从某种意义上来说,这个定义与我们对微服务的划分不谋而合:面向提供业务能力的自治、独立的部署单元。所以虽然我们不能完全根据有界上下文来划分服务,但有界上下文,或者说DDD,绝对是我们设计微服务架构的重要方法之一。 如果我们回到DDD的策略设计,我们会发现,在问题域中,DDD通过子问题域(子域)的划分完成了业务能力的分解,而有界上下文在解决方案域中完成了进一步的分解。 。当然,我们不能完全假设子问题域和有界上下文之间存在严格的一对一关系,但大多数情况下子问题域被设计为一个或多个有界上下文。从某种意义上说,子域和有界上下文是相辅相成的。重点是区分问题域和解决方案域。这是实施DDD最困难的部分,也是判断一个架构师能力进步的分水岭。 战术建模总结 DDD的建模要素比较简单,本文描述的元模型应该可以满足大多数场景的建模。毛主席曾有一句名言:“战略上要藐视敌人,战术上要重视敌人”。就架构设计而言,我们没有敌人,业务需求是我们的朋友。因此,在领域驱动架构设计方面,我们需要的是“战略上聚焦朋友,战术上简化建模”。我希望这句话能够帮助正在实践DDD的团队重新思考自己对战略问题领域的投入和侧重点,不要再挥舞着战术模型的大锤去寻找实际上并不存在的钉子。 【本文为51CTO专栏作家“ThoughtWorks”原创稿件,微信公众号:Sitwak,转载请联系原作者】 点击此处查看该作者更多好文章