【问题标题】:Laravel: Service/Repository Pattern and duplicating codeLaravel:服务/存储库模式和重复代码
【发布时间】:2016-10-10 04:02:03
【问题描述】:

在我的项目中,我决定使用服务模式(可能与存储库模式)来处理我的应用程序中的业务逻辑。例如,我有一个代表客户的 Client 模型和一个负责特定于客户的业务逻辑的相应 ClientService

class ClientService extends Service implements ClientServiceContract
{
    public function create(array $attributes)
    {
        // Create a new client...
    }

    public function doSomethingElse(Client $client)
    {
        // Do something else
    }
}

例如,我有另一个服务UserService,它类似于上面的ClientService,因为它具有创建User 模型和执行其他操作的方法。

现在在我的网站上,假设我有一个表格,有人可以填写表格来登记他们有兴趣成为客户。在我的后端系统中,我想创建一个按钮,该按钮获取客户的兴趣记录ClientInterest,并创建一个Client、一个User,将两者关联起来,最后向新用户发送一封包含详细信息的电子邮件.

什么时候使用服务模式最好把这个逻辑放在哪里?

我考虑过:

  1. 创建一个服务和方法ClientInterestService::createClientAndUser(...),它将使用ClientServiceUserService类来创建ClientUser实例,然后在触发一个发送事件的事件之前执行关联电子邮件。这种方法意味着我没有复制代码,但是我将类耦合在一起并且我打破了一些 SOLID 原则。我不确定,但我觉得这也不太适合测试。

  2. 如上所述,创建一个服务类和方法来执行逻辑,但我不会使用其他两个服务,而是编写逻辑来创建 ClientUser 实例,执行关联并触发事件发送电子邮件。这种方法感觉更好,我的代码更松散耦合,我没有违反任何 SOLID 原则,但是,我可能会重复代码。

  3. 只需将 ClientInterestService::createClientAndUser(...) 中的逻辑放入我的控制器中即可。这样做意味着我的控制器中有业务逻辑,这与拥有服务的意义相悖。

【问题讨论】:

    标签: php laravel laravel-5 architecture repository-pattern


    【解决方案1】:

    我认为,如果您将其分解为更小的步骤,您就可以实现 DRY 架构。我看到的步骤是:

    • 创建客户端
    • 创建用户
    • 关联(通过数据透视表、连接表等)
    • 电子邮件

    为避免出现可怕的重复代码,您需要在一个或多个服务类中围绕这些代码创建一个方法。然后,您将创建一个操作,封装基于这些方法所涉及的所有步骤。

    不要害怕在你的服务类之外实现一些东西——这并不意味着它在你的服务层之外。

    我将注册客户兴趣视为一项操作。您遵循同步步骤来实现所需的操作。因此,基于创建用户、客户端等方法,我们可以构建一个操作来注册客户兴趣,如下所示:

    <?php
    
    class ClientService {
    
    public function addAction(IAction $action)
    {
      return $action->process();
    }
    
    public function createUser() {} // business logic for creating a user.
    
    public function createClient() {} // business logic for creating a client.
    
    public function createAssociation() {} // business logic for creating an association.
    
    } 
    
    interface IAction {
    
      public function process();
    
    }
    
    class RegisterClientInterestAction implements IAction {
    
      protected $client; 
    
      public function __construct(ClientService $client)
      {
        $this->client = $client; 
      }
    
      public function process()
      {
        $this->createUser()->createClient()->createAssociation();
      }
    
      private function createUser() {} // interact with your client service to call the method $client->createUser()
    
      private function createClient() {} // interact with your client service to call the method $client->createClient()
    
      private function createAssociation() {} // interact with your client service to call the method $client->createAssociation()
    
    }
    
    //USAGE
    
    $service  = new ClientService; 
    $results  = $service->addAction(new RegisterClientInterestAction($service));
    
    ?> 
    

    通过这种方式,您可以在新操作中使用 createUser 等方法,而无需复制代码。通过在服务类上添加 addAction,您仍在服务层内部执行业务逻辑。


    如果需要两个或更多服务,我会采取稍微不同的方法,将execute 移动到该操作的位置。

    就处理多个服务而言,您可以在操作的构造函数中使用 DI。

    像这样:

    <?php
    
    class Service {
    
      public function addAction(IAction $action)
      {
        return $action->process();
      }
    
      // Other stuff for a base service...
    
    }
    
    class UserService extends Service {
    
      public function createUser() {} // business logic for creating a user.
    
    }
    
    class ClientService extends Service {
    
    public function createClient() {} // business logic for creating a client.
    
    public function createAssociation() {} // business logic for creating an association.
    
    } 
    
    interface IAction {
    
      public function process();
    
    }
    
    class RegisterClientInterestAction implements IAction {
    
      protected $client; 
    
      protected $service; 
    
      public function __construct(ClientService $client, UserService $user)
      {
        $this->user   = $user; 
        $this->client = $client; 
      }
    
      public function process()
      {
        $this->createUser()->createClient()->createAssociation();
      }
    
      private function createUser() {} // interact with your user service to call the method $client->createUser()
    
      private function createClient() {} // interact with your client service to call the method $client->createClient()
    
      private function createAssociation() {} // interact with your client service to call the method $client->createAssociation()
    
    }
    
    //USAGE
    
    $service  = new Service; 
    $results  = $service->addAction(new RegisterClientInterestAction(new ClientService, new UserService));
    
    ?>
    

    【讨论】:

    • 不错的方法。但是,如果我有两个不同的服务类 ClientServiceUserService,每个都有各自的 create 方法,你会如何处理它?
    • 我已经编辑了我的答案,以概述您如何处理多个服务。这是一个很好的方法,直到你发现自己需要注入很多类来处理动作。 (也许 5 个或更多?)。如果是这样,您应该重新设计层次结构。
    • 感谢更新。我真的很喜欢您创建可以以这种方式接受和使用服务的“动作”的方法。也许我可以在我的服务类中编写更通用的、实体特定的方法,然后实现接受和使用这些服务的操作来执行所需的功能。我倾向于为几乎所有内容编写动作,因为这对我来说感觉更自然。感觉有点像我在早期版本的 Laravel 中习惯的命令总线样式架构,但更干净。
    • 如果你觉得它更自然,那么其他人肯定也会觉得自然——这在维护和扩展代码库时非常棒,因为事情是人们期望的。我很高兴能提供帮助! :)
    【解决方案2】:

    对我来说感觉最好的是您提出的#2 解决方案。

    我喜欢做的是构建两个服务类并查看有哪些重复项,然后将任何重复项重构/提取到另一个类。这样,所有类都非常可测试,并且您违反任何 SOLID 原则的可能性最小。

    【讨论】:

    • 感谢您的回答。这绝对是我倾向于的那个。如果我也采用存储库模式,那么这将大大有助于消除大量重复
    猜你喜欢
    • 2017-09-19
    • 2016-10-07
    • 1970-01-01
    • 2017-01-06
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    • 2015-03-08
    相关资源
    最近更新 更多