jessenpan's blog

vuePress-theme-reco jessenpan    2016 - 2020
jessenpan's blog

Choose mode

  • dark
  • auto
  • light
主页
分类
  • 杂谈
  • 写作技巧
  • 职业发展
  • 计算机
  • DDD
标签
时光机
关于我
联系我
  • GitHub
author-avatar

jessenpan

6

文章

6

标签

主页
分类
  • 杂谈
  • 写作技巧
  • 职业发展
  • 计算机
  • DDD
标签
时光机
关于我
联系我
  • GitHub
  • 切勿站在提供者的角度去梳理南向网关

    • 南向网关是六边形架构对外依赖的表述
      • 依赖的接口应当站在使用者角度梳理
        • 南向网关站在提供者角度实现会越来越臃肿
        • 站在使用者的角度才能更好的防腐
      • 一些遗留问题解答

      切勿站在提供者的角度去梳理南向网关

      vuePress-theme-reco jessenpan    2016 - 2020

      切勿站在提供者的角度去梳理南向网关


      jessenpan 2020-04-01 DDD

      # 南向网关是六边形架构对外依赖的表述

      DDD中的六边形架构之所以称之为六边形架构,是因为它的架构图和六边形基本上保持一样。六边形内部的内容代表DDD中的核心模型,包含所有的值对象和实体。六边形的两个相对立的顶点分别表示DDD中的对外提供的领域服务以及对外依赖的仓储层接口、第三方上下文的服务接口。

      具体的图形表示如下:

      从图形的方位上看,我们将图形朝北(上)的部分称之为北向网关,即对外提供的领域服务;将图形朝南(下)的部分称之为南向网关,即对外依赖的仓储&三方上下文的服务接口。

      需要注意的是,南北向网关指的都是接口,而不是具体的实现,具体的实现放置在基础设施层,采用依赖注入的方式实现。这样做的目的是隔离具体实现,灵活选择,达到解耦的目的。

      # 依赖的接口应当站在使用者角度梳理

      # 南向网关站在提供者角度实现会越来越臃肿

      基于上述对南向网关的定义,在梳理的时候我们会把所有对外依赖的服务(RPC或HTTP)接口定义在领域模型的南向网关里。在微服务大行其道的今天,基本上远程调用都是RPC。因此在实现上,会用RPC作为包名来包含所有属于南向网关定义的接口。

      ``` 
      ├── xxx-demo-domain
        ├──aggregate
        	├─entity
        	├─valueobject
        ├──rpc
        	├─WarePoolSaleStateReadProvider.java
        	├─RestrictSaleCheckProvider.java
          ├─ContractQueryProvider.java
        	├─NewCartProvider.java
          ├─OldCartProvider.java
        	├─......
          ├─......
      ```
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      实践上,以电商订单为例,大致的包和对应的类放置结构基本如上图。上图中,rpc包下面的就是依赖的接口定义,示例中只包含五个类。但是在实际工程中,远远不止这个数字,基本上rpc包里的接口会达到几十甚至上百个。

      这么多的rpc依赖接口定义虽然满足了依赖注入。一定程度上完成了隔离实现,降低了复杂度。但这么多数量的接口:

      • 会将整个模型变得非常的臃肿,无法阅读和理解。
      • 在核心模型里与第三方紧耦合,第三方新加/删/修一个接口及对应的出入参,核心模型必须跟着修改。

      为什么会出现这种现象呢?

      **主要是因为我们是站在接口提供方的角度在梳理和实现南向网关。**在实现一个需求时,如果需要依赖第三方上下文的能力,基本上就会在rpc包下面将提供该能力的接口全部定义出来。如果是多个就挨个定义一遍,一个亦然。

      那我们怎么解决这种问题呢?

      我们需要转变思路,站在使用者的角度去梳理,掌控更多的主导权。

      # 站在使用者的角度才能更好的防腐

      分析上面订单的示例中的几个外部接口可以看出:

      • 第一个接口是商品系统提供的商品可售、第二个接口是限售平台提供的商品可售。它们两个共同支撑了订单在提单时判断某一个商品是否能够购买的校验。只是因为当前提供方没有收口(在限售平台或单独一个系统),导致在订单里按提供者的角度,也定义了两个,其实它们应该是一个。

        那我们应该怎么做?

        随着团队的整合,当最后两个接口收口时,订单也要修改核心模型去适应。这样是不是订单与限售(商品和限售平台的)显得耦合的太深了。同时,这种外部接口融合,导致我们模型的修改,也与我们的初衷:核心模型只关注业务(只因业务变更而变更,而不是技术),相违背,应该尽量避免。

        我们应该在核心模型只定义一个抽象的可售接口(判断商品是否可售):ValidateSkuSaleStateService来进行防腐。在基础设施层的实现里封装当前的多个提供方。

      • 第三个ContractQueryProvider接口是用户平台返回一个用户账号对应的合同信息,订单使用这些信息是为了根据其中某一个字段或几个来判断用户是否有效。从使用者的角度看,订单使用此接口的目的只是为了校验用户是否合法。当前如此使用,只是用户平台业务外泄,没有直接提供判断是否用户有效的接口,导致让调用方来自写逻辑手动判断。

        那我们应该怎么做?

        一样随着架构演化,用户平台需要将此类逻辑内聚,此时订单就无需之前的逻辑代码了。从此角度看,订单之前是不应该直接将ContractQueryProvider放置在模型内部的,因为它不关心合同信息,只关心用户是否有效。

        站在使用者的角度,订单应该定义ValidateUserService接口,来达到真正的防腐。防止出现第三方一变更,你就变更的尴尬局面。

      • 最后两个接口表示当前因为购物车升级,存在新老两个购物车接口给订单使用。场景和上述的第一种类似,我们也不应当直接在核心模型里定义两个领域服务,而是要建立真正的防腐。解决方案和上述第一种一样。

      从上述的几个示例中可以得出一个结论:

      应该站在使用者的角度去分析建模,实现真正的、深层次的防腐

      # 一些遗留问题解答

      站在使用者的角度去梳理,会带来一些问题。

      问:实现真正的防腐有没有一个方法论来判断哪些外部接口需要合并?不然梳理的时候没有标准不好执行。

      答:一个简单的标准,对每一个外部的RPC接口,优先考虑合并。当无法合并的时候,也不能定义和外部RPC一模一样的方法签名。如果不能满足上述两条,需要给出明确原因。

      问:南向的网关的包名叫什么?

      答:当前大部分模块将南向网关的包名命名为了RPC。这种包名有很强的暗示,表示此包里的内容都是RPC接口,从而直接将所有外部的三方接口直接放置于此,而不做防腐。此处建议将南向网关命名为:outbound。