服務提供者是一個有效的將工具與業務解耦的方案,下面結合一個實用案例來解釋服務提供者在實現提供基礎服務的工具中的應用。
服務提供者
服務提供者是 Laravel 應用啓動的中心,所有 Laravel 的核心服務都是通過服務提供者啓動,通過使用服務提供者來管理類的依賴和執行依賴注入,可以很好地將一些底層服務與應用層代碼解耦。
短信服務
短信服務對於一個系統來說,是基礎的、通用的服務,不依賴於業務細節,所以應該將其與業務解耦。
系統設計
將不同服務商的sdk稱爲驅動,短信服務應該滿足以下需求:
- 可替換驅動
- 驅動實例化對使用者透明
1、可替換驅動
要滿足第1點,首先應該使用接口約束對外暴露的方法,只有驅動滿足接口約束,才能不影響業務直接替換驅動,於是設計接口:
<?php
namespace App\Interfaces;
Interface SmsDriverInterface
{
public function sendMessage($phones, $template_id, $parameters);
}
如果接入的廠商是七牛雲,則創建七牛雲短信驅動,並在驅動中調用SDK實現功能:
<?php
namespace App;
use Qiniu\Auth;
use Qiniu\Http\Client;
use Qiniu\Http\Error;
use Qiniu\Sms\Sms;
class QiniuDriver implements SmsDriverInterface
{
const HOST = 'https://sms.qiniuapi.com';
const VERSION = 'v1';
public function __construct()
{
$this->auth = new Auth(config('access_key'), config('secret_key'));
$this->sms = new Sms($this->auth);
}
public function sendMessage($phones, $template_id, $parameters = [])
{
$phones = array_map(function($item)
{
if(!is_string($item))
{
$item = strval($item);
}
return $item;
}, $phones);
$ret = $this->sms->sendMessage($template_id, $phones, $parameters);
$result = $this->getResultApiRet($ret);
return $result;
}
}
別的廠商的驅動也這樣實現接口SmsDriverInterface,在更換廠商的時候,換一個驅動實例化就可以了。
2、驅動實例化對使用者透明
此處的使用者就是使用短信服務實現業務需求的工程師啦,因爲短信服務的基礎、通用的特性,會被在業務中很多地方使用,如果更換驅動的話,會涉及很多具體業務代碼的修改,所以需要創建一個服務類,用來統籌驅動的使用,具體業務中再調用這個服務類,驅動的實例化就對業務透明瞭:
<?php
namespace App;
class SmsService
{
public $driver;
public function driver($driver_name)
{
switch($driver_name)
{
case 'qiniu':
$this->driver = new QiniuDriver();
break;
case 'aliyun':
$this->driver = new AliyunDriver();
break;
}
}
public function senndMessage($phone, $template_id)
{
return $this->driver->sendMessage($phone, $template_id);
}
}
再做改進,將傳參選擇驅動類型改爲從配置中獲取驅動類型:
<?php
namespace App;
class SmsService
{
public $driver;
public function driver()
{
switch(config('driver'))
{
case 'qiniu':
$this->driver = new QiniuDriver();
break;
case 'aliyun':
$this->driver = new AliyunDriver();
break;
}
}
......
}
至此,基本滿足了剛纔所說的2點基本需求,但是,在增加驅動的時候要去改動driver()中的代碼,增加其它驅動項,在服務類中枚舉出所有的驅動似乎不夠簡潔,這裏可以使用服務提供者再做優化:
<?php
namespace App\Providers;
use App\Interfaces\SmsDriverInterface;
use App\NullSms;
use App\QiniuSms;
use App\SmsService;
use Illuminate\Support\ServiceProvider;
class SmsServiceProvider extends ServiceProvider
{
protected $defer = true;
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->app->singleton(SmsDriverInterface::class, function ($app) {
switch(config('driver'))
{
case 'qiniu':
return new QiniuDriver();
}
return new NullSms();
});
}
public function provides()
{
return [SmsDriverInterface::class];
}
}
這裏將驅動接口與配置中指定的具體驅動綁定,在服務類中的實例化方式可以改成依賴注入,因爲短信驅動應該使用單例並且爲了調用短信服務方便,將服務類中方法改爲靜態方法:
namespace App;
class SmsService
{
public $driver;
public function __construct(SmsDriverInterface $driver)
{
$this->driver = $driver;
}
public static function driver()
{
return app(self::class)->driver;
}
public static function sendMessage($phone, $template_id)
{
return SmsSend::driver()->sendMessage($phone, $template_id);
}
}
最後將短信服務提供者添加到config/app.php文件的providers列表中,短信服務者的開發就完成了。
在這個案例中,短信服務提供者通過服務容器的依賴注入,實現了驅動在服務類中的自動實例化,在邏輯上將底層驅動與上層服務解耦。
這個案例比較簡單,還有很多可以完善的地方,比如,不在服務提供者中能夠使用switch去匹配驅動類型,而是增加一個驅動管理器,根據命名規則去實例化對應的驅動,這樣的話就達到了增加並更換驅動的時候,只增加驅動類,以及更換config配置,就能“平滑”替換驅動的目的。