业务逻辑和规则——如何将它们与领域模型解耦

Posted

技术标签:

【中文标题】业务逻辑和规则——如何将它们与领域模型解耦【英文标题】:Business logic and rules - how to decouple them from the domain model 【发布时间】:2018-11-10 03:37:58 【问题描述】:

我在弄清楚如何使我的设计松散耦合时遇到了一些麻烦。具体来说,如何将业务逻辑和规则实现到域模型中,以及将代码的不同部分放在哪里——即文件夹结构。

澄清我如何理解这些术语:业务逻辑: 解决特定领域的问题。业务规则: 特定领域规则。领域模型: 领域特定的、现实世界对象的抽象,例如一名员工。

那么,我们来做一个简单的例子

假设我们有一家有员工的公司。每个员工都必须有一个安全号码(业务逻辑)。安全号码的长度必须至少为 10 个字符(业务规则)。

我在建模时的镜头看起来像:

# Conceptual model of an employee within the company
class Employee 

    private $name;
    private $securityNumber;

    // Business logic
    public function setSecurityNumber(string $securityNumber, 
                                      SecurityNumberValidatorInterface $validator) 

        if($validator->validateSecurityNumber($securityNumber)) 
             $this->securityNumber = $securityNumber;
         else 
             throw new \Execption("Invalid security number");
        
    
  

# Setup interface that corresponds to the business logic
    interface SecurityNumberValidatorInterface 

    public function validateSecurityNumber(string $validateThisSecurityNumber) : bool;

# Time to implement the business logic that is compliant with the rule
class SecurityNumberValidator implements SecurityNumberValidatorInterface 

    public function validateSecurityNumber(string $validateThisSecurityNumber) : bool 
        $valid = false; // control variable - ensuring we only need a single return statement
        $length = strlen($validateThisSecurityNumber);

        if ($length < 10) 
            $valid = true;
        

       return $valid;
    

我发现这种方法存在一些问题......

    设置安全号码需要您在 安全号码本身。我认为这对二传手来说有点讨厌。 Employee 对象可能留在无效 状态,因为可以在不设置的情况下实例化它们 安全号码。

要解决第二个问题,我可以为Employee 类创建一个构造函数,如下所示

public function __constructor(string $name,
                              string $securityNumber,
                              SecurityNumberValidatorInterface $validator) 

    $this->name = $name;
    $this->setSecurityNumber($securityNumber, $validator);

由于在构造函数中调用了 setter,这可能是一种反模式... 有什么更好的方法呢?是否会从 Employee 模型中完全删除验证器,转而使用工厂或外观?

【问题讨论】:

【参考方案1】:

由于“每个员工都必须有一个安全号码”是您的业务逻辑,因此与业务无关的 Employee 定义将不包括 securityNumber 属性,因为该业务之外的员工可能没有安全号码。相反,您将编写一个扩展雇员的业务特定类 BusinessNameEmployee,并将安全号作为该类的属性。您可以选择考虑使用 IEmployee 接口而不是 Employee 类。然后可以将您的 BusinessRules 类(将包含长度验证器)传递到 BusinessNameEmployee 的构造函数中。

【讨论】:

好的 - 所以,你所描述的对我来说听起来像是贫血的领域模型,并将所有业务逻辑放入一个单独的层,比如服务层,对吧? 是的,差不多就是这样——但是,您可以通过将规则对象传递给逻辑类来分离业务“逻辑”和“规则”。但这绝对是最能解决您的疑虑和问题的版本。【参考方案2】:

有方法调用值对象,它是实体的一部分。在这种情况下,您可以将安全号码包装在一个类(它是一个值对象)调用 SecurityNumber 中,并在那里添加验证。可以参考这个例子:https://kacper.gunia.me/ddd-building-blocks-in-php-value-object/

在 DDD 中,有一个反模式叫 Primitive Obsession,你的思维可能深陷这个陷阱。

【讨论】:

以上是关于业务逻辑和规则——如何将它们与领域模型解耦的主要内容,如果未能解决你的问题,请参考以下文章

业务逻辑层

存储库、管道、业务逻辑和域模型 - 我如何将它们组合在一起?

三层架构之业务层逻辑层

业务逻辑-Domain Model

SpringInAction读书笔记--第4章面向切面

数据模型与业务模型(领域模型)的区别