如何使您的实体非动态性并保持模型的一致性
#php #体系结构 #ddd

我记得当我从战术设计开始时,我到处都在听到我们必须制作非动态实体。贫血实体是指实体作为数据结构,没有大量的固定器,没有逻辑。

但是,很难找到解决方案,即如何使实体非动态。

在本文中,我想向您展示,您如何改变对实体的思考并使它们变得非动态。

首先,我想向您展示泄漏的模型,我敢肯定您已经在许多项目中看到了它

泄漏模型

让我们考虑一个泄漏模型的示例。

正如我之前提到的,我们需要它贫血的实体。在这里,我们有一个具有两个参数phoneNumberphoneNumberAreaCode的简单用户实体。区域代码不过是该数字的前缀,例如+48,而phoneNumber就是其余的。

这样说,我们拥有带有代码和编号的整个电话号码 - +48 xxx xxx xxx(格式和编号当然取决于您的国家)。

class User
{
    private ?string $phoneNumber = null;
    private ?string $phoneNumberAreaCode = null;

    public function setPhoneNumber(string $phoneNumber): void
    {
        $this->phoneNumber = $phoneNumber;
    }

    public function setPhoneNumberAreaCode(string $phoneNumberAreaCode): void
    {
        $this->phoneNumberAreaCode = $phoneNumberAreaCode;
    }

    public function getPhoneNumber(): ?string
    {
        return $this->phoneNumber;
    }

    public function getPhoneNumberAreaCode(): ?string
    {
        return $this->phoneNumberAreaCode;
    }
}

您可以猜到,这两个值不应单独持续。

是的,那么我们接下来要做什么?也许是服务?是的!服务将是可以设置数字和前缀的事物的完美元素。像乐团的指挥!

这是一项服务:

class UpdatePhoneNumberService
{
    public function __construct(
        private readonly UserRepositoryInterface $userRepository
    ) {
    }

    /**
     * @throws UpdatePhoneNumberException
     */
    public function update(string $phoneNumber, string $areaCode, string $userId): void
    {

        // format the numbers 

        // check if the parameters are not empty

        // tens of lines...

        // check if the number isn't assigned to any other user

        // again... tens of lines...

        $user = $this->userRepository->findById($userId);

        // finally you can make a set 
        $user->setPhoneNumber($phoneNumber);
        $user->setPhoneNumberAreaCode($areaCode);

        $this->userRepository->save($user);
    }
}

代码足够好,直到另一个开发人员无法进入您服务的数十个代码。只是找出一个更好的解决方案。

如果我们拥有公共API的用户实体(setPhoneNumbersetPhoneNumberAreaCode)。

class PhoneController
{
    ...

    public function updateAction(
        Request $request,
        User $user, // logged user
    ) {
        ...
        $user->setPhoneNumber(...);
        $this->userRepository->save($user);
        ...
    }

不好了!开发人员忘记了区域代码!

这里发生了什么?新的开发人员忘记了区域代码。
那是一个泄漏的模型!如果您有一些不应该单独更改的参数,则实体必须检查一致性。

真理的一个来源应该在实体中。

好的,但是我该如何更改以实现它?

如果实体不是服务并且我们不能注入任何对象,我们如何将逻辑移至实体?

让我们使用另一个构建块,一个策略。看看一个重构实体的示例。

class User
{
    private string $id;
    private PhoneNumber $phoneNumber;
    private bool $phoneNumberVerified = false;

    public function __construct()
    {
        $this->id = Uuid::uuid4()->toString();
    }

    public function getId(): string
    {
        return $this->id;
    }

    public function updatePhoneNumber(PhoneNumber $phoneNumber, ChangePhoneNumberPolicy $policy): Result
    {
        $result = $policy->canBeUpdated($this, $phoneNumber);

        if ($result->isFailure()) {
            return $result;
        }

        $this->phoneNumber = $phoneNumber;

        return Result::success(new PhoneNumberUpdatedEvent($this->getId(), $phoneNumber));
    }

您现在可以看到,实体具有updatePhoneNumber的方法,没有sets方法。 updatePhoneNumber方法采用参数ChangePhoneNumberPolicy

我们还可以注意到新的builidng阻止了一个值对象-PhoneNumber。因此,它迫使验证,我们无法在没有必要参数的情况下坚持使用用户实体。

让我们看看该政策。

class ChangePhoneNumberPolicy
{
    public function __construct(private readonly UserRepositoryInterface $userRepository)
    {
    }

    public function canBeUpdated(User $user, PhoneNumber $phoneNumber): Result
    {
        $differentUser = $this->userRepository->findByPhoneNumber($phoneNumber);

        if ($differentUser instanceof User && !$differentUser->isEqual($user)) {
            return Result::failure(new Reason('the phone number is already in use'));
        }

        // ... other conditions ...

        // if the requires changed, you'll change only the policy, or make interface and you'll change your DI config

        return Result::success();
    }
}

主要是策略将在接口后面,因为逻辑可能会改变。有时,您会发现政策是通过设定方法注入的,并且可以通过工厂构建实体。
通常,我们最终获得了一个更改的地方,您的模型将是一致和验证的。现在您知道如何使用策略。

我知道这仅仅是开始,我只提出了一个庞大的主题。要使用控制器,存储库观看解决方案,等等,请检查GitHub repo

How to make your entity non-anemic, and keep the consistency of the model

I remember when I started my way with Tactical Design, everywhere I heard that we have to make non-anemic entities. By anemic entities I mean entities as a data structure, with plenty of setters and no logic.

But, that was tough to find out a solution, to how to make the entity non-anemic.

In this article, I want to show you, how you can change your thinking about entities and make them non-anemic.

First I want to show you the leaking model, I'm sure you've seen it in many projects

A leaking model

Let's consider the example of a leaking model.

As I mentioned before we need it anemic entity. Here we have, a simple User entity with two parameters koude0 and koude1. Area Code is nothing more than the prefix of the number, for example…