架构演化

分层架构演化

领域合并

微服务架构的演进

域模型中对象的层次从内到外依次是:值对象、实体、聚合和限界上下文。实体或值对象的简单变更,一般不会让领域模型和微服务发生大的变化。但聚合的重组或拆分却可以。这是因为聚合内业务功能内聚,能独立完成特定的业务逻辑。那聚合的重组或拆分,势必就会引起业务模块和系统功能的变化了。这里我们可以以聚合为基础单元,完成领域模型和微服务架构的演进。聚合可以作为一个整体,在不同的领域模型之间重组或者拆分,或者直接将一个聚合独立为微服务。

微服务演化

我们结合上图,以微服务1为例,讲解下微服务架构的演进过程:

  • 当你发现微服务1中聚合a的功能经常被高频访问,以致拖累整个微服务1的性能时,我们可以把聚合a的代码,从微服务1中剥离出来,独立为微服务2。这样微服务2就可轻松应对高性能场景。
  • 在业务发展到一定程度以后,你会发现微服务3的领域模型有了变化,聚合d会更适合放到微服务1的领域模型中。这时你就可以将聚合d的代码整体搬迁到微服务1中。如果你在设计时已经定义好了聚合之间的代码边界,这个过程不会太复杂,也不会花太多时间。
  • 最后我们发现,在经历模型和架构演进后,微服务1已经从最初包含聚合a、b、c,演进为包含聚合b、c、d的新领域模型和微服务了。

微服务内服务的演进

在微服务内部,实体的方法被领域服务组合和封装,领域服务又被应用服务组合和封装。在服务逐层组合和封装的过程中,你会发现这样一个有趣的现象。

应用层内合并

我们看下上面这张图。在服务设计时,你并不一定能完整预测有哪些下层服务会被多少个上层服务组装,因此领域层通常只提供一些原子服务,比如领域服务a、b、c。但随着系统功能增强和外部接入越来越多,应用服务会不断丰富。有一天你会发现领域服务bc同时多次被多个应用服务调用了,执行顺序也基本一致。这时你可以考虑将bc合并,再将应用服务中bc的功能下沉到领域层,演进为新的领域服务(b+c。这样既减少了服务的数量,也减轻了上层服务组合和编排的复杂度。

渐进式能力下沉

在现实业务中,很多的功能都是用例特有的(Use case specific)的,如果盲目的使用Domain收拢业务并不见得能带来多大的益处。相反,这种收拢会导致Domain层的膨胀过厚,不够纯粹,反而会影响复用性和表达能力。所谓的能力下沉,是指我们不强求一次就能设计出Domain的能力,也不需要强制要求把所有的业务功能都放到Domain层,而是采用实用主义的态度,即只对那些需要在多个场景中需要被复用的能力进行抽象下沉,而不需要复用的,就暂时放在App层的Use Case里就好了。这里的Use Case是《架构整洁之道》里面的术语,简单理解就是响应一个Request的处理过程。

这种循序渐进的能力下沉策略,应该是一种更符合实际、更敏捷的方法。因为我们承认模型不是一次性设计出来的,而是迭代演化出来的。下沉的过程如下图所示,假设两个Use Case中,我们发现uc1step3uc2step1有类似的功能,我们就可以考虑让其下沉到Domain层,从而增加代码的复用性。

应用层能力下沉示意图

指导下沉有两个关键指标:代码的复用性和内聚性。复用性是告诉我们When(什么时候该下沉了,即有重复代码的时候。内聚性是告诉我们How(要下沉到哪里,功能有没有内聚到恰当的实体上,有没有放到合适的层次上;因为Domain层的能力也是有两个层次的,一个是Domain Service这是相对比较粗的粒度,另一个是DomainModel这个是最细粒度的复用。

比如,在我们的商品域,经常需要判断一个商品是不是最小单位,是不是中包商品。像这种能力就非常有必要直接挂载在Model上。

public class CSPU {
  private String code;
  private String baseCode;

  //省略其它属性
  /**
   * 单品是否为最小单位。
   *
   */
  public boolean isMinimumUnit() {
    return StringUtils.equals(code, baseCode);
  }

  /**
   * 针对中包的特殊处理
   *
   */
  public boolean isMidPackage() {
    return StringUtils.equals(code, midPackageCode);
  }
}

之前,因为老系统中没有领域模型,没有CSPU这个实体。你会发现像判断单品是否为最小单位的逻辑是以StringUtils.equals(code, baseCode)的形式散落在代码的各个角落。这种代码的可理解性是可想而知的,至少我在第一眼看到这个代码的时候,是完全不知道什么意思。

上一页
下一页