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 继承映射来映射一个公共实体的三个不同上下文 (类):NotActivatedCustomer、DeletedCustomer 和 Customer。此外,还有一个 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 并且 运行 DeleteCustomerHandler,Customer 将被更新,但它的鉴别器字段不会!
谷歌搜索,没有办法更新鉴别器字段,不像我那样采用一些骇人听闻的方式(运行手动原始查询来更新字段)。
此外,我需要使用 EntityManager->merge() 方法将手动初始化的 DeletedCustomer 添加到内部 UnitOfWork。看起来也有点脏,而且它是 Doctrine 3 的弃用方法,所以问题还有是否有更好的方法来处理我的情况?
所以,总结所有问题:
- 我将客户的状态更改为DeletedCustomer完全错误吗?我只是想避免Customer God Object,区分这个Entity的限界上下文,有点那个。
- 如何避免出现EntityManager->merge()? AuthenticatedCustomer 来自会话 (JWT)。
我认为您想要避免 Customer 变成上帝对象是完全正确的。继承是一种方法,但是针对不同状态的客户使用它会导致问题。
我遇到的两个关键问题:
- 随着新状态的出现,您会继续添加不同的继承实体吗?
- 当您让客户经历两种不同的状态时会发生什么,例如客户是
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
,但您需要做更多的样板文件。
长话短说。
我使用 Doctrine 的单一 Table 继承映射来映射一个公共实体的三个不同上下文 (类):NotActivatedCustomer、DeletedCustomer 和 Customer。此外,还有一个 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 并且 运行 DeleteCustomerHandler,Customer 将被更新,但它的鉴别器字段不会! 谷歌搜索,没有办法更新鉴别器字段,不像我那样采用一些骇人听闻的方式(运行手动原始查询来更新字段)。
此外,我需要使用 EntityManager->merge() 方法将手动初始化的 DeletedCustomer 添加到内部 UnitOfWork。看起来也有点脏,而且它是 Doctrine 3 的弃用方法,所以问题还有是否有更好的方法来处理我的情况?
所以,总结所有问题:
- 我将客户的状态更改为DeletedCustomer完全错误吗?我只是想避免Customer God Object,区分这个Entity的限界上下文,有点那个。
- 如何避免出现EntityManager->merge()? AuthenticatedCustomer 来自会话 (JWT)。
我认为您想要避免 Customer 变成上帝对象是完全正确的。继承是一种方法,但是针对不同状态的客户使用它会导致问题。
我遇到的两个关键问题:
- 随着新状态的出现,您会继续添加不同的继承实体吗?
- 当您让客户经历两种不同的状态时会发生什么,例如客户是
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
,但您需要做更多的样板文件。