单体转变到微服务之前采取DDD的三个步骤 - Jim Rottinger


作为单体一部分编写代码很容易,我们可以随时查询数据库,在应用程序的其他部分调用我们想要的任何函数,而不必考虑整个单体组织结构,因为我们正在插入现有的体系结构。然而,这种类型的开发导致的问题是一个脆弱的,纠缠不清的代码库,其中对应用程序的一部分的任何更改都可以改变甚至破坏某些其他部分中的某些内容,而无需任何人知道原因。不仅如此,部署变得困难,而且还为进入代码库的新开发人员创造了可怕的学习曲线。这根本不可取,并且许多发现自己处于这种情况的人开始阅读并理解基于服务的体系结构(SOA)的优点。问题是 - 巨石越大,分解就越难。

从一个混乱的整体直接转向SOA是不可行的。

我们可以得到某种中间状态,这样可以更容易地开始分解。这个中间状态仍然是一个整体,但是它是由领域组织的,反而没有我们原始代码库的纠缠或脆弱。一旦我们做到这一点,就可以更容易地决定我们的应用程序的未来,特别是在决定什么是突破服务以及哪些部分应该保持在一起。

在这篇文章中,我们将介绍域驱动设计(DDD),然后通过三个步骤,您可以将凌乱的整体转换为刚刚描述的有组织的中间状态。

领域驱动设计
在开始任何重构之前,我们必须弄清楚我们的应用程序的域是什么。最简单的方法是将应用程序分解为可以根据业务逻辑进行解释的组件。例如,在结帐应用程序中,某些域可能是地址验证,运输,税收和付款处理。在软件中,这种分解或构建由业务逻辑组织的代码的行为称为域驱动设计(DDD)。

领域驱动设计可以帮助我们实现SOA的原因在于,通过将代码分解为单个域,然后就可以创建类似服务的部件,这些部件组合在一起形成我们的应用程序。如果需要,可以将这些类似服务的部分分解为主应用程序所要求的自己的微服务。或者,如果您决定保留单体,那么现在您可以将其置于易于迭代的状态,并且易于理解应用程序中发生的情况。

步骤1-基于领域隔离业务逻辑以消除相互依赖性
这里有目的地选择“隔离”这个词,因为它不仅仅意味着隔离。通过隔离,我们不仅仅意味着将功能分离到不同的文件中,我们希望更进一步,甚至不让它们触及应用程序的其他部分,除非我们特别希望它通过注入依赖项。

在构建应用程序时,通常情况是您的逻辑将触及许多不同的域。再想一想电子商务应用程序中的结账流程。单个结账涉及验证送货地址,计算税率,从承运人处获取运费,验证库存是否可用,......列表继续。完全可能在一个进程中处理所有结账,但这只会使您的代码难以维护并且几乎不可能进行测试。相反,我们希望将与这些部分中的每一部分相关的所有逻辑完全分开,使得它们根本不相互接触。

保持应用程序逻辑按域排序,是实现基于服务的体系结构的第一步。如果您有带有不同功能的抓包的控制器或实用程序文件,请首先将它们分开并按责任组织它们并删除每个功能的相互依赖性。

第2步 - 定义您的接口并隐藏其他所有内容
在单一代码库中工作的一个缺点是,对于新开发人员来说,存在巨大的学习曲线。它是第一次看到代码库的压倒性的,一切都在一个地方,你不知道什么调用什么和发生在哪里。所有的逻辑都在一个地方,你不知道从哪里开始。
作为开发人员,我们不应该理解应用程序的每个其他部分的内部工作方式,以便在代码库中工作。DDD带给我们的一个优点是,它允许我们从业务逻辑的角度考虑我们的应用程序。每个域中的所有逻辑都应该由单个接口表示,该接口可用于理解隐藏在其后面的所有内容的功能。

以下是计算订单税率的接口示例:

// iTaxCalculator.php
include ValueObject\Address;
include ValueObject\TaxRate;
interface iTaxCalculator
{
 
/**
  * @throws InvalidArgumentError
  * @return bool
  */

  public function setTaxNexus(array $addresses);
 
/**
  * @return bool
  */

  public function setDestination(Address $address);
 
/**
  * @return bool
  */

  public function setShippingOrigin(Address $address);
 
/**
  * @return \ValueObject\TaxRate
  */

  public function getTaxRate();
}

通过查看这个接口,我们可以在不查看代码的情况下推断出它的工作原理。它使用目的地地址,运输来源和商店的税收关系来获得订单的税率。即使在单一的代码库中,这种将所有细节隐藏在单个接口背后的做法也是一个很好的习惯。

步骤3-使用不可变值对象
今天许多流行的编程语言,包括我主要使用的两种编程语言,PHP和JavaScript,都可以很容易地将随机容器信息作为关联数组或对象传递。分解我们的代码库的本质意味着许多数据将通过我们的各种新组件流动。虽然传递普通的旧对象可以工作,但是如果有某种合同明确指出每个子域区域内的内容以及将要从中返回的内容将会很好。如果这些对象是不可变的并且只有在明确定义了setter时才能改变它也会很好。

这是值对象的来源。值对象不是模型实例,也没有ID。它们是具有已定义属性的信息的不可变容器,其状态完全取决于其值。这是一个例子:


class TaxRate
{
   /**
    * @var float
    */

    private $tax_rate;
   
/**
    * @var string add|base|instead
    */

    private $rate_type;
    public function __construct($tax_rate, $rate_type)
    {
        $this->tax_rate = $tax_rate;
        $this->rate_type = $rate_type;
    }
   
/**
    * Get the tax rate
    */

    public function getTaxRate()
    {
        return $this->tax_rate;
    }
   
/**
    * Get tax type
    */

    public function getTaxType()
    {
        return $this->tax_type;
    }
}

起初,它可能看起来很乏味,也没必要这样做,但它让我们相信,在一个以税率运作的系统中,我们将始终使用税率,而不是随机的价值​​,可能有也可能没有我们需要的领域。

总结
从整体代码转向SOA微服务是一项艰巨的任务,而且不可能一步到位。如果您发现自己处于代码库太大而无法快速迭代的位置,请不要立即开始尝试破解它。相反,使用本文中描述的DDD概念将您的整体组织成明确定义的子域是您的目标。一旦完成,就可以更容易地将代码作为单独的服务分解出来。