【问题标题】:Where to place business logic in Symfony2?Symfony2 中的业务逻辑应该放在哪里?
【发布时间】:2012-07-16 09:47:55
【问题描述】:

在阅读了很多帖子和 Stack Overflow 资源之后,我仍然对“将业务逻辑放在哪里?”这个著名的问题有些疑问。看了StackOverflow QuestionA Blog Post,相信我已经很好地理解了代码分离的问题了。

假设我有一个 Web 表单,您可以在其中添加将被添加到数据库的用户。这个例子涉及到这些概念:

  • 表格
  • 控制器
  • 实体
  • 服务
  • 存储库

如果我没有遗漏什么,您必须创建一个具有一些属性、getter、setter 等的实体,以使其持久保存到数据库中。如果您想获取或写入该实体,您将使用entityManager,对于“非规范”查询,使用entityRepository(您可以在其中使用“查询语言”查询)。

现在您必须为所有业务逻辑定义一个服务(即带有“惰性”实例的 PHP 类);这是放置“重”代码的地方。将服务记录到应用程序中后,您几乎可以在任何地方使用它,这涉及代码重用等。

当您呈现和发布表单时,您将它与您的实体绑定(当然还有约束),并使用上面定义的所有概念将所有内容组合在一起。

所以,“old-me”会这样写控制器的动作:

public function indexAction(Request $request)
    {
        $modified = False;
        if($request->getMethod() == 'POST'){ // submit, so have to modify data
            $em = $this->getDoctrine()->getEntityManager();
            $parameters = $request->request->get('User'); //form retriving
            $id = $parameters['id'];
            $user = $em->getRepository('SestanteUserBundle:User')->find($id);
            $form = $this->createForm(new UserType(), $user);
            $form->bindRequest($request);
            $em->flush();
            $modified = True;
        }

        $users = $this->getDoctrine()->getEntityManager()->getRepository('SestanteUserBundle:User')->findAll();
        return $this->render('SestanteUserBundle:Default:index.html.twig',array('users'=>$users));
    }

“New-me”以这种方式重构了代码:

   public function indexAction(Request $request)
    {
        $um = $this->get('user_manager');
        $modified = False;
        if($request->getMethod() == 'POST'){ // submit, so have to modify data
            $user = $um->getUserById($request,False);
            $form = $this->createForm(new UserType(), $user);
            $form->bindRequest($request);
            $um->flushAll();
            $modified = True; 
        }
        $users = $um->showAllUser();
        return $this->render('SestanteUserBundle:Default:index.html.twig',array('users'=>$users));
    }

$um 是一个自定义服务,其中存储了从 #1 代码段到 #2 代码段您看不到的所有代码。

所以,这是我的问题:

  1. 我终于掌握了 symfony2 和 {M}VC 的精髓了吗?
  2. 重构是好的吗?如果没有,有什么更好的方法?

Post Scriptum:我知道我可以使用 FOSUserBundle 进行用户存储和身份验证,但这是一个自学如何使用 Symfony 的基本示例。 此外,为了工作,我的服务被注入了 ORM.Doctrine.* (只是为谁读过这个问题而感到困惑)

【问题讨论】:

  • $modified 的作用是什么,getUserById() 的第二个参数的作用是什么?
  • 好吧,领域业务逻辑进入model layer。莫斯利在domain objects
  • @redbirdo :问题的目的无关紧要。
  • @tereško :所以我正在“走好路”。我已经阅读了您的答案,并且发现某些点完全符合我上面的描述(实体:域对象和验证器、服务:作用于该域对象和数据映射器(即存储库和实体管理器))
  • @DonCallisto 我认为 getUserById() 的第二个参数确实很重要,因为我试图了解您的 UserManager 是否具有定义明确的接口(在“重构是否良好”的上下文中) .至少我建议您避免将 $request 对象暴露给您的 UserManager,因为它是一个 UI 构造。最好从 $request 中提取 id 并将其传递给 UserManager。

标签: model-view-controller symfony


【解决方案1】:

关于业务逻辑的放置位置有两种主要方法:SOA 架构和领域驱动架构。如果你的业务对象(实体)是贫乏的,我的意思是,如果它们没有业务逻辑,只有 getter 和 setter,那么你会更喜欢 SOA。但是,如果您在业务对象中构建业务逻辑,那么您会更喜欢另一个。 Adam Bien 讨论了这些方法:

使用 Java EE 6 进行领域驱动设计:http://www.javaworld.com/javaworld/jw-05-2009/jw-05-domain-driven-design.html

采用 Java EE 6 的精益服务架构:http://www.javaworld.com/javaworld/jw-04-2009/jw-04-lean-soa-with-javaee6.html

它是 Java,但您可以理解。

【讨论】:

  • 感谢您回答我的问题。我已经阅读了这些文章,但这些文章并没有给我更多我想知道的内容:)如果您可以用更多细节扩展您的答案并将其与我的“真实”示例相匹配,我会很高兴(因为所有社区将)阅读、理解并 - 甚至 - 给你一个积极的反馈。
【解决方案2】:

重构是好的吗?如果没有,有什么更好的方法?

最佳框架实践之一是使用参数转换器直接从用户请求中调用实体

来自 Symfony 文档的示例:

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

/**
 * @Route("/blog/{id}")
 * @ParamConverter("post", class="SensioBlogBundle:Post")
 */
public function showAction(Post $post)
{

}

有关参数转换器的更多信息

http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html

【讨论】:

  • 是的,我知道,这是一个非常古老的答案,顺便说一句,您应该发表评论:您没有回答我的问题。但是现在我知道将业务逻辑放在哪里:它属于哪里。我的意思是,如果逻辑与实体相关,则几乎必须(至少对我而言)将其保留在实体内部;否则我现在使用一个服务,可以将逻辑拆分为子服务,以保持单一原则责任有效。干杯!
  • 逻辑不应该放在实体中
【解决方案3】:

Robert C. Martin(代码简洁的人)在他的新书clean architecture 中说,您应该将业务逻辑独立于框架,因为框架会随着时间而改变。

因此,您可以将业务逻辑放在单独的文件夹中,例如 App/Core 或 App/Manager 并避免此处的 symfony 类继承:

<?php

namespace App\Core;


class UserManager extends BaseManager implements ManagerInterface
{

}

【讨论】:

  • 是的,没错,我几年前就读过那本书,从那以后我的想法对这个话题更加准确:)
  • 是的,我知道它是如何工作的:自 5/7 年以来一直在这里 :) 这个答案顺便说一句似乎太“笼统”而无法投票
  • 这是非常具体的 symfony,这就是我在框架中实现业务逻辑的方式
【解决方案4】:

我知道这是一个老问题,但由于我有类似的问题,我想分享我的经验,希望它可能对某人有所帮助。 我的建议是引入命令总线并开始使用命令模式。工作流程差不多是这样的:

  1. 控制器接收请求并将其转换为命令(可能使用表单来执行此操作,并且您可能需要一些 DTO 将数据从一层干净地移动到另一层)
  2. 控制器将该命令发送到命令总线
  3. 命令总线查找处理程序并处理命令
  4. 然后控制器可以根据需要生成响应。

基于您的示例的一些代码:

public function indexAction(Request $request)
{
    $command = new CreateUser();
    $form = $this->createForm(new CreateUserFormType(), $command);
    $form->submit($request);
    if ($form->isValid()) {
        $this->get('command_bus')->handle();
    }
    return $this->render(
        'SestanteUserBundle:Default:index.html.twig', 
        ['users' => $this->get('user_manager')->showAllUser()]
    );
}

然后您的命令处理程序(它实际上是服务层的一部分)将负责创建用户。这有几个优点:

  • 您的控制器不太可能变得臃肿,因为它们几乎没有逻辑
  • 您的业务逻辑与应用程序 (HTTP) 逻辑分离
  • 您的代码变得更加可测试
  • 您可以重复使用相同的命令处理程序,但数据来自不同的端口(例如 CLI)

还有一些缺点:

  • 应用此模式所需的类数量较多,并且通常与应用程序公开的功能数量呈线性关系
  • 移动的部分更多,推理起来有点困难,因此团队的学习曲线可能会更陡峭。

几个值得注意的命令总线:

https://github.com/thephpleague/tactician https://github.com/SimpleBus/MessageBus

【讨论】:

    猜你喜欢
    • 2016-12-18
    • 2017-08-11
    • 1970-01-01
    • 2011-06-28
    • 1970-01-01
    • 1970-01-01
    • 2011-08-02
    • 1970-01-01
    • 2012-03-28
    相关资源
    最近更新 更多