Laravel: Service/Repository 模式和复制代码

Laravel: Service/Repository Pattern and duplicating code

在我的项目中,我决定使用服务模式(可能与存储库模式一起使用)来处理我的应用程序中的业务逻辑。例如,我有一个代表客户的 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 classes 创建 ClientUser 个实例,然后在触发发送电子邮件的事件之前执行关联。这种方法意味着我没有复制代码,但是我将 classes 耦合在一起并且我打破了一些 SOLID 原则。我不确定,但我觉得这也不适合测试。

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

  3. 简单地将我在 ClientInterestService::createClientAndUser(...) 中的逻辑放入我的控制器中。这样做意味着我的控制器中有业务逻辑,这在某种程度上破坏了提供服务的意义。

最适合我的是您提出的 #2 解决方案。

我喜欢做的是构建两个服务 classes 并查看有什么重复,然后 refactor/extract 与另一个 class 的任何重复。这样,所有 classes 都非常可测试,并且您破坏任何 SOLID 原则的可能性最小。

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

  • 创建客户
  • 创建用户
  • 关联(通过枢轴 table、连接点 table 等)
  • 电子邮件

为避免可怕的重复代码,您将在服务 class 或 classes 中围绕每个代码创建一个方法。然后,您将创建一个操作,封装所有基于这些方法的相关步骤。

不要害怕在您的服务之外实现东西 class - 这并不意味着它在您的服务层之外。

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

<?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 class,您仍然在服务层内部执行业务逻辑。


如果需要两项或更多服务,我会采取稍微不同的方法,将 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));

?>