Doctrine2:实体的限界上下文和 SINGLE_TABLE 继承映射。有点困惑我是不是做对了

Doctrine2: Bounded Contexts of the Entity and SINGLE_TABLE inheritance mapping. Kinda confused on am I doing it the right way

长话短说。

我使用 Doctrine 的单一 Table 继承映射来映射一个公共实体的三个不同上下文 (类):NotActivatedCustomerDeletedCustomerCustomer。此外,还有一个 AbstractCustomer 包含下一个:

App\Identity\Domain\Customer\AbstractCustomer:
  type: entity
  inheritanceType: SINGLE_TABLE
  discriminatorColumn:
    name: discr
    type: string
  discriminatorMap:
    Customer: App\Identity\Domain\Customer\Customer
    NotActivatedCustomer: App\Identity\Domain\Customer\NotActivatedCustomer
    DeletedCustomer: App\Identity\Domain\Customer\DeletedCustomer
  table: customer
  id:
    id:
      type: customer_id
      unique: true
      generator:
        strategy: CUSTOM
      customIdGenerator:
        class: Symfony\Bridge\Doctrine\IdGenerator\UuidV4Generator
  fields:
    email:
      type: email
      length: 180
      unique: true

子类型定义示例:

<?php

declare(strict_types=1);

namespace App\Identity\Domain\Customer;

use App\Identity\Domain\User\Email;

class DeletedCustomer extends AbstractCustomer
{
    public const TYPE = 'DeletedCustomer';

    public function __construct(CustomerId $id)
    {
        $this->_setId($id);
        $this->_setEmail(new Email(sprintf('%s@mail.local', $id->value())));
    }
}

用例:

<?php

declare(strict_types=1);

namespace App\Identity\Application\Customer\UseCase\DeleteCustomer;

use App\Identity\Application\Customer\CustomerEntityManager;
use App\Identity\Application\User\AuthenticatedCustomer;
use App\Identity\Domain\Customer\DeletedCustomer;
use App\Shared\Application\ImageManager;

final class DeleteCustomerHandler
{
    private CustomerEntityManager $customerEntityManager;
    private AuthenticatedCustomer $authenticatedCustomer;
    private ImageManager $imageManager;

    public function __construct(AuthenticatedCustomer $authenticatedCustomer,
                                CustomerEntityManager $customerEntityManagerByActiveTenant,
                                ImageManager $customerPhotoManager)
    {
        $this->customerEntityManager = $customerEntityManagerByActiveTenant;
        $this->authenticatedCustomer = $authenticatedCustomer;
        $this->imageManager = $customerPhotoManager;
    }

    public function handle(): void
    {
        $customer = $this->authenticatedCustomer->customer();

        $photo = (string) $customer->photo();

        $deletedCustomer = new DeletedCustomer($customer->id());

        // TODO OR return DeletedCustomer that way
        // $deletedCustomer = $customer->deactive();

        //  entityManager->merge() called here
        $this->customerEntityManager->sync($deletedCustomer);

        // simple entityManager->flush() under the hood
        $this->customerEntityManager->update();

        // that's a raw query to update discriminator field, hackish way I'm using
        // UPDATE customer SET discr = ? WHERE id = ?
        $this->customerEntityManager->updateInheritanceType($customer, DeletedCustomer::TYPE);

        if ($photo) {
            $this->imageManager->remove($photo);
        }
    }
}

因此,如果您已经有一个现有的 Customer 并且 运行 DeleteCustomerHandlerCustomer 将被更新,但它的鉴别器字段不会! 谷歌搜索,没有办法更新鉴别器字段,不像我那样采用一些骇人听闻的方式(运行手动原始查询来更新字段)。

此外,我需要使用 EntityManager->merge() 方法将手动初始化的 DeletedCustomer 添加到内部 UnitOfWork。看起来也有点脏,而且它是 Doctrine 3 的弃用方法,所以问题还有是否有更好的方法来处理我的情况?

所以,总结所有问题:

  1. 我将客户的状态更改为DeletedCustomer完全错误吗?我只是想避免Customer God Object,区分这个Entity的限界上下文,有点那个。
  2. 如何避免出现EntityManager->merge()AuthenticatedCustomer 来自会话 (JWT)。

我认为您想要避免 Customer 变成上帝对象是完全正确的。继承是一种方法,但是针对不同状态的客户使用它会导致问题。

我遇到的两个关键问题:

  1. 随着新状态的出现,您会继续添加不同的继承实体吗?
  2. 当您让客户经历两种不同的状态时会发生什么,例如客户是 NotActivatedCustomer 但现在是 DeletedCustomer

因此,只有当继承的类型确实是更具体的类型时,我才会保留继承,其中给定实体在其整个生命周期中只会是这些类型中的一种。例如,汽车不会变成摩托车。

我有两种不同的解决问题的模式。我倾向于从第一个开始,然后转到第二个。

interface DeletedCustomer
{
  public function getDeletedAt(): DateTime;
}

interface NotActivatedCustomer
{
  public function getCreatedAt(): DateTime;
}

class Customer implements DeletedCustomer, NotActivatedCustomer
{
  private $id;
  private $name;
  private DateTime $deletedAt;
  private bool $isActivated = false;

  public function getDeletedAt(): DateTime {...}
  public function getCreatedAt(): DateTime {...}
}

class DeletedCustomerRepository
{
  public function findAll(): array
  {
    return $this->createQuery(<<<DQL
      SELECT customer 
      FROM   Customer 
      WHERE  customer.deletedAt IS NOT NULL
    >>>)->getQuery()->getResults();
  }
}

class NotActivatedCustomerRepository
{
  public function findAll(): array
  {
    return $this->createQuery(<<<DQL
      SELECT customer 
      FROM   Customer 
      WHERE  customer.isActivated = false
    >>>)->getQuery()->getResults();
  }
}

class DeletedCustomerService
{
  public function doTheThing(DeletedCustomer $customer) {}
}

这减少了耦合,这是上帝对象的主要问题之一。因此,当列开始激增时,我可以将它们移到加入 Customer 的真实实体中。引用 DeletedCustomer 的组件仍将收到一个。

第二个模式是精简版事件源——与“CustomerLifecycleEvent”实体具有多对一关系。根据客户是否有“已删除”事件进行查询。第二种方法要复杂得多,包括更新和查询。您仍然可以拥有 return 个实体的专用存储库,例如 DeletedCustomer,但您需要做更多的样板文件。