Php Symfony:服务和富域模型

Php Symfony:服务和富域模型,php,symfony,dependency-injection,domain-driven-design,Php,Symfony,Dependency Injection,Domain Driven Design,对于使用条令ORM的Symfony项目,我习惯于使用贫乏的领域模型,在Symfony服务中处理业务逻辑。对于涉及大量业务逻辑的项目,我想知道使用富域模型(我不熟悉)是否不是一个更好的解决方案我不想比较RDM和ADM,因为已经有很多在线资源可以用来找出每个解决方案的优缺点。我很想知道RDM是否适合Symfony项目。为了下定决心,我想了解一下我应该如何在实际应用程序中实现RMD 我的主要问题是: RDM真的适合Symfony的理念和最佳实践吗 RDM让模型做了太多的工作,这难道没有打破坚实的原则

对于使用条令ORM的Symfony项目,我习惯于使用贫乏的领域模型,在Symfony服务中处理业务逻辑。对于涉及大量业务逻辑的项目,我想知道使用富域模型(我不熟悉)是否不是一个更好的解决方案我不想比较RDM和ADM,因为已经有很多在线资源可以用来找出每个解决方案的优缺点。我很想知道RDM是否适合Symfony项目。为了下定决心,我想了解一下我应该如何在实际应用程序中实现RMD

我的主要问题是:

  • RDM真的适合Symfony的理念和最佳实践吗
  • RDM让模型做了太多的工作,这难道没有打破坚实的原则吗
  • 模型如何管理依赖关系
我将给出一个理论示例,以更清晰的方式表达我的担忧

假设我们正在开发RESTAPI。我们想要实现一个用户注册。用户注册依赖于外部服务提供商(使用API),用户创建应导致创建一些其他相关实体(此处我们将使用虚拟实体),并使用Symfony Messenger以异步方式填充ElasticSearch索引

快乐的流程如下所示:

  • 请求正文的验证
  • 调用外部服务提供者API来创建用户
  • 在数据库中插入用户数据
  • 创建与用户相关的实体
  • 填充ElasticSearch索引
  • 向用户发送确认电子邮件
  • 出于实际原因,下面的代码被简化,因此可能不是100%准确,也不是功能性的。它的目的只是展示两种设计之间的差异

    贫血领域模型实施

    <?php
    
    // src/Entity/User.php
    
    namespace App\Entity;
    
    class User
    {
        private $id;
    
        private $externalId;
    
        private $email;
    
        private $plainPassword;
    
        private $password;
    
        private $lastName;
    
        private $firstName;
    
        // Include more properties, getters and setters, ...
    }
    
    <?php
    
    // src/Entity/User.php
    
    namespace App\Entity;
    
    class User
    {
        private $id;
    
        private $externalId;
    
        private $email;
    
        private $plainPassword;
    
        private $password;
    
        private $lastName;
    
        private $firstName;
    
        // Include more properties, getters and setters, ...
    }
    

    我希望您知道,这(显然)主要是基于意见,因此您可能同意或不同意它

    我认为你对什么是富域模型和贫血症域模型有很大的误解。但让我们从几个假设开始:

    在我的理解中,symfony中的实体(更准确地说,在学说的ORM中)在大多数情况下已经是模型。因此,不需要额外的
    用户
    模型。即使您想要拆分,我也不会将完全相同的字段放入模型中,而是将实体作为字段。如果您碰巧将所有实体函数复制到模型中,那么您就做错了。由于默认情况下,实体的所有字段都应该是私有的,因此没有理由不将其视为模型。(我的意思是,它已经可以处理对象,而不是它们在关系中的ID…)

    用户
    模型/实体永远不应该关心发送电子邮件,因为它打破了关注点的分离。相反,应该有一些东西来模拟发送电子邮件的过程。我发现这是如何工作的,描述得相当清楚。注意对装运和CheckoutService->Checkout的更改

    具有讽刺意味的是,您的“贫血域模型”的用户注册服务相当不错。尽管用户实体在该实现中有点不足,它可能会验证用户实体,但除此之外,该服务可以重命名为
    UserRegistration
    ,并且非常适合RDM。(我同意,表单已经在进行验证(这真的很方便),但是可能存在一些验证,这些验证不是关于用户本身的一致性,而是关于作为数据库/模型中用户集合的一部分的用户,或者其他东西)

    总而言之:在我看来,Symfony可以很好地完成RDM。但真正的关键(一如既往)是实际选择/设计最好的模型

    本质上说:贫血意味着你没有一个地方所有的事情都是一致的,而是以一种风险的方式进行分割,即一致性/完整性或将关注点分离给独立的单位。相反,RDM使它聚集在语义上合理的地方。这并没有改变事实,你仍然想要分离关注点

    现在回答您的问题:

    RDM真的适合Symfony的理念和最佳实践吗

    为什么不呢。但这取决于建模,可能会根据Symfony的最佳实践进行调整

    RDM让模型做了太多的工作,这难道没有打破坚实的原则吗

    如果做得好,一般不会。您的实现肯定会打破稳定,但RDM不必这样做。没有人说拥有UserRegister、UserCancel和UserUpdate服务/模型是错误的。RDM是关于将语义上属于业务流程/单元的内容在代码中保持在一起(这并不否定关注点分离或单一目的)

    模型如何管理依赖关系

    因为在我看来,业务流程是模型,将充当服务,所以依赖关系就像它们在服务中处理一样。另一方面,实体永远不应该需要服务*。(可能存在一些非常特殊的情况,在这种情况下,您可能希望实际拥有一个管理实体创建/更新的服务(可能是工厂)

    你对此有何看法?我做错什么了吗


    我们可以说,您的实现是“不幸的”,因为在我的理解中,它不是RDM,并且(正如您自己所意识到的)到处都是不可靠的。因此,问题的第二部分是肯定的

    谢谢你详细的回答!基于此,我可以说我的一个困难是,我很难将模型视为数据持有者以外的东西:据我所知,模型永远不应该是服务,因此属性和依赖关系混乱不堪。我可能也混淆和混合了模型和实体的概念(对我来说不是这样)
    <?php
    
    // src/Controller/UserController.php
    
    namespace App\Controller;
    
    class UserController
    {
        public function registerAction(Request $request, UserRegistrationService $userRegistrationService, Serializer $serializer)
        {
            $user = new User();
            $form = $this->createForm(UserType::class, $user);
    
            $form->handleRequest($request);
            if (!$form->isValid()) {
                // Process errors
            }
    
            $userRegistrationService->register($user);
    
            return $this->serializer->serialize($user);
        }
    }
    
    <?php
    
    // src/Entity/User.php
    
    namespace App\Entity;
    
    class User
    {
        private $id;
    
        private $externalId;
    
        private $email;
    
        private $plainPassword;
    
        private $password;
    
        private $lastName;
    
        private $firstName;
    
        // Include more properties, getters and setters, ...
    }
    
    <?php
    
    // src/Model/UserRegistration.php
    
    namespace App\Model;
    
    class UserRegistration
    {
        private $email;
    
        private $plainPassword;
    
        private $lastName;
    
        private $firstName;
    
        private function __construct(string $email, string $plainPassword, string $lastName, string $firstName)
        {
            $this->email = $email;
            $this->plainPassword = $plainPassword;
            $this->lastName = $lastName;
            $this->firstName = $firstName;
        }
    
        // Getters
    
        public static function createFromRequest(Validator $validator, Request $request)
        {
            $requestData = $request->request->all();
            $userRegistration = new self(requestData['email'], $requestData['plainPassword'], $requestData['lastName'], $requestData['firstName']);
    
            $violations = $validator->validate($userRegistration);
            if (count($violations) > 0) {
                throw new \Exception(); // handle errors
            }
    
            return $userRegistration;
        }
    }
    
    <?php
    
    // src/Model/User.php
    
    namespace App\Model;
    
    class User
    {
        private $id;
    
        private $externalId;
    
        private $email;
    
        private $oassword;
    
        private $lastName;
    
        private $lastName;
    
        private function __construct(string $id, string $externalId, string $email, string $password, string $lastName, string $firstName)
        {
            $this->id = $id;
            $this->externalId = $externalId;
            $this->email = $email;
            $this->password = $password;
            $this->lastName = $lastName;
            $this->firstName = $firstName;
        }
    
        // Getters
    
        public static function createFromUserRegistration(Validator $validator, PasswordEncoder $passwordEncoder, ExternalProviderClient $externalProviderClient, EntityManager $em, MessageBusInterface $bus, UserRegistration $userRegistration)
        {
            $password = $passwordEncoder->encodePassword($userRegistration->getPlainPassword());
            $externalId = $externalProviderClient->registerUser([
                'email' => $userRegistration->getEmail(),
                'firstName' => $userRegistration->getFirstName(),
                'lastName' => $userRegistration->getLastName(),
            ]);
    
            $userEntity = (new \App\Entity\User())
                ->setExternalId($externalId)
                ->setEmail($userRegistration->getEmail())
                ->setPassword($password)
                ->setLastName($userRegistration->getLastName())
                ->setFirstName($userRegistration->getFirstName())
            ;
    
            $em->persist($userEntity);
            $em->flush();
    
            $id = ;
    
    
            $user = self::buildFromUserEntity($validator, $userEntity);
    
            $bus->dispatch(new UserMessage($user));
    
            return $user;
        }
    
        public static function buildFromUserEntity(Validator $validator, \App\Entity\User $userEntity)
        {
            $user = new self(
                $userEntity->getId(),
                $userEntity->getExternalId(),
                $userEntity->getEmail(),
                $userEntity->getPassword(),
                $userEntity->getLastName(),
                $userEntity->getFirstName()
            );
    
            $violations = $validator->validate($user);
            if (count($violations) > 0) {
                throw new \Exception(); // handle errors
            }
    
            return $user;
        }
    
        public function completeRegistration(EntityManager $em, ElasticSearch $elasticSearch, Mailer $mailer)
        {
            $dummy = new Dummy($this);
            $dummy->save($em);
    
            $this->populateElasticSearch($elasticSearch);
            $dummy->populateElasticSearch($elasticSearch);
    
            $this->sendConfirmationEmail($mailer);
        }
    
        public function populateElasticSearch(ElasticSearch $elasticSearch)
        {
            $this->elasticSearch->populate($this);
        }
    
        public function sendConfirmationEmail(Mailer $mailer)
        {
            $this->mailer->sendConfirmationEmail($this);
        }
    
        public function serialize(Serializer $serializer)
        {
            return $serializer->serialize($user);
        }
    }
    
    <?php
    
    // src/Controller/UserController.php
    
    namespace App\Controller;
    
    class UserController
    {
        public function registerAction(Request $request, Validator $validator, PasswordEncoder $passwordEncoder, ExternalProviderClient $externalProviderClient, EntityManager $em, MessageBusInterface $bus, Serializer $serializer)
        {
            $userRegistration = UserRegistration::createFromRequest($validator, $request);
            $user = User::createFromUserRegistration($validator, $passwordEncoder, $externalProviderClient, $em, $bus, $userRegistration);
    
            return $user->serialize($serializer);
        }
    }