Service Locator Pattern và Laravel

Service Locator là một Design Pattern phổ biến trong ngôn ngữ PHP cũng như các ngôn ngữ khác. Ý tưởng đằng sau Service Locator là thay vì hard code
khởi tạo các class khác, ta tạo ra một class trung tâm có thể chứa, khởi tao và định vị tất cả các class khác cần thiết cho ứng dụng. Từ đó giúp cho ứng dụng trở nên dễ bảo trì và mở rộng hơn.

1. Tổng quan về Service Locator

  • Nếu nhìn qua ở ví dụ ở dưới đây, ta có thể dễ dàng thấy rằng Service Locator ở trên không quan tâm cách hoạt động của các class khác như thế nào. Nó chỉ có nhiệm vụ chính là đăng ký (addInstance, addClass) và trả lại class (get), trong trường hợp chưa có thì nó sẽ khởi tạo vào lưu lại như là Singleton.
<?php

namespace DesignPatterns\More\ServiceLocator;

use OutOfRangeException;
use InvalidArgumentException;

class ServiceLocator
{
    /**
     * @var string[][]
     */
    private array $services = [];

    /**
     * @var Service[]
     */
    private array $instantiated = [];

    public function addInstance(string $class, Service $service)
    {
        $this->instantiated[$class] = $service;
    }

    public function addClass(string $class, array $params)
    {
        $this->services[$class] = $params;
    }

    public function has(string $interface): bool
    {
        return isset($this->services[$interface]) || isset($this->instantiated[$interface]);
    }

    public function get(string $class): Service
    {
        if (isset($this->instantiated[$class])) {
            return $this->instantiated[$class];
        }

        $object = new $class(...$this->services[$class]);

        if (!$object instanceof Service) {
            throw new InvalidArgumentException('Could not register service: is no instance of Service');
        }

        $this->instantiated[$class] = $object;

        return $object;
    }
}
  • Để sử dụng Service Locator ta chỉ cần khởi tạo.
<?php

use DesignPatterns\More\ServiceLocator;
use DesignPatterns\Service\LogServiceInterface;

class Service
{
    private ServiceLocator $serviceLocator;

    public function __construct(ServiceLocator $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }

    public function doSomething()
    {
        $this->serviceLocator->get(LogServiceInterface::class)->info('...');
    }
}

2. Service Locator trong Laravel

  • Ở trong Laravel, Service Container mặc dù không được định nghĩa rõ ràng là một Service Locator trong document, mà chỉ được định nghĩa là một công cụ để quản lý lớp phụ thuộc. Nhưng bản thân nó cung cấp cho chúng ta những phương thức tương tự để sử dụng như một Service Locator.
  • Để đăng ký một service ta có thể làm như sau.
<?php

namespace App\Providers;

use App\Services\LoggerServiceInterface;
use App\Services\LoggerService;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(LoggerServiceInterface::class, LoggerService::class);
    }
}
  • Để lấy ra service cần dùng.
<?php

namespace DesignPatterns\Service;

use Illuminate\Contracts\Container\Container;

class OtherService
{
    private Container $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    public function doSomething()
    {
        $this->container->make(LoggerServiceInterface::class)->info('...');
    }
}
  • Hoặc đơn giản hơn.
<?php

namespace DesignPatterns\Service;

class OtherService
{
    public function doSomething()
    {
        app(LoggerServiceInterface::class)->info('...');
    }
}

3. Nhược điểm trong việc sử dụng Serivce Locator

  • Mặc dù Service Locator giúp chúng ta có thể tách riêng các đoạn khởi tạo class ra và tập trung vào một class làm nhiệm vụ chuyên biệt. Có một số quan điểm cho rằng Service Locator là một anti design pattern với những đặc điểm như sau:
  • Thay vì để lộ chúng ra các class phụ thuộc như Dependency Injection, việc sử dụng Service Locator lại ấn các class đó đi, để lộ ra mỗi class phụ thuộc là chính Service Locator.
  • Để biết thực sự class hiện tại đang phụ thuộc vào những class nào chúng ta phải mất thời gian đọc qua toàn bộ class và tìm đến những chỗ sử dụng Service Locator. Việc kiểm soát class có bị phình to hay không hoặc việc quyết định mock các class là cần thiết trở nên khó khăn hơn.
  • Khi muốn thay thế các class ta lại phải tác động thẳng vào Service Locator.
  • Việc phụ thuộc vào Service Locator được coi là thừa thãi bởi vì chúng ta có thể lấy ra bất cứ class nào, mô hình chung tất cả class trở nên phụ thuộc nhau.