DDD-实体创建业务规则验证


DDD - Entity creation business rule validation

我有这样的业务规则:

如果求职者想申请职位空缺,请确保申请中使用的简历已完成,并且求职者没有已申请该职位空缺。如果满足条件,则该应用程序将在JobApplication中编写。

这就是我想到的:

JobSeeker.php

class JobSeeker {
    private $applications;
    private $resume;
    /** Other irrelevant props **/
    public function apply(Vacancy $vacancy, Resume $resume) {
        // Business rule #1
        if(!$resume->isCompleted()) throw new 'Exception('Resume '.$resume->getTitle().' is incomplete.');
        // Business rule #2
        $alreadyApplied = array_filter($this->applications->toArray(), function(JobApplication $application) use($vacancy) {
            return $application->getVacancy() === $vacancy;
        });
        if($alreadyApplied) throw new 'Exception('Vacancy '.$vacancy->getTitle().' is already applied');
        // If both rules passed, then create a JobApplication
        $application = new JobApplication($this, $vacancy, $resume);
        $this->applications->add($application);
        return $application;
    }
}

JobApplication.php

class JobApplication {
    private $applicant;
    private $vacancy;
    private $resume;
    public function __construct(JobSeeker $applicant, Vacancy $vacancy, Resume $resume) {
        $this->applicant = $applicant;
        $this->vacancy = $vacancy;
        $this->resume = $resume;
    }
}

如果我期望每个人都只使用

$jobApplication = $jobSeeker->apply($vacancy, $jobSeeker->getResume());

那就没有问题了。

当有人做这个时,问题就出现了

$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);

第二个示例将绕过业务规则验证。

我确实想到了将规则检查分离为另一种方法:

JobSeeker.php

class JobSeeker {
    public function canApply() {
        // Here goes those 2 business rules mentioned
    }
    public function apply(Vacancy $vacancy, Resume $resume) {
        if($this->canApply($vacancy, $resume)) {
            return new JobApplication($this, $vacancy, $resume);
        }
    }
}

JobApplication.php

class JobApplication {
    public function __construct(JobSeeker $jobSeeker, Vacancy $vacancy, Resume $resume) {
        if($jobSeeker->canApply($vacancy, $resume)) {
            // Same as before
        }
    }
}

虽然第二种方法保证了业务规则约束,但它是非常多余的,仍然不能提供预期的结果。

$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);

我需要对此有所了解。

谢谢!

在我看来,根据你的操作方式,你有两个聚合根JobSeekerVacancy

Resume类似于用户的配置文件

DDD几乎什么都喜欢使用服务。

所以我们有了JobSeekerApplicaitonService,这项服务将用于外部世界。

JobSeekerApplicaitonService上,我将添加方法apply

public function apply(JobSeeker $jobSeeker, Vacancy $vacancy);

首先,我们检查是否符合商业规则。即

$jobSeeker->getResume()->isCompleted();

如果未完成此检查,则会引发错误。

接下来,我们在JobSeekerApplicaitonService中制作另一个函数,检查JobSeeker是否已经申请,也可以用于视图,让用户看到他已经申请了。

public function hasApplied(JobSeeker $jobSeeker, Vacancy $vacancy);

但这种方法现在可以用于我们的应用函数

$this->hasApplied($jobSeeker,$vacation);

在已应用时再次抛出异常。

现在,您可以节省地重新使用new JobApplication。尽管我会说JobSeekerApplicaitonService存储库并在那里创建它,但它被保存在数据库中,因为这就是应用程序服务,一个委托器。

代码

class JobSeekerApplicaitonService {
    public function apply(JobSeeker $jobSeeker, Vacancy $vacancy) {
        if ($jobSeeker->getResume()->isCompleted()) {
            // throw exception
        } elseif ($this->hasApplied($jobSeeker, $vacancy)) {
            // throw exception
        }
        // save logic or something else you want
    }
    public function hasApplied(JobSeeker $jobSeeker, Vacancy $vacancy) {
        // your check, I would now use the JobApplicationRepository
        return false;
    }
}

JobSeeker&JobApplication是正确的。JobSeeker.apply方法充当JobApplications:的工厂

job_application = job_seeker.apply(vacancy, resume)

看起来不错。

然而,下面的陈述没有多大意义:

$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);

考虑到现实世界,你有没有见过一份求职申请凭空出现并落在你的桌子上?我没有:)在大多数情况下,一个实体是从另一个创建的:

employee = company.hire(person)
invoice = employee.create_invoice(customer, invoice_terms)
etc...

如果您在命令处理程序中看到一个实体是"new ed",它应该会引起注意。