"static Domain helper class" Ambient Context 是反模式吗?

Is "static Domain helper class" Ambient Context Anti-Pattern?

正如 Steven van Deursen 和 Mark Seemann 所著的 Manning 书中所定义的,“依赖注入原则、实践和模式”(我推荐必读),那里是我感兴趣的一种特定反模式,那就是 Ambient Context Anti-Pattern。它也被描述为here。一般问题是,如果您将一些不稳定的依赖项置于静态 class 之后,您会将代码库的多个部分耦合到该不稳定的依赖项。

我的困惑始于易变依赖的定义。 volatile dependency 的定义之一是这种依赖还在开发中或者没有开发。所以我的问题是:如果这种依赖是一些明确定义的领域知识,例如一些特定领域的电子邮件验证器,放置在领域层,并且它仍在进行一些代码更改,如果我们把它视为反模式在静态 class 后面并在整个代码库中使用它? (我们谈论的是 Domain 处于中心并且所有内容都指向 Domain 层的洋葱架构)

编辑 1

这是一个简化的示例域 class:

public class EmailValidationService
{
   private static readonly Regex _emailValidationRegex = /*some regex*/;

   public static EmailValidationStatus IsValid(string email)
   {
     var isValid = _emailValidationRegex.Match(email).Length > 0; ;

     return isValid;
   }
}

这个验证器的消费者被放置在多层,例如用户输入验证,文件导入验证等。这里有两个简化的消费者:

public class RawDataImporter{

    private Employee ProcessEmployee(EmployeeRowData data)
    {
        if (string.IsNullOrEmpty(data.Identifier)) { return new InvalidEmployee(); }
    
        if (EmailValidationService.IsValid(data.Identifier) { return new Employee(data.Identifier); }
    
    }
}

public class EmailAttribute :
        ValidationAttribute
{
    public override bool IsValid(object value)
    {
        var status = EmailValidationService.IsValid((string)value);
        if (status.IsValid)
        {
            return true;
        }

        return false;
    }
}

One of the definitions of volatile dependency is that this dependency is still under development or not developed.

令我震惊的是,这本书(我是合著者)中的这句话并没有得到详细阐述。书上是这样说的:

A Dependency should be considered volatile if [...] [t]he Dependency doesn’t yet exist, or is still in development.

就是这样。没有给出更多信息。幸运的是,第一版包含了更多信息。在该版本中,前面的语句后跟以下句子:

The obvious symptom of such dependencies is the inability to do parallel development.

我不得不深入了解我们删除它的原因,但校对人员的反馈表明第二句话有点令人困惑。但是我们并没有改进那条线,而是似乎(不小心?)删除了它。在该要点中保留“并行开发”的概念会很有用,因为它回顾了第 1.2.2 节,其中说明了以下有关并行开发的内容:

When a software development project grows to a certain size, it becomes necessary to have multiple developers work in parallel on the same code base. At a larger scale, it’s even necessary to separate the development team into multiple teams of manageable sizes. Each team is often assigned responsibility for an area of the overall application. To demarcate responsibilities, each team develops one or more modules that will need to be integrated into the finished application. Unless the areas of each team are truly independent, some teams are likely to depend on functionality developed by other teams.

让我再次尝试纠正本书的遗漏并详细说明为什么仍在开发中的依赖项可以被视为易失性。

当与更多人一起开发应用程序时,未开发的功能在其不存在或无法并行开发阻碍开发时隐式地变得易变。团队越大,或者开发一个软件的团队越多,这些开发不足的部分就越能阻碍开发人员的表现。

大多数在多个团队并行开发的代码库上工作过的开发人员都经历过,你更经常被其他人阻止,经历更多的合并冲突,看到更多由不正确合并的代码引起的错误,更多的代码和测试打破你所依赖的代码的变化。这些问题的部分原因可能是对其他 classes 的硬依赖(尽管,诚然,发生这种情况的原因还有很多)。通过将这些风险更高的部分隐藏在抽象背后,可以减轻这种风险。这意味着您组织的结构和规模会对代码库的设计和结构产生影响。您应该选择最具成本效益的设计(长运行)。

从这个陈述中您可以得出结论,某事物是不稳定的还是稳定的依赖并不总是一门硬科学。这与包含非确定性行为的依赖项完全不同;它们总是不稳定的。

注意:我会看看我们是否可以将其中的一些信息放入本书的勘误表中。遗憾的是,无法在下一次印刷中更新文本,因为这会导致对页面布局和编号进行级联(全面)更改,这是我们的出版商不允许的。

If that dependency is some well defined domain knowledge, such as some domain-specific email validator, placed in Domain layer, and it is still under some code-change, is it considered an Anti-Pattern if we put it behind the static class and use it all over the code-base?

您的电子邮件验证器是我将放在灰色类别中的一个很好的例子;无论哪种方式都可以。它的行为是确定性的,因为:给定相同的输入,它总是会产生相同的输出。不过,您可能会问的另一个问题是,您是否需要松散耦合?如果您希望能够将一个电子邮件验证器替换为另一个,或者更一般地说,添加更多验证检查是否需要更改电子邮件验证器的消费者,就会发生这种情况?这将是一个很好的迹象,表明您又在谈论不稳定的依赖关系。

现在让我们回到您关于环境上下文反模式的问题。假设您认为此验证是一个易失性依赖项;这仍然不能使它成为一个环境上下文。 Ambient Context 的特征之一是行为可以被替换。例如,查看以下演示环境上下文的示例;它是本书清单 5.11 的简化版本:

public static class TimeProvider
{
    public static ITimeProvider Current { get; set; }
        = new DefaultTimeProvider();
}

静态 TimeProvider class 可以访问 ITimeProvider 依赖项。您希望能够替换时间提供者行为这一事实是一个非常非常强烈的迹象,表明这是一个易变的依赖关系。

如果不能用另一种依赖替换(以某种方式)易失性依赖,就没有环境上下文。例如,当您允许替换基础 Regex 时,您的验证器将成为环境上下文,如下所示:

public class EmailValidationService
{
   // Ambient Context
   public static Regex EmailValidationRegex { get; set; } = /*some regex*/;

   public static EmailValidationStatus IsValid(string email)
   {
     var isValid = EmailValidationRegex.Match(email).Length > 0; ;

     return isValid;
   }
}

Regex 是具体的 class - 不是抽象-,但这不会改变它仍然是环境上下文示例的事实。

相反,如果您确定此静态 class 包含易变行为,并且此 class 由消费者直接调用,则本书将此视为 Control Freak 反-图案:

The Control Freak anti-pattern occurs every time you depend on a Volatile Dependency in any place other than a Composition Root. It’s a violation of the Dependency Inversion Principle [...]