结合域与活动记录

Combine domain with active record

我用yii2,发现它的active record很方便

但有时我发现我们总是把逻辑函数放在我认为应该属于域的活动记录中。

而且我查了一些书,其中大部分建议使用数据映射器将数据库记录映射到域。

虽然这是一个分割域和数据的好方法,但我不想浪费 yii2 的活动记录功能。

我认为我们可以从活动记录扩展域,这样数据库操作将在域的父 class 活动记录中进行,业务逻辑操作将在域中进行:

class UserModel extends ActiveRecord{
      // do database operations
}

class UserDomain extends UserModel{
    // do domain's logic
}

不知道这个设计好不好?请告诉我你的建议。

更新#1

class UserDomain {
    private $model;

    public function __construct(UserModel $model){
         $this->model=$model;
    }

    public function __set($name, $value){
         if (isset($this->model->$name)) {
             $this->model->$name=$value;
         } else {
             $this->$name=$value;
         }
    }

    public function __get($name){
         if (isset($this->model->$name)) {
             return $this->model->$name;
         } else {
             return $this->$name;
         }
    }
}

分离域和数据层

您假设的方法肯定比在 ActiveRecord 中编写所有业务逻辑更好 class。它将使您的代码易于维护和清晰。在我看来,错过的一件事是灵活性。主要思想是:

Domain classes should use implementation classes, not inherit from them.

这不是公理,但在许多情况下是正确的。这里是an article,帮助您选择您需要的设计。

一个简单的例子展示了如何用组合代替继承:

class UserDomain {

    private $model;

    public function __construct(UserModel $model)
    {
        $this->model = $model;
    }
}

这是获得灵活性的第一步。它允许您使用多个模型 class 并使 UserDomain 更易于测试,因为它具有干净且 松耦合 依赖性。

Yii2 使用 Dependency Injection pattern help you control such dependencies. Here is a link to official docs with example of usage Dependency Injection Container。简而言之,您可以像这样创建域 class 的实例:

$user = $container->get('UserDomain');

Yii 会为你注入所有需要的依赖。


访问数据层

另一个问题是关于从域访问数据层。我很确定,使用 PHP magic methods 是个坏主意。

UserDomain class 您应该使用更高级别的方法,这与您在 UserModel 所做的不同。因此,通常情况下,您在域层没有 setEmail() 方法,而是使用 updateProfile() 代替。

class UserDomain
{

    public function updateProfile(string $name, string $email)
    {
        $this->model->name = $name;
        $this->model->email = $email;
        $this->model->save();
    }

}

如果使用属性很重要,就像您在评论中假设的那样,我更愿意使用 Properties 的 Yii 实现。对于电子邮件,代码将如下所示:

 /**
 * Class UserDomain
 *
 * @property string email
 */
class UserDomain extends \yii\base\Object
{

    public function setEmail(string $email)
    {
        $this->model->email = trim($email);
    }

}

$userDomain = new UserDomain();
$userDomain->email = '11@gmail.com';

请注意,UserDomain\yii\base\Object 扩展而来,使 setter 可用。


自动设置 ActiveRecord 属性

如果您真的需要从域层设置很多 ActiveRecord 属性,那么在我看来,它 根本不是 域层。在这种情况下,继承是一个 好的 决定,因为您正在使用业务逻辑扩展 ActiveRecord 的基本功能,而不是将 AR 用作数据映射器。

这就是 ActiveRecord 的设计目的。因此,只要您的域层保持简单明了,就会方便和优化。