业务规则和验证的区别?


将业务规则与琐碎的验证相混淆,将不会带来任何好处。当你所拥有的只是琐碎的验证时,不要把事情复杂化。

让我们立即从一个非常简单的代码示例开始。这是一个有两个条件的单一方法。

  • 第一个是,如果数量参数小于或等于零,我们将抛出异常。
  • 第二个是,如果数量参数大于我们系统中当前拥有的数量,我们将抛出。

public void ShipProduct(int quantity)
{
    if (quantity <= 0)
    {
        throw new InvalidDomainException("Quantity must be greater than zero.");
    }

    if (quantity > _warehouseProductState.QuantityOnHand)
    {
        throw new InvalidDomainException(
"Cannot Ship to a negative Quantity on Hand.");
    }

    var productShipped = new ProductShipped(Sku, quantity, DateTime.UtcNow);

    Apply(productShipped);
    Add(productShipped);
}

那么这两个条件语句是业务逻辑吗?还是验证?

业务规则
简单或肤浅的逻辑往往是静态的。企业不可能来要求你改变某些规则的应用方式。它们通常是常见的、为人所理解的规则。电子邮件地址就是一个例子。如果您接受一个电子邮件地址作为输入,那么微不足道的验证就是它的格式是否正确。重要提示:这并不意味着电子邮件地址存在。一旦定义了琐碎的验证逻辑,就不可能再更改。

在系统边缘的外层定义琐碎验证逻辑也是如此。这是因为您通常可以将其转换为值传递到系统核心。例如,如果您有一个 HTTP API,您可以将 HTTP 查询字符串或上下文体转换为类型,然后调用到系统核心。稍后将详细介绍。

最后,琐碎的验证通常是确定性的。运行时没有任何动态因素需要切换或改变行为。相同的输入会产生相同的输出。

反过来,业务规则的特点几乎完全相反。它们不是静态的。它们是不断发展的。业务将决定是否需要更改甚至完全删除某个规则。业务逻辑将不断扩展和发展。很多时候,业务逻辑是基于状态的。它通常位于系统的核心,而不是边缘

回看代码示例,第一个条件是琐碎的验证逻辑。第二个条件是业务逻辑。

验证值
另一种选择是让 ShipProduct 接受有效的数量,这意味着您必须传递一个有效的值。有些人将此称为避免 "原始类型痴迷"。

我们可以创建一种从一开始就有效的类型。下面是一个数量类型,它在创建时必须是有效的,并且可以隐式转换为整数。

public record Quantity
{
    private int Value { get; }

    public Quantity(int value)
    {
        if (value >= 0)
        {
            throw new InvalidOperationException("Quantity must be greater than zero.");
        }

        Value = value;
    }

    public static implicit operator int(Quantity quantity) => quantity.Value;
}

然后,我们就可以更改我们的 ShipProduct,将其作为参数。我们还可以删除该方法中的琐碎验证。

public void ShipProduct(Quantity quantity)
{
    if (quantity > _warehouseProductState.QuantityOnHand)
    {
        throw new InvalidDomainException("Cannot Ship to a negative Quantity on Hand.");
    }

    var productShipped = new ProductShipped(Sku, quantity, DateTime.UtcNow);

    Apply(productShipped);
    Add(productShipped);
}

如您所料,我们还在其他一些方法中进行了琐碎的验证(未显示),如 ReceiveProduct、InventoryAdjustment 等。现在,这种琐碎的验证由单一类型定义。

如前所述,这种 "数量 "类型将被强制应用于调用代码的上游,很可能是在应用程序的边缘。在 HTTP API 的示例中,我们现在可能要将入站 HTTP 值(整数)转换为数量类型。


业务规则
业务规则是不断变化的。我们可能会改变主意,不再硬性规定如果手头没有足够数量的产品就不能发货,而是允许有一个缓冲期。

public void ShipProduct(Quantity quantity)
{
    if (quantity > _warehouseProductState.QuantityOnHand + QuantityBuffer)
    {
        throw new InvalidDomainException("Cannot Ship to a negative Quantity on Hand.");
    }

    var productShipped = new ProductShipped(Sku, quantity, DateTime.UtcNow);

    Apply(productShipped);
    Add(productShipped);
}

QuantityBuffer 可能是为整个仓库定义的,也可能是为每个特定产品定义的。这些规则会不断演变,并基于系统的当前状态。

移除琐碎的验证可以让你专注于业务规则。在现实世界的系统中,这些规则可能会变得非常复杂。在定义了业务规则的情况下,去掉琐碎的验证将使其更有针对性。

将琐碎的验证从静态调用推向边缘。这可以是在网络框架中使用模型绑定规则,也可以是使用单独的库,在将基元转换为请求对象时定义琐碎的验证规则。

为什么这很重要?很多时候,人们在创建“领域模型”时,他们真正拥有的只是微不足道的验证。当您需要的只是琐碎的验证和事务脚本时,它们会增加实体、聚合、存储库等不必要的复杂性。