DDD 领域驱动设计

发布时间 2023-11-14 12:44:16作者: 木乃伊人

一、简介

       领域驱动设计(DDD)是由埃里克-埃文斯(Eric Evans)提出的,它是一套思想、原则和模式,有助于根据业务领域的基础模型设计软件系统。

二、实践架构

        分层架构、整洁架构(洋葱架构)、六边形架构(端口适配器架构)、菱形对称架构

        2.1、分层架构

                架构图:

                       

 

                架构说明:

    1. 用户接口层。负责向用户展示数据和接收用户输入,根据不同的前端应用,可以订制不同的数据适配器。
    2. 应用层。 负责协调领域层的多个聚合完成服务的组合和编排,不包含核心业务逻辑。
    3. 领域层。负责实现核心业务逻辑,包括实体,值对象,聚合,领域服务等概念。
    4. 基础设置层:负责提供通用的技术和基础服务,如数据库,缓存,消息中间件等。     

               优点:

                     1、提高了软件系统的可维护性,可扩展性和可复用性。因为每一层都可以独立的修改和替换,而不影响其它层的功能。

                     2、降低了软件系统的复杂度。因为每一层只关注一个单一的功能或关注点,而不需要了解其它层的细节。

                     3、增强了软件系统的安全性。 因为每一层都可以对数据进行封装和验证,避免了表示层直接访问数据访问层。

        2.2、整洁架构

                架构图:             

                          

                架构说明:(从内到外)

    1. 实体层。包含系统的业务规则和领域模型,是最稳定和最独立的层次。
    2. 用例层。包含系统的应用逻辑和用例,依赖于实体层,但不依赖于外部设备或框架。
    3. 接口适配器层。 包含控制器,网关,呈现器等组件,负责将外部请求转换为内部用例,并将内部数据转换为外部格式。
    4. 架构和驱动器层。包含数据库,UI,Web框架等具体技术细节,是最易变和最不稳定的层次。   

               注意:整洁架构遵循依赖倒置原则。即内部圆环不应该依赖于外部圆环,而应该通过接口或抽象类来定义依赖关系。    

        2.3、六边形架构

                说明:为解决整洁架构的问题而来。

                架构图:

                   

                架构说明:

    1. 领域层。包含了系统的业务规则和领域模型,是最核心和最稳定的层。
    2. 应用层:包含了系统的用例和应用逻辑。依赖于领域层,但不依赖于外部设备或框架。
    3. 端口适配器层:包含了控制器、网关、呈现器等组件,负责将外部请求转换为内部用例,并将内部数据转换为外部格式。

        2.4、菱形对称架构

                说明:由分层架构和六边形架构进化而来

                架构图:              

                             

                架构说明:

  1. 北向网关的远程网关:进程间通信。
  2. 北向网关的本地网关:进程内通讯。
  3. 领域层的领域模型:领域逻辑。
  4. 南向网关的端口抽象:各种资源库操作的抽象接口,可以被领域层依赖。
  5. 南向网关的适配器实现:网关端口的实现,运行时通过依赖注入将适配器实现注入到领域层。

三、设计实践

        3.1、空间

                 DDD有两个不同的空间:问题空间、解决问题空间

               【问题空间】

                 在问题空间,用战略模式来定义系统的大规模结构,它专注于分析一个领域、子领域和泛在语言。

               【解决方案空间】

                 在解决方案空间,采用战术模式来提供一套设计模式,你可以用它来创建领域模型。这些模式包括有界的上下文、上下文映射、实体、聚合体、领域事件、领域服务、应用服务和基础设施。这些战术模式将帮助你设计既松散耦合又有凝聚力的微服务

       

                                  

                 3.2、实践应用DDD

                         3.2.1、场景

                                  我们用一个支付系统建模说明DDD在实践中的应用。  场景如下:

      1.  一位顾客想在商家的网站上购买一件衣服,价格是10块。
      2. 顾客可以用各种支付方式来支付这件T恤,如银行卡或微信钱包。
      3.  顾客付款后,商家可以从支付网关获得通知,这样他们就可以向顾客展示付款成功的页面。
      4.  商家可以在WebApp中查看付款详情,这样他们就可以知道这件衣服可以获得多少资金,银行卡或者微信钱包扣除了多少费用,以及资金何时会被结算到他的银行卡中。

                         3.2.2、领域建模

      1. 分析现实世界中的业务用例,以获得问题空间中的域和子域。通常,在这个阶段,Event Storming是一个很好的工具。
      2.  定义解决方案空间中的有界上下文。
      3. 在有界限的上下文中,应用战术性DDD模式来定义实体、聚合、领域服务、领域事件等。
      4. 使用上一步的结果来确定你的团队中的微服务。

                                【问题空间】
                                   1、领域

                                         支付系统

                                   2、子域

                                         支付处理:商家可以通过各种支付方式接受顾客的付款。

                                         金融:对商家的支付资金进行清算和结算。

                                  3、通用语言

                                        在与领域砖家讨论后,以下是所有团队接受的通用语言。

                                        支付意图:商家创建的订单,指定价格、产品、顾客等。

                                        付款企图:商家创建的交易,以接受顾客对特定订单的付款。

                                        付款方式:顾客为产品或服务付款的方式。

                                        付款结算:一批结算到商家银行卡的付款。

                                        付款视图:一个聚合的付款细节视图,包含与一个付款有关的所有数据。

                                   【解决问题空间】

                                      1、有界上下文

                                           有界上下文(BC)限定了一个领域模型的范围。从问题空间的分析结果来看,我们可以定义以下有界上下文。  

                                           支付网关:API网关,为商户提供可靠的API,以创建或查看付款。

                                           支付核心:支付意图、尝试、方法资源管理。

                                           支付适配器:与一个外部PSP(微信/支付宝/银行卡等)集成。

                                           支付结算:为商家计算和结算每笔支付的原则和费用。

                                           支付融合:支付细节的聚合视图。

                                           上下文地图是这样的:

                                   

                          2、领域模型

                               从上面我们分析的场景和无所不在的语言中,我们可以确定以下聚合、实体、价值对象和领域事件。

                                

                        3、领域服务

                              实践中,领域服务是为一个聚合体,提供的无状态业务逻辑服务,遵循单一责任模式。通常情况下,我们会在领域服务中封装领域仓库、聚合变化和领域事件发布。以PaymentAttemptExecutorService为例:

                              

                        4、领域事件

                              领域事件可以使系统更具可扩展性,并避免任何耦合(一个聚合体不应该决定其他聚合体应该做什么),以及时间耦合(付款的成功完成并不取决于所有进程在同一时间可用)。                           

                          

                 

           例如,当PaymentCaptureCommand将支付状态改为已支付时,领域事件PaymentAttemptCapturedEvent被发送,以通知聚合的PaymentAttempt被捕获。在PaymentAttemptCapturedEvent的领域事件处理程序中,我们可以把副作用放在业务逻辑上,比如通知支付融合的边界上下文来更新支付细节和支付结算的边界上下文来计算结算金额和费用。 

                      5、基础设施

                          在DDD模式中,基础设施层被用来将核心业务领域与技术实现细节分开。通常,该层采用反污层(ACL)模式。以领域存储库为例:

                           

     领域仓库只定义了接口,比如他们能做什么,但实现细节应该隐藏在基础设施层里面,比如使用PostgreSQL或MongoDB来保存数据。例如,在基础设施层,PaymentAttemptPgRepository是基于PostgreSQL的具体实现,toPO是用于将域对象PaymentAttempt转换为持久化对象的映射器。

                          

                       因此,在领域层,我们只关注领域模型,它与基础设施技术完全脱钩。当基础设施层有任何变化时,不需要在领域层中进行改变。

                        【从领域层到微服务】

 

现在,我们已经为支付系统定义了一组有边界的上下文,并在每个有边界的上下文中确定了一组实体、集合体和领域事件服务。

        下一步就是要从领域模型到应用微服务的设计。

                      在这里,我们选择将一个有界上下文映射到一个微服务。

                        

                     3.2.3、总结

                               当我们试图对支付系统进行建模时,我们触及了领域驱动设计(DDD)模式的各种概念和策略。采用DDD可以提供许多好处,例如,在所有的团队中进行清晰的沟通,以及在设计系统时提供一个成熟的模式来管理复杂性和提供更好的可扩展性。

  1.  有了无处不在的语言,我们可以实现更多的自我描述的类名和函数名。
  2.  通过聚合模式,我们可以实现清晰的边界和单一的责任  。
  3.  通过领域事件模式,我们可以将核心业务流程与聚合体上的副作用分开 。
  4.  通过基础设施层和ACL模式,我们可以将核心业务领域模型与技术实现细节分开 。
  5.    通过有边界的上下文模式,我们可以推导出潜在的微服务候选人。                 

  四、思维导图